From 337aa24dd19bbee36af43dc65fdb28366f886268 Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sat, 9 May 2026 14:21:54 +0300 Subject: [PATCH 01/38] cli styleguide --- .ai/cli-guidelines.md | 1608 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1608 insertions(+) create mode 100644 .ai/cli-guidelines.md diff --git a/.ai/cli-guidelines.md b/.ai/cli-guidelines.md new file mode 100644 index 0000000..216a41c --- /dev/null +++ b/.ai/cli-guidelines.md @@ -0,0 +1,1608 @@ +# CLI UX Style Guide + +## 1. Core Principles + +### Rule 1. Treat the CLI as a product interface and an API + +A CLI is used both by humans and by scripts. Design every command, flag, output format, and exit code as a long-term contract. + +**Guidelines:** + +* Avoid breaking command names, flags, output schemas, and exit codes after release. +* Document deprecations before removing anything. +* Prefer additive changes over breaking changes. +* Assume users will automate every command. + +--- + +### Rule 2. Be human-first by default, machine-readable on demand + +The default experience should be pleasant for humans. Automation should be supported through explicit flags. + +**Use:** + +```bash +tool models list +``` + +For human-readable output. + +```bash +tool models list --json +``` + +For automation. + +**Required flags:** + +```bash +--json +--plain +--quiet +--no-input +``` + +--- + +### Rule 3. Make the happy path obvious + +A new user should understand what to do next after every major command. + +**Good output:** + +```text +✓ Model deployed: llama-3-8b + +Endpoint: + https://api.example.com/v1/chat/completions + +Next steps: + tool endpoint test llama-3-8b + tool logs llama-3-8b +``` + +**Avoid:** + +```text +Done. +``` + +--- + +## 2. Command Structure + +### Rule 4. Use a consistent command grammar + +Use a predictable hierarchy: + +```bash +tool +``` + +**Examples:** + +```bash +tool model list +tool model deploy +tool model delete + +tool endpoint create +tool endpoint list +tool endpoint test + +tool registry sync +tool registry inspect +``` + +Avoid mixing naming styles: + +```bash +tool deploy-model +tool modelRemove +tool start_inference +tool runModel +``` + +--- + +### Rule 5. Resources should be nouns, actions should be verbs + +Resources describe the object. Actions describe what happens to the object. + +**Good:** + +```bash +tool model deploy +tool endpoint delete +tool registry sync +``` + +**Bad:** + +```bash +tool deploy model +tool delete-endpoint +tool syncing-registry +``` + +--- + +### Rule 6. Use lowercase kebab-case + +All command names and flags should use lowercase kebab-case. + +**Good:** + +```bash +tool model deploy --model-name llama-3 --gpu-count 2 +``` + +**Bad:** + +```bash +tool Model Deploy --modelName llama-3 --GPUCount 2 +tool model deploy --model_name llama-3 +``` + +--- + +### Rule 7. Prefer explicit names over unclear abbreviations + +Use short names only when they are obvious and standard. + +**Good:** + +```bash +--namespace +--registry +--gpu-count +--model +--output +``` + +**Acceptable:** + +```bash +--cpu +--gpu +--ram +--url +``` + +**Bad:** + +```bash +--ns +--reg +--gc +--mdl +--outp +``` + +Short aliases may exist, but they must not replace clear long names. + +```bash +-o, --output +-f, --file +-h, --help +``` + +--- + +## 3. Arguments and Flags + +### Rule 8. Use positional arguments only for obvious primary inputs + +Use positional arguments when there is only one natural value. + +**Good:** + +```bash +tool model inspect llama-3-8b +tool endpoint delete production-chat +``` + +Use flags when values may be confused. + +**Good:** + +```bash +tool model copy --from llama-3-8b --to llama-3-8b-prod +``` + +**Bad:** + +```bash +tool model copy llama-3-8b llama-3-8b-prod +``` + +The second version forces users to remember argument order. + +--- + +### Rule 9. Required values should usually be flags in complex commands + +For complex operations, prefer named flags. + +**Good:** + +```bash +tool model deploy \ + --model llama-3-8b \ + --namespace production \ + --gpu a100 \ + --replicas 2 +``` + +**Bad:** + +```bash +tool model deploy llama-3-8b production a100 2 +``` + +--- + +### Rule 10. Boolean flags should be positive and explicit + +Prefer positive names. + +**Good:** + +```bash +--enable-cache +--verify-checksum +--wait +``` + +Avoid confusing negatives. + +```bash +--no-disable-cache +--dont-wait +``` + +For disabling default behavior, `--no-*` is acceptable. + +```bash +--no-color +--no-input +--no-cache +``` + +--- + +### Rule 11. Use consistent flag names across commands + +The same concept must have the same flag name everywhere. + +Use consistently: + +```bash +--namespace +--output +--format +--quiet +--json +--yes +--no-input +--dry-run +``` + +Do not mix: + +```bash +--namespace +--ns +--project +--workspace +``` + +unless they represent genuinely different concepts. + +--- + +### Rule 12. Every destructive command must support explicit confirmation + +Interactive confirmation: + +```bash +tool endpoint delete production-chat +``` + +```text +This will delete endpoint "production-chat". +Type "production-chat" to confirm: +``` + +Automation confirmation: + +```bash +tool endpoint delete production-chat --confirm production-chat +``` + +For less dangerous operations, `--yes` is acceptable: + +```bash +tool cache clear --yes +``` + +--- + +### Rule 13. Every complex write operation should support `--dry-run` + +Use `--dry-run` to preview changes without applying them. + +```bash +tool model deploy llama-3-70b --dry-run +``` + +Example output: + +```text +Dry run: no changes will be applied. + +Would create: + Deployment: llama-3-70b + Replicas: 2 + GPU type: a100-80gb + Endpoint: /v1/chat/completions + +Estimated resources: + GPU memory: 160GB + Storage: 140GB +``` + +--- + +## 4. Help and Documentation + +### Rule 14. Every command must support `--help` + +These should work: + +```bash +tool --help +tool model --help +tool model deploy --help +tool help model deploy +``` + +--- + +### Rule 15. Help output must follow a standard structure + +Recommended format: + +```text +Description: + Deploy a model and expose it through an OpenAI-compatible endpoint. + +Usage: + tool model deploy [flags] + +Examples: + tool model deploy llama-3-8b + tool model deploy llama-3-70b --gpu a100 --replicas 2 + tool model deploy llama-3-8b --namespace production --json + +Flags: + --gpu string GPU type to use + --replicas int Number of replicas + --namespace string Target namespace + --json Output result as JSON + --dry-run Preview changes without applying them + -h, --help Show help + +Related commands: + tool model list + tool endpoint test + tool logs +``` + +--- + +### Rule 16. Put examples before exhaustive reference + +Users copy examples more often than they read full flag descriptions. + +Each command should include at least: + +* One minimal example. +* One realistic production example. +* One automation-friendly example. + +--- + +### Rule 17. Show help when the user makes a recoverable mistake + +When required input is missing, show the problem, usage, and examples. + +**Good:** + +```text +Missing required argument: + +Usage: + tool model deploy [flags] + +Examples: + tool model deploy llama-3-8b + tool model deploy llama-3-70b --gpu a100 --replicas 2 +``` + +**Bad:** + +```text +Error: invalid args +``` + +--- + +## 5. Output Style + +### Rule 18. Default output should be concise and useful + +Default output should answer: + +1. What happened? +2. What changed? +3. What should I do next? + +**Good:** + +```text +✓ Endpoint created: production-chat + +URL: + https://api.example.com/v1/chat/completions + +Next: + tool endpoint test production-chat +``` + +--- + +### Rule 19. Use tables for lists + +Use aligned tables for human-readable lists. + +```text +NAME STATUS MODEL GPU REPLICAS +production-chat ready llama-3-8b a10g 2 +staging-chat pending mistral-7b l4 1 +``` + +Keep table columns stable across releases. + +--- + +### Rule 20. Use detailed views for single resources + +For one resource, use grouped key-value output. + +```text +Endpoint: production-chat + +Status: ready +Model: llama-3-8b +GPU: a10g +Replicas: 2 +URL: https://api.example.com/v1/chat/completions +Created: 2026-05-09 12:30:00 +``` + +--- + +### Rule 21. Machine-readable output must be stable + +For `--json`, use predictable field names and avoid decorative content. + +```json +{ + "name": "production-chat", + "status": "ready", + "model": "llama-3-8b", + "gpu": "a10g", + "replicas": 2, + "url": "https://api.example.com/v1/chat/completions" +} +``` + +Do not include spinners, warnings, progress, or human instructions in JSON output. + +--- + +### Rule 22. Separate stdout and stderr + +Use `stdout` for command results. + +Use `stderr` for: + +* Errors. +* Warnings. +* Progress. +* Debug logs. +* Spinners. +* Deprecation notices. + +This must work cleanly: + +```bash +tool model list --json | jq '.models[]' +``` + +--- + +### Rule 23. Support output formatting + +Recommended flags: + +```bash +--json +--plain +--table +--wide +--quiet +``` + +Example: + +```bash +tool endpoint list --output json +tool endpoint list --output table +tool endpoint list --quiet +``` + +Pick either `--json` or `--output json` as the primary convention. Supporting both is acceptable, but the documentation should prefer one. + +--- + +## 6. Error Design + +### Rule 24. Errors must explain what happened and how to fix it + +A good error includes: + +1. Problem. +2. Cause. +3. Fix. +4. Command to try. +5. Optional error code. + +**Good:** + +```text +Error: model "llama-3-70b" requires more GPU memory. + +Required: + 80GB GPU memory + +Available: + 40GB GPU memory + +Try: + tool model deploy llama-3-8b + tool nodepool add --gpu a100-80gb + +Error code: + MODEL_GPU_MEMORY_INSUFFICIENT +``` + +**Bad:** + +```text +Error: failed +``` + +--- + +### Rule 25. Use stable error codes + +Every important error should have a stable machine-readable code. + +Examples: + +```text +MODEL_NOT_FOUND +AUTH_REQUIRED +PERMISSION_DENIED +REGISTRY_UNAVAILABLE +GPU_MEMORY_INSUFFICIENT +CHECKSUM_FAILED +CONFIG_INVALID +``` + +These are useful for support, docs, telemetry, and automation. + +--- + +### Rule 26. Suggest the closest valid command or value + +When the user mistypes something, suggest a correction. + +```text +Unknown command: modle + +Did you mean? + tool model +``` + +For invalid values: + +```text +Unknown GPU type: a1000 + +Available GPU types: + a10g + l4 + a100 + h100 +``` + +--- + +### Rule 27. Do not expose internal stack traces by default + +Default errors should be user-facing. + +**Bad:** + +```text +thread 'main' panicked at src/scheduler.rs:194 +``` + +**Good:** + +```text +Error: scheduler is unavailable. + +The API gateway could not connect to the scheduler service. + +Try: + tool status + tool logs scheduler +``` + +Expose debug details only with: + +```bash +--debug +--verbose +``` + +--- + +## 7. Progress and Long-Running Operations + +### Rule 28. Never leave users staring at a blank terminal + +For operations longer than a few seconds, show progress. + +Use: + +* Spinner for unknown duration. +* Step counter for multi-step workflows. +* Progress bar for measurable downloads or uploads. +* Live status for deployments. + +--- + +### Rule 29. Use step-based progress for workflows + +Example: + +```text +Deploying model: llama-3-8b + +✓ Validated model license +✓ Checked GPU compatibility +✓ Downloaded model weights +✓ Verified checksum +→ Creating runtime artifact + Starting endpoint +``` + +--- + +### Rule 30. Show measurable progress when possible + +For downloads: + +```text +Downloading model weights... 12.4GB / 32.0GB +``` + +For parallel tasks: + +```text +Preparing shards... 6 / 16 +``` + +For deployment: + +```text +Starting replicas... 1 / 2 ready +``` + +--- + +### Rule 31. Clean up progress output after completion + +Final output should be readable and not contain dozens of spinner redraws. + +**Good final output:** + +```text +✓ Downloaded model weights +✓ Verified checksum +✓ Created runtime artifact +✓ Endpoint is ready +``` + +--- + +## 8. Interactivity + +### Rule 32. Interactive prompts are allowed only when stdin is a TTY + +Do not prompt in CI, scripts, or piped commands. + +This should never hang: + +```bash +tool model deploy llama-3-8b --json > result.json +``` + +--- + +### Rule 33. Every prompt must have a non-interactive equivalent + +Interactive: + +```bash +tool init +``` + +Non-interactive: + +```bash +tool init \ + --project production \ + --registry s3://company-models \ + --namespace llm-prod \ + --no-input +``` + +--- + +### Rule 34. Prompts should be specific and safe + +**Good:** + +```text +Select a registry: + + 1. Hugging Face + 2. S3 + 3. OCI registry + 4. Internal registry + +Registry: +``` + +**Bad:** + +```text +Input: +``` + +--- + +### Rule 35. Defaults should be visible + +Show default values in prompts. + +```text +Namespace [default: default]: +Replicas [default: 1]: +GPU type [default: auto]: +``` + +--- + +## 9. Automation and CI/CD + +### Rule 36. Commands must be scriptable + +Every command used in automation should support: + +```bash +--json +--quiet +--no-input +--yes +--dry-run +``` + +Where relevant. + +--- + +### Rule 37. Exit codes must be meaningful + +Recommended baseline: + +```text +0 Success +1 General error +2 Invalid usage +3 Not found +4 Permission denied +5 Authentication required +6 Conflict +7 Validation failed +8 External dependency unavailable +``` + +Document all public exit codes. + +--- + +### Rule 38. `--quiet` should suppress non-essential output + +`--quiet` should show only the final result or nothing on success. + +Example: + +```bash +tool model deploy llama-3-8b --quiet +``` + +Acceptable output: + +```text +production-chat +``` + +Or no output, depending on command semantics. + +--- + +### Rule 39. `--verbose` and `--debug` should be separate + +Use: + +```bash +--verbose +``` + +For more user-facing detail. + +Use: + +```bash +--debug +``` + +For diagnostic information useful to engineers and support. + +Do not expose secrets in either mode. + +--- + +## 10. Configuration + +### Rule 40. Use a clear configuration precedence order + +Recommended order: + +```text +1. CLI flags +2. Environment variables +3. Project config +4. User config +5. System config +6. Built-in defaults +``` + +Document this clearly. + +--- + +### Rule 41. Flags must override environment variables + +Example: + +```bash +TOOL_NAMESPACE=staging tool model deploy llama-3-8b --namespace production +``` + +The command should use: + +```text +production +``` + +not: + +```text +staging +``` + +--- + +### Rule 42. Config commands should be explicit + +Use: + +```bash +tool config get +tool config set registry s3://company-models +tool config unset registry +tool config list +``` + +Avoid hidden magic. + +--- + +### Rule 43. Show config source when helpful + +For debugging: + +```bash +tool config list --show-source +``` + +Example output: + +```text +KEY VALUE SOURCE +registry s3://company-models project config +namespace production environment +gpu auto default +``` + +--- + +## 11. Color and Accessibility + +### Rule 44. Color must never be the only source of meaning + +Do not rely only on red, green, or yellow. Use symbols and text too. + +**Good:** + +```text +✓ Ready +⚠ Warning +✗ Failed +``` + +**Bad:** + +```text +Ready +Warning +Failed +``` + +with meaning expressed only through color. + +--- + +### Rule 45. Support `--no-color` + +Every command should respect: + +```bash +--no-color +``` + +And the environment variable: + +```bash +NO_COLOR=1 +``` + +--- + +### Rule 46. Use color sparingly + +Recommended color semantics: + +```text +Green success +Yellow warning +Red error +Blue links or neutral emphasis +Gray secondary metadata +``` + +Avoid colorful output that competes with the content. + +--- + +## 12. Naming Conventions + +### Rule 47. Use consistent action verbs + +Recommended verbs: + +```text +create +list +get +inspect +update +delete +deploy +start +stop +restart +sync +test +logs +status +doctor +``` + +Avoid synonyms for the same action. + +Do not mix: + +```text +list +show-all +print +display +``` + +Pick one. + +--- + +### Rule 48. Use `list` for collections and `get` or `inspect` for one item + +Collections: + +```bash +tool model list +tool endpoint list +``` + +Single resource: + +```bash +tool model inspect llama-3-8b +tool endpoint get production-chat +``` + +Choose either `get` or `inspect` as the preferred single-resource verb. + +For developer tools, `inspect` is often more descriptive. + +--- + +### Rule 49. Use `delete`, not `remove`, unless there is a semantic difference + +Recommended: + +```bash +tool endpoint delete production-chat +``` + +Use `remove` only when detaching something rather than destroying it. + +Example: + +```bash +tool endpoint remove-label production-chat stable +``` + +--- + +### Rule 50. Use `status` for system state + +```bash +tool status +tool model status llama-3-8b +tool endpoint status production-chat +``` + +`status` should be quick, readable, and safe. + +--- + +### Rule 51. Use `doctor` for diagnostics + +```bash +tool doctor +``` + +Should check: + +```text +✓ CLI version +✓ Authentication +✓ Cluster access +✓ Registry access +✓ GPU nodes +✓ Scheduler status +✓ API gateway status +``` + +--- + +## 13. Safety and Trust + +### Rule 52. Dangerous commands must be hard to run accidentally + +Use confirmation for: + +* Delete. +* Reset. +* Destroy. +* Force deploy. +* Overwrite. +* Production changes. +* Expensive operations. + +--- + +### Rule 53. Use `--force` only for bypassing safety checks + +`--force` should mean: “I know this is risky.” + +Do not use `--force` as a generic fix for validation problems. + +Bad: + +```bash +tool model deploy llama-3 --force +``` + +when the user simply forgot a namespace. + +Good: + +```bash +tool endpoint delete production-chat --force --confirm production-chat +``` + +--- + +### Rule 54. Show cost or resource impact before expensive operations + +For infrastructure-heavy commands, show estimates. + +```text +This deployment may allocate: + + GPUs: 2 × a100-80gb + Memory: 160GB + Storage: 140GB + Replicas: 2 + +Continue? [y/N] +``` + +--- + +### Rule 55. Never print secrets by default + +Mask secrets: + +```text +API key: sk-...x92a +``` + +Provide explicit reveal commands only when necessary: + +```bash +tool auth token show --reveal +``` + +--- + +## 14. Resilience and Idempotency + +### Rule 56. Commands should be safe to retry + +If a command fails midway, running it again should not create duplicate resources. + +Example: + +```bash +tool model deploy llama-3-8b +``` + +If the model was already downloaded, the command should reuse it. + +```text +✓ Model weights already downloaded +✓ Checksum verified +→ Resuming deployment +``` + +--- + +### Rule 57. Long operations should resume when possible + +Especially for: + +* Model downloads. +* Artifact builds. +* Image pushes. +* Deployments. +* Registry syncs. + +--- + +### Rule 58. Partial failure should be explicit + +```text +Deployment partially completed. + +Completed: + ✓ Model downloaded + ✓ Checksum verified + +Failed: + ✗ Endpoint creation + +Reason: + Namespace "production" does not exist. + +Try: + tool namespace create production + tool model deploy llama-3-8b --namespace production +``` + +--- + +## 15. Versioning and Deprecation + +### Rule 59. Show CLI version + +Support: + +```bash +tool version +tool --version +``` + +Output should include: + +```text +CLI version +Build commit +API version +Server compatibility +``` + +Example: + +```text +tool version 1.4.2 +API version: v1 +Build: 9f3a12c +``` + +--- + +### Rule 60. Warn before removing commands or flags + +Deprecation warning: + +```text +Warning: --model-id is deprecated and will be removed in v2.0. +Use --model instead. +``` + +--- + +### Rule 61. Deprecation messages must include replacement guidance + +Bad: + +```text +Warning: deprecated. +``` + +Good: + +```text +Warning: "tool deploy" is deprecated and will be removed in v2.0. + +Use: + tool model deploy +``` + +--- + +## 16. Autocomplete + +### Rule 62. Provide shell completion + +Support: + +```bash +tool completion bash +tool completion zsh +tool completion fish +``` + +--- + +### Rule 63. Completion should include dynamic resources where possible + +Autocomplete should suggest: + +* Commands. +* Flags. +* Models. +* Endpoints. +* Namespaces. +* Registries. +* Config keys. + +Example: + +```bash +tool endpoint inspect +``` + +Should suggest existing endpoints. + +--- + +## 17. Logging + +### Rule 64. Logs should be easy to follow + +Use: + +```bash +tool logs endpoint production-chat +tool logs scheduler +tool logs gateway +``` + +Support: + +```bash +--follow +--since +--tail +--level +``` + +Example: + +```bash +tool logs endpoint production-chat --follow --tail 100 +``` + +--- + +### Rule 65. Logs should not replace structured status + +Do not force users to inspect logs to understand normal state. + +Use: + +```bash +tool status +tool endpoint status production-chat +``` + +for state. + +Use: + +```bash +tool logs +``` + +for investigation. + +--- + +## 18. Recommended Global Flags + +Every CLI should consider these global flags: + +```bash +-h, --help +--version +--verbose +--debug +--quiet +--json +--no-color +--no-input +--config +--profile +``` + +For infrastructure tools: + +```bash +--namespace +--context +--dry-run +--yes +--confirm +--timeout +``` + +--- + +## 19. Recommended Command Set + +For an infrastructure or AI platform CLI, a strong baseline is: + +```bash +tool init +tool status +tool doctor +tool config get +tool config set +tool config list + +tool auth login +tool auth logout +tool auth status + +tool model list +tool model inspect +tool model deploy +tool model delete + +tool registry list +tool registry add +tool registry sync +tool registry inspect + +tool endpoint list +tool endpoint inspect +tool endpoint test +tool endpoint delete + +tool logs +tool version +tool completion +``` + +--- + +## 20. Output Examples + +### Success + +```text +✓ Model deployed: llama-3-8b + +Endpoint: + https://api.example.com/v1/chat/completions + +Next: + tool endpoint test llama-3-8b +``` + +--- + +### Warning + +```text +Warning: GPU type was not specified. + +Using automatic selection: + GPU: a10g + +To choose manually: + tool model deploy llama-3-8b --gpu a100 +``` + +--- + +### Error + +```text +Error: registry is unavailable. + +The CLI could not connect to: + s3://company-models + +Try: + tool registry inspect company-models + tool doctor + +Error code: + REGISTRY_UNAVAILABLE +``` + +--- + +### Dry run + +```text +Dry run: no changes will be applied. + +Would create: + Model deployment: llama-3-8b + Endpoint: production-chat + Replicas: 2 + GPU: a10g + +Would modify: + Namespace: production +``` + +--- + +### JSON + +```json +{ + "status": "ready", + "endpoint": { + "name": "production-chat", + "url": "https://api.example.com/v1/chat/completions" + }, + "model": { + "name": "llama-3-8b" + } +} +``` + +--- + +## 21. CLI Review Checklist + +Before releasing a command, check: + +```text +[ ] Does the command follow tool ? +[ ] Are names lowercase and kebab-case? +[ ] Does it support --help? +[ ] Does help include examples? +[ ] Are errors actionable? +[ ] Are stdout and stderr separated? +[ ] Is JSON output available where useful? +[ ] Does it work in CI without prompts? +[ ] Are destructive actions protected? +[ ] Is --dry-run available for complex write operations? +[ ] Are exit codes documented? +[ ] Are colors optional? +[ ] Is the command safe to retry? +[ ] Are deprecated flags handled gracefully? +[ ] Is the output useful after success? +[ ] Does the command suggest the next step? +``` + +--- + +## 22. Opinionated Defaults + +Use these defaults unless there is a strong reason not to: + +```text +Command style: tool +Case style: lowercase kebab-case +Default output: human-readable +Machine output: --json +Help: --help and help command +Progress: stderr +Result data: stdout +Errors: stderr +Color: enabled only in TTY +No color: --no-color and NO_COLOR +Automation: --quiet, --no-input, --yes +Safety: confirmation for destructive actions +Preview: --dry-run for write operations +Diagnostics: doctor and status commands +``` + +This can serve as the baseline style guide for designing a professional CLI with strong developer experience and enterprise readiness. From b5771ca6c53bad5c77158139ed1cd38faa2bea83 Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sat, 9 May 2026 14:24:50 +0300 Subject: [PATCH 02/38] cli styleguide --- .ai/cli-guidelines.md | 1672 +++++++---------------------------------- 1 file changed, 285 insertions(+), 1387 deletions(-) diff --git a/.ai/cli-guidelines.md b/.ai/cli-guidelines.md index 216a41c..72bd283 100644 --- a/.ai/cli-guidelines.md +++ b/.ai/cli-guidelines.md @@ -1,283 +1,46 @@ # CLI UX Style Guide -## 1. Core Principles +Design a CLI as both a product interface and a scriptable API. Commands, flags, +output schemas, and exit codes are long-term contracts. -### Rule 1. Treat the CLI as a product interface and an API +## Core Rules -A CLI is used both by humans and by scripts. Design every command, flag, output format, and exit code as a long-term contract. +1. Prefer additive changes. Document deprecations before removing commands, + flags, fields, or exit codes. +2. Default output is human-readable. Provide machine-readable output explicitly: + `--json`, `--plain`, `--quiet`, and `--no-input`. +3. Make successful commands answer: what happened, what changed, and what to do + next. +4. Use a consistent command grammar: `tool `. +5. Resources are nouns; actions are verbs: `tool model deploy`, + `tool endpoint delete`, `tool registry sync`. +6. Command names and flags use lowercase kebab-case. +7. Prefer explicit names over unclear abbreviations. Short aliases may exist + only beside clear long flags: `-o, --output`, `-f, --file`, `-h, --help`. -**Guidelines:** +## Arguments and Flags -* Avoid breaking command names, flags, output schemas, and exit codes after release. -* Document deprecations before removing anything. -* Prefer additive changes over breaking changes. -* Assume users will automate every command. - ---- - -### Rule 2. Be human-first by default, machine-readable on demand - -The default experience should be pleasant for humans. Automation should be supported through explicit flags. - -**Use:** - -```bash -tool models list -``` - -For human-readable output. - -```bash -tool models list --json -``` - -For automation. - -**Required flags:** - -```bash ---json ---plain ---quiet ---no-input -``` - ---- - -### Rule 3. Make the happy path obvious - -A new user should understand what to do next after every major command. - -**Good output:** - -```text -✓ Model deployed: llama-3-8b - -Endpoint: - https://api.example.com/v1/chat/completions - -Next steps: - tool endpoint test llama-3-8b - tool logs llama-3-8b -``` - -**Avoid:** - -```text -Done. -``` - ---- - -## 2. Command Structure - -### Rule 4. Use a consistent command grammar - -Use a predictable hierarchy: - -```bash -tool -``` - -**Examples:** - -```bash -tool model list -tool model deploy -tool model delete - -tool endpoint create -tool endpoint list -tool endpoint test - -tool registry sync -tool registry inspect -``` - -Avoid mixing naming styles: - -```bash -tool deploy-model -tool modelRemove -tool start_inference -tool runModel -``` - ---- - -### Rule 5. Resources should be nouns, actions should be verbs - -Resources describe the object. Actions describe what happens to the object. - -**Good:** - -```bash -tool model deploy -tool endpoint delete -tool registry sync -``` - -**Bad:** - -```bash -tool deploy model -tool delete-endpoint -tool syncing-registry -``` - ---- - -### Rule 6. Use lowercase kebab-case - -All command names and flags should use lowercase kebab-case. - -**Good:** - -```bash -tool model deploy --model-name llama-3 --gpu-count 2 -``` - -**Bad:** - -```bash -tool Model Deploy --modelName llama-3 --GPUCount 2 -tool model deploy --model_name llama-3 -``` - ---- - -### Rule 7. Prefer explicit names over unclear abbreviations - -Use short names only when they are obvious and standard. - -**Good:** - -```bash ---namespace ---registry ---gpu-count ---model ---output -``` - -**Acceptable:** - -```bash ---cpu ---gpu ---ram ---url -``` - -**Bad:** - -```bash ---ns ---reg ---gc ---mdl ---outp -``` - -Short aliases may exist, but they must not replace clear long names. - -```bash --o, --output --f, --file --h, --help -``` - ---- - -## 3. Arguments and Flags - -### Rule 8. Use positional arguments only for obvious primary inputs - -Use positional arguments when there is only one natural value. - -**Good:** +Use positional arguments only for obvious primary inputs: ```bash tool model inspect llama-3-8b tool endpoint delete production-chat ``` -Use flags when values may be confused. - -**Good:** +Use named flags when argument order is ambiguous or the command is complex: ```bash tool model copy --from llama-3-8b --to llama-3-8b-prod +tool model deploy --model llama-3-8b --namespace production --gpu a100 --replicas 2 ``` -**Bad:** - -```bash -tool model copy llama-3-8b llama-3-8b-prod -``` - -The second version forces users to remember argument order. - ---- - -### Rule 9. Required values should usually be flags in complex commands - -For complex operations, prefer named flags. - -**Good:** - -```bash -tool model deploy \ - --model llama-3-8b \ - --namespace production \ - --gpu a100 \ - --replicas 2 -``` - -**Bad:** - -```bash -tool model deploy llama-3-8b production a100 2 -``` - ---- - -### Rule 10. Boolean flags should be positive and explicit - -Prefer positive names. - -**Good:** - -```bash ---enable-cache ---verify-checksum ---wait -``` - -Avoid confusing negatives. - -```bash ---no-disable-cache ---dont-wait -``` - -For disabling default behavior, `--no-*` is acceptable. - -```bash ---no-color ---no-input ---no-cache -``` - ---- +Boolean flags should be positive: `--enable-cache`, `--verify-checksum`, +`--wait`. `--no-*` is acceptable only for disabling default behavior: +`--no-color`, `--no-input`, `--no-cache`. -### Rule 11. Use consistent flag names across commands +Use the same flag name for the same concept everywhere: -The same concept must have the same flag name everywhere. - -Use consistently: - -```bash +```text --namespace --output --format @@ -288,1132 +51,369 @@ Use consistently: --dry-run ``` -Do not mix: - -```bash ---namespace ---ns ---project ---workspace -``` - -unless they represent genuinely different concepts. - ---- - -### Rule 12. Every destructive command must support explicit confirmation - -Interactive confirmation: - -```bash -tool endpoint delete production-chat -``` - -```text -This will delete endpoint "production-chat". -Type "production-chat" to confirm: -``` - -Automation confirmation: - -```bash -tool endpoint delete production-chat --confirm production-chat -``` - -For less dangerous operations, `--yes` is acceptable: - -```bash -tool cache clear --yes -``` - ---- - -### Rule 13. Every complex write operation should support `--dry-run` - -Use `--dry-run` to preview changes without applying them. - -```bash -tool model deploy llama-3-70b --dry-run -``` - -Example output: - -```text -Dry run: no changes will be applied. - -Would create: - Deployment: llama-3-70b - Replicas: 2 - GPU type: a100-80gb - Endpoint: /v1/chat/completions - -Estimated resources: - GPU memory: 160GB - Storage: 140GB -``` - ---- - -## 4. Help and Documentation - -### Rule 14. Every command must support `--help` - -These should work: - -```bash -tool --help -tool model --help -tool model deploy --help -tool help model deploy -``` - ---- - -### Rule 15. Help output must follow a standard structure - -Recommended format: - -```text -Description: - Deploy a model and expose it through an OpenAI-compatible endpoint. - -Usage: - tool model deploy [flags] - -Examples: - tool model deploy llama-3-8b - tool model deploy llama-3-70b --gpu a100 --replicas 2 - tool model deploy llama-3-8b --namespace production --json - -Flags: - --gpu string GPU type to use - --replicas int Number of replicas - --namespace string Target namespace - --json Output result as JSON - --dry-run Preview changes without applying them - -h, --help Show help - -Related commands: - tool model list - tool endpoint test - tool logs -``` - ---- - -### Rule 16. Put examples before exhaustive reference - -Users copy examples more often than they read full flag descriptions. - -Each command should include at least: - -* One minimal example. -* One realistic production example. -* One automation-friendly example. - ---- - -### Rule 17. Show help when the user makes a recoverable mistake - -When required input is missing, show the problem, usage, and examples. - -**Good:** - -```text -Missing required argument: - -Usage: - tool model deploy [flags] - -Examples: - tool model deploy llama-3-8b - tool model deploy llama-3-70b --gpu a100 --replicas 2 -``` - -**Bad:** - -```text -Error: invalid args -``` - ---- - -## 5. Output Style - -### Rule 18. Default output should be concise and useful - -Default output should answer: - -1. What happened? -2. What changed? -3. What should I do next? - -**Good:** - -```text -✓ Endpoint created: production-chat - -URL: - https://api.example.com/v1/chat/completions - -Next: - tool endpoint test production-chat -``` - ---- - -### Rule 19. Use tables for lists - -Use aligned tables for human-readable lists. - -```text -NAME STATUS MODEL GPU REPLICAS -production-chat ready llama-3-8b a10g 2 -staging-chat pending mistral-7b l4 1 -``` - -Keep table columns stable across releases. - ---- - -### Rule 20. Use detailed views for single resources - -For one resource, use grouped key-value output. - -```text -Endpoint: production-chat - -Status: ready -Model: llama-3-8b -GPU: a10g -Replicas: 2 -URL: https://api.example.com/v1/chat/completions -Created: 2026-05-09 12:30:00 -``` - ---- - -### Rule 21. Machine-readable output must be stable - -For `--json`, use predictable field names and avoid decorative content. - -```json -{ - "name": "production-chat", - "status": "ready", - "model": "llama-3-8b", - "gpu": "a10g", - "replicas": 2, - "url": "https://api.example.com/v1/chat/completions" -} -``` - -Do not include spinners, warnings, progress, or human instructions in JSON output. - ---- - -### Rule 22. Separate stdout and stderr - -Use `stdout` for command results. - -Use `stderr` for: - -* Errors. -* Warnings. -* Progress. -* Debug logs. -* Spinners. -* Deprecation notices. - -This must work cleanly: - -```bash -tool model list --json | jq '.models[]' -``` - ---- - -### Rule 23. Support output formatting - -Recommended flags: - -```bash ---json ---plain ---table ---wide ---quiet -``` - -Example: - -```bash -tool endpoint list --output json -tool endpoint list --output table -tool endpoint list --quiet -``` - -Pick either `--json` or `--output json` as the primary convention. Supporting both is acceptable, but the documentation should prefer one. - ---- - -## 6. Error Design - -### Rule 24. Errors must explain what happened and how to fix it - -A good error includes: - -1. Problem. -2. Cause. -3. Fix. -4. Command to try. -5. Optional error code. - -**Good:** - -```text -Error: model "llama-3-70b" requires more GPU memory. - -Required: - 80GB GPU memory - -Available: - 40GB GPU memory - -Try: - tool model deploy llama-3-8b - tool nodepool add --gpu a100-80gb - -Error code: - MODEL_GPU_MEMORY_INSUFFICIENT -``` - -**Bad:** - -```text -Error: failed -``` - ---- - -### Rule 25. Use stable error codes - -Every important error should have a stable machine-readable code. - -Examples: - -```text -MODEL_NOT_FOUND -AUTH_REQUIRED -PERMISSION_DENIED -REGISTRY_UNAVAILABLE -GPU_MEMORY_INSUFFICIENT -CHECKSUM_FAILED -CONFIG_INVALID -``` - -These are useful for support, docs, telemetry, and automation. - ---- - -### Rule 26. Suggest the closest valid command or value - -When the user mistypes something, suggest a correction. - -```text -Unknown command: modle - -Did you mean? - tool model -``` - -For invalid values: - -```text -Unknown GPU type: a1000 - -Available GPU types: - a10g - l4 - a100 - h100 -``` - ---- - -### Rule 27. Do not expose internal stack traces by default - -Default errors should be user-facing. - -**Bad:** - -```text -thread 'main' panicked at src/scheduler.rs:194 -``` - -**Good:** - -```text -Error: scheduler is unavailable. - -The API gateway could not connect to the scheduler service. - -Try: - tool status - tool logs scheduler -``` - -Expose debug details only with: - -```bash ---debug ---verbose -``` - ---- - -## 7. Progress and Long-Running Operations - -### Rule 28. Never leave users staring at a blank terminal - -For operations longer than a few seconds, show progress. - -Use: - -* Spinner for unknown duration. -* Step counter for multi-step workflows. -* Progress bar for measurable downloads or uploads. -* Live status for deployments. - ---- - -### Rule 29. Use step-based progress for workflows - -Example: - -```text -Deploying model: llama-3-8b - -✓ Validated model license -✓ Checked GPU compatibility -✓ Downloaded model weights -✓ Verified checksum -→ Creating runtime artifact - Starting endpoint -``` - ---- - -### Rule 30. Show measurable progress when possible - -For downloads: - -```text -Downloading model weights... 12.4GB / 32.0GB -``` - -For parallel tasks: - -```text -Preparing shards... 6 / 16 -``` - -For deployment: - -```text -Starting replicas... 1 / 2 ready -``` - ---- - -### Rule 31. Clean up progress output after completion - -Final output should be readable and not contain dozens of spinner redraws. - -**Good final output:** - -```text -✓ Downloaded model weights -✓ Verified checksum -✓ Created runtime artifact -✓ Endpoint is ready -``` - ---- - -## 8. Interactivity - -### Rule 32. Interactive prompts are allowed only when stdin is a TTY - -Do not prompt in CI, scripts, or piped commands. - -This should never hang: - -```bash -tool model deploy llama-3-8b --json > result.json -``` - ---- - -### Rule 33. Every prompt must have a non-interactive equivalent - -Interactive: - -```bash -tool init -``` - -Non-interactive: - -```bash -tool init \ - --project production \ - --registry s3://company-models \ - --namespace llm-prod \ - --no-input -``` - ---- - -### Rule 34. Prompts should be specific and safe - -**Good:** - -```text -Select a registry: - - 1. Hugging Face - 2. S3 - 3. OCI registry - 4. Internal registry - -Registry: -``` - -**Bad:** - -```text -Input: -``` - ---- - -### Rule 35. Defaults should be visible - -Show default values in prompts. - -```text -Namespace [default: default]: -Replicas [default: 1]: -GPU type [default: auto]: -``` - ---- - -## 9. Automation and CI/CD - -### Rule 36. Commands must be scriptable - -Every command used in automation should support: - -```bash ---json ---quiet ---no-input ---yes ---dry-run -``` - -Where relevant. - ---- - -### Rule 37. Exit codes must be meaningful - -Recommended baseline: - -```text -0 Success -1 General error -2 Invalid usage -3 Not found -4 Permission denied -5 Authentication required -6 Conflict -7 Validation failed -8 External dependency unavailable -``` - -Document all public exit codes. - ---- - -### Rule 38. `--quiet` should suppress non-essential output - -`--quiet` should show only the final result or nothing on success. - -Example: - -```bash -tool model deploy llama-3-8b --quiet -``` - -Acceptable output: - -```text -production-chat -``` - -Or no output, depending on command semantics. - ---- - -### Rule 39. `--verbose` and `--debug` should be separate - -Use: - -```bash ---verbose -``` - -For more user-facing detail. - -Use: - -```bash ---debug -``` - -For diagnostic information useful to engineers and support. - -Do not expose secrets in either mode. - ---- - -## 10. Configuration - -### Rule 40. Use a clear configuration precedence order - -Recommended order: - -```text -1. CLI flags -2. Environment variables -3. Project config -4. User config -5. System config -6. Built-in defaults -``` - -Document this clearly. - ---- - -### Rule 41. Flags must override environment variables - -Example: - -```bash -TOOL_NAMESPACE=staging tool model deploy llama-3-8b --namespace production -``` - -The command should use: - -```text -production -``` - -not: - -```text -staging -``` - ---- - -### Rule 42. Config commands should be explicit - -Use: - -```bash -tool config get -tool config set registry s3://company-models -tool config unset registry -tool config list -``` - -Avoid hidden magic. - ---- - -### Rule 43. Show config source when helpful - -For debugging: - -```bash -tool config list --show-source -``` - -Example output: - -```text -KEY VALUE SOURCE -registry s3://company-models project config -namespace production environment -gpu auto default -``` - ---- - -## 11. Color and Accessibility - -### Rule 44. Color must never be the only source of meaning - -Do not rely only on red, green, or yellow. Use symbols and text too. - -**Good:** - -```text -✓ Ready -⚠ Warning -✗ Failed -``` - -**Bad:** - -```text -Ready -Warning -Failed -``` - -with meaning expressed only through color. - ---- - -### Rule 45. Support `--no-color` - -Every command should respect: - -```bash ---no-color -``` - -And the environment variable: - -```bash -NO_COLOR=1 -``` - ---- - -### Rule 46. Use color sparingly - -Recommended color semantics: - -```text -Green success -Yellow warning -Red error -Blue links or neutral emphasis -Gray secondary metadata -``` - -Avoid colorful output that competes with the content. - ---- - -## 12. Naming Conventions - -### Rule 47. Use consistent action verbs - -Recommended verbs: - -```text -create -list -get -inspect -update -delete -deploy -start -stop -restart -sync -test -logs -status -doctor -``` - -Avoid synonyms for the same action. - -Do not mix: - -```text -list -show-all -print -display -``` - -Pick one. - ---- - -### Rule 48. Use `list` for collections and `get` or `inspect` for one item - -Collections: - -```bash -tool model list -tool endpoint list -``` - -Single resource: - -```bash -tool model inspect llama-3-8b -tool endpoint get production-chat -``` - -Choose either `get` or `inspect` as the preferred single-resource verb. - -For developer tools, `inspect` is often more descriptive. - ---- - -### Rule 49. Use `delete`, not `remove`, unless there is a semantic difference - -Recommended: - -```bash -tool endpoint delete production-chat -``` - -Use `remove` only when detaching something rather than destroying it. - -Example: - -```bash -tool endpoint remove-label production-chat stable -``` - ---- - -### Rule 50. Use `status` for system state - -```bash -tool status -tool model status llama-3-8b -tool endpoint status production-chat -``` - -`status` should be quick, readable, and safe. +## Safety ---- +Every destructive command must support explicit confirmation. -### Rule 51. Use `doctor` for diagnostics +Interactive confirmation: -```bash -tool doctor +```text +This will delete endpoint "production-chat". +Type "production-chat" to confirm: ``` -Should check: +Automation confirmation: -```text -✓ CLI version -✓ Authentication -✓ Cluster access -✓ Registry access -✓ GPU nodes -✓ Scheduler status -✓ API gateway status +```bash +tool endpoint delete production-chat --confirm production-chat +tool cache clear --yes ``` ---- - -## 13. Safety and Trust +Complex write operations should support `--dry-run` and build the same planned +change without applying it: -### Rule 52. Dangerous commands must be hard to run accidentally +```text +Dry run: no changes will be applied. -Use confirmation for: +Would create: + Deployment: llama-3-70b + Replicas: 2 + GPU type: a100-80gb +``` -* Delete. -* Reset. -* Destroy. -* Force deploy. -* Overwrite. -* Production changes. -* Expensive operations. +Dangerous commands must be hard to run accidentally: delete, reset, destroy, +force deploy, overwrite, production changes, and expensive operations. Use +`--force` only to bypass explicit safety checks. Show cost or resource impact +before infrastructure-heavy operations. Never print secrets by default; mask +them unless a deliberate reveal command is used. ---- +## Help -### Rule 53. Use `--force` only for bypassing safety checks +Every command must support: -`--force` should mean: “I know this is risky.” +```bash +tool --help +tool model --help +tool model deploy --help +tool help model deploy +``` -Do not use `--force` as a generic fix for validation problems. +Help output should follow this shape: -Bad: +```text +Description: + Deploy a model and expose it through an OpenAI-compatible endpoint. -```bash -tool model deploy llama-3 --force -``` +Usage: + tool model deploy [flags] -when the user simply forgot a namespace. +Examples: + tool model deploy llama-3-8b + tool model deploy llama-3-70b --gpu a100 --replicas 2 + tool model deploy llama-3-8b --namespace production --json -Good: +Flags: + --gpu string GPU type to use + --replicas int Number of replicas + --namespace string Target namespace + --json Output result as JSON + --dry-run Preview changes without applying them + -h, --help Show help -```bash -tool endpoint delete production-chat --force --confirm production-chat +Related commands: + tool model list + tool endpoint test + tool logs ``` ---- +Put examples before exhaustive reference. Include at least one minimal example, +one realistic production example, and one automation-friendly example. For +recoverable usage errors, show the problem, usage, and examples. -### Rule 54. Show cost or resource impact before expensive operations +## Output -For infrastructure-heavy commands, show estimates. +Default output should be concise and useful: ```text -This deployment may allocate: +Endpoint created: production-chat - GPUs: 2 × a100-80gb - Memory: 160GB - Storage: 140GB - Replicas: 2 +URL: + https://api.example.com/v1/chat/completions -Continue? [y/N] +Next: + tool endpoint test production-chat ``` ---- - -### Rule 55. Never print secrets by default - -Mask secrets: +Use aligned tables for lists and keep columns stable: ```text -API key: sk-...x92a -``` - -Provide explicit reveal commands only when necessary: - -```bash -tool auth token show --reveal +NAME STATUS MODEL GPU REPLICAS +production-chat ready llama-3-8b a10g 2 +staging-chat pending mistral-7b l4 1 ``` ---- +Use grouped key-value output for one resource: -## 14. Resilience and Idempotency +```text +Endpoint: production-chat -### Rule 56. Commands should be safe to retry +Status: ready +Model: llama-3-8b +GPU: a10g +Replicas: 2 +URL: https://api.example.com/v1/chat/completions +Created: 2026-05-09 12:30:00 +``` -If a command fails midway, running it again should not create duplicate resources. +Machine-readable output must be stable and undecorated. Do not include spinners, +warnings, progress, or human instructions in JSON. -Example: +Use stdout for command results. Use stderr for errors, warnings, progress, +debug logs, spinners, and deprecation notices. This must work: ```bash -tool model deploy llama-3-8b +tool model list --json | jq '.models[]' ``` -If the model was already downloaded, the command should reuse it. +Support output formatting with a documented primary convention: ```text -✓ Model weights already downloaded -✓ Checksum verified -→ Resuming deployment +--json +--plain +--table +--wide +--quiet +--output ``` ---- +## Errors -### Rule 57. Long operations should resume when possible +Errors must explain the problem, cause, fix, command to try, and stable error +code when useful: -Especially for: +```text +Error: model "llama-3-70b" requires more GPU memory. -* Model downloads. -* Artifact builds. -* Image pushes. -* Deployments. -* Registry syncs. +Required: + 80GB GPU memory ---- +Available: + 40GB GPU memory -### Rule 58. Partial failure should be explicit +Try: + tool model deploy llama-3-8b + tool nodepool add --gpu a100-80gb -```text -Deployment partially completed. +Error code: + MODEL_GPU_MEMORY_INSUFFICIENT +``` -Completed: - ✓ Model downloaded - ✓ Checksum verified +Use stable machine-readable error codes such as `MODEL_NOT_FOUND`, +`AUTH_REQUIRED`, `PERMISSION_DENIED`, `REGISTRY_UNAVAILABLE`, +`GPU_MEMORY_INSUFFICIENT`, `CHECKSUM_FAILED`, and `CONFIG_INVALID`. -Failed: - ✗ Endpoint creation +Suggest close matches for mistyped commands or invalid values. Do not expose +internal stack traces by default; reserve diagnostic detail for `--verbose` or +`--debug`, and never expose secrets in either mode. -Reason: - Namespace "production" does not exist. +## Progress -Try: - tool namespace create production - tool model deploy llama-3-8b --namespace production -``` +For operations longer than a few seconds, show progress on stderr: + +```text +Deploying model: llama-3-8b ---- +OK Validated model license +OK Checked GPU compatibility +OK Downloaded model weights +OK Verified checksum +.. Creating runtime artifact + Starting endpoint +``` -## 15. Versioning and Deprecation +Use a spinner for unknown duration, step counters for workflows, progress bars +for measurable transfers, and live status for deployments. Clean up progress +output after completion so the final result is readable. -### Rule 59. Show CLI version +## Interactivity -Support: +Interactive prompts are allowed only when stdin is a TTY. Commands must never +hang in CI, scripts, or piped usage: ```bash -tool version -tool --version +tool model deploy llama-3-8b --json > result.json ``` -Output should include: +Every prompt needs a non-interactive equivalent using flags and `--no-input`. +Prompts must be specific, safe, and show defaults: ```text -CLI version -Build commit -API version -Server compatibility +Namespace [default: default]: +Replicas [default: 1]: +GPU type [default: auto]: ``` -Example: +## Automation and Exit Codes + +Automation-friendly commands should support `--json`, `--quiet`, `--no-input`, +`--yes`, and `--dry-run` where relevant. + +Baseline exit codes: ```text -tool version 1.4.2 -API version: v1 -Build: 9f3a12c +0 Success +1 General error +2 Invalid usage +3 Not found +4 Permission denied +5 Authentication required +6 Conflict +7 Validation failed +8 External dependency unavailable ``` ---- +Document all public exit codes. `--quiet` should suppress non-essential output +and emit only the final value or nothing on success. Keep `--verbose` +user-facing and `--debug` diagnostic. -### Rule 60. Warn before removing commands or flags +## Configuration -Deprecation warning: +Use and document this precedence order: ```text -Warning: --model-id is deprecated and will be removed in v2.0. -Use --model instead. +1. CLI flags +2. Environment variables +3. Project config +4. User config +5. System config +6. Built-in defaults ``` ---- +Flags must override environment variables. Config commands should be explicit: -### Rule 61. Deprecation messages must include replacement guidance +```bash +tool config get +tool config set registry s3://company-models +tool config unset registry +tool config list +tool config list --show-source +``` -Bad: +Show config source when debugging: ```text -Warning: deprecated. +KEY VALUE SOURCE +registry s3://company-models project config +namespace production environment +gpu auto default ``` -Good: +## Color and Accessibility -```text -Warning: "tool deploy" is deprecated and will be removed in v2.0. +Color must never be the only source of meaning; pair it with text or symbols. +Respect `--no-color` and `NO_COLOR=1`. Enable color only in TTY output and use +it sparingly: -Use: - tool model deploy +```text +Green success +Yellow warning +Red error +Blue links or neutral emphasis +Gray secondary metadata ``` ---- +## Naming + +Use consistent action verbs: -## 16. Autocomplete +```text +create +list +get +inspect +update +delete +deploy +start +stop +restart +sync +test +logs +status +doctor +``` -### Rule 62. Provide shell completion +Use `list` for collections and choose one of `get` or `inspect` for one item. +Prefer `delete` over `remove`; reserve `remove` for detaching something rather +than destroying it. Use `status` for quick system state and `doctor` for +diagnostics. -Support: +## Resilience -```bash -tool completion bash -tool completion zsh -tool completion fish -``` +Commands should be safe to retry and should not create duplicate resources after +partial failure. Long operations should resume where possible: model downloads, +artifact builds, image pushes, deployments, and registry syncs. ---- +Partial failure must be explicit: -### Rule 63. Completion should include dynamic resources where possible +```text +Deployment partially completed. -Autocomplete should suggest: +Completed: + OK Model downloaded + OK Checksum verified -* Commands. -* Flags. -* Models. -* Endpoints. -* Namespaces. -* Registries. -* Config keys. +Failed: + ERR Endpoint creation -Example: +Reason: + Namespace "production" does not exist. -```bash -tool endpoint inspect +Try: + tool namespace create production + tool model deploy llama-3-8b --namespace production ``` -Should suggest existing endpoints. +## Versioning and Deprecation ---- +Support `tool version` and `tool --version`. Include CLI version, build commit, +API version, and server compatibility when available. -## 17. Logging +Deprecation warnings must include removal timing and replacement guidance: -### Rule 64. Logs should be easy to follow +```text +Warning: "tool deploy" is deprecated and will be removed in v2.0. Use: - -```bash -tool logs endpoint production-chat -tool logs scheduler -tool logs gateway + tool model deploy ``` -Support: - -```bash ---follow ---since ---tail ---level -``` +## Completion and Logs -Example: +Provide shell completion: ```bash -tool logs endpoint production-chat --follow --tail 100 +tool completion bash +tool completion zsh +tool completion fish ``` ---- - -### Rule 65. Logs should not replace structured status - -Do not force users to inspect logs to understand normal state. +Completion should include commands, flags, models, endpoints, namespaces, +registries, and config keys where possible. -Use: +Logs should be easy to follow but should not replace structured status: ```bash +tool logs endpoint production-chat --follow --tail 100 +tool logs scheduler --since 1h --level warn tool status tool endpoint status production-chat ``` -for state. - -Use: - -```bash -tool logs -``` - -for investigation. - ---- - -## 18. Recommended Global Flags - -Every CLI should consider these global flags: +## Recommended Global Flags -```bash +```text -h, --help --version --verbose @@ -1424,11 +424,6 @@ Every CLI should consider these global flags: --no-input --config --profile -``` - -For infrastructure tools: - -```bash --namespace --context --dry-run @@ -1437,11 +432,7 @@ For infrastructure tools: --timeout ``` ---- - -## 19. Recommended Command Set - -For an infrastructure or AI platform CLI, a strong baseline is: +## Recommended Command Set ```bash tool init @@ -1475,118 +466,27 @@ tool version tool completion ``` ---- - -## 20. Output Examples - -### Success - -```text -✓ Model deployed: llama-3-8b - -Endpoint: - https://api.example.com/v1/chat/completions - -Next: - tool endpoint test llama-3-8b -``` - ---- - -### Warning - -```text -Warning: GPU type was not specified. - -Using automatic selection: - GPU: a10g - -To choose manually: - tool model deploy llama-3-8b --gpu a100 -``` - ---- - -### Error - -```text -Error: registry is unavailable. - -The CLI could not connect to: - s3://company-models - -Try: - tool registry inspect company-models - tool doctor - -Error code: - REGISTRY_UNAVAILABLE -``` - ---- - -### Dry run - -```text -Dry run: no changes will be applied. - -Would create: - Model deployment: llama-3-8b - Endpoint: production-chat - Replicas: 2 - GPU: a10g - -Would modify: - Namespace: production -``` - ---- - -### JSON - -```json -{ - "status": "ready", - "endpoint": { - "name": "production-chat", - "url": "https://api.example.com/v1/chat/completions" - }, - "model": { - "name": "llama-3-8b" - } -} -``` - ---- - -## 21. CLI Review Checklist - -Before releasing a command, check: +## Review Checklist ```text -[ ] Does the command follow tool ? -[ ] Are names lowercase and kebab-case? -[ ] Does it support --help? -[ ] Does help include examples? -[ ] Are errors actionable? -[ ] Are stdout and stderr separated? -[ ] Is JSON output available where useful? -[ ] Does it work in CI without prompts? -[ ] Are destructive actions protected? -[ ] Is --dry-run available for complex write operations? -[ ] Are exit codes documented? -[ ] Are colors optional? -[ ] Is the command safe to retry? -[ ] Are deprecated flags handled gracefully? -[ ] Is the output useful after success? -[ ] Does the command suggest the next step? +[ ] Follows tool +[ ] Uses lowercase kebab-case +[ ] Supports --help with examples +[ ] Has actionable errors and stable error codes +[ ] Separates stdout and stderr +[ ] Provides JSON output where useful +[ ] Works in CI without prompts +[ ] Protects destructive actions +[ ] Supports --dry-run for complex writes +[ ] Documents exit codes +[ ] Makes color optional +[ ] Is safe to retry +[ ] Handles deprecated flags gracefully +[ ] Produces useful success output +[ ] Suggests next steps where relevant ``` ---- - -## 22. Opinionated Defaults - -Use these defaults unless there is a strong reason not to: +## Opinionated Defaults ```text Command style: tool @@ -1604,5 +504,3 @@ Safety: confirmation for destructive actions Preview: --dry-run for write operations Diagnostics: doctor and status commands ``` - -This can serve as the baseline style guide for designing a professional CLI with strong developer experience and enterprise readiness. From 8b3fd56ea5fef7b536b8f40ae4f3a0084c355d8a Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sun, 10 May 2026 11:50:54 +0300 Subject: [PATCH 03/38] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BD=D0=B0=D0=B4=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.md | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 todo.md diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..9f462ef --- /dev/null +++ b/todo.md @@ -0,0 +1,243 @@ +# Task Plan: Add CLI Mode to avito-py + +## Project Context + +- Package name: `avito-py`. +- Import package: `avito`. +- Current public facade: `avito.client.AvitoClient`. +- Current config contract: `avito.config.AvitoSettings`. +- Current auth config contract: `avito.auth.settings.AuthSettings`. +- Packaging: Poetry-style `[tool.poetry]` in `pyproject.toml`; no console script is currently registered. +- Existing module entry point: `avito/__main__.py` is only a smoke check that creates `AvitoClient`. +- Architecture rule: CLI code must stay outside SDK core/domain/transport/auth layers. +- Styleguide constraints: + - do not return raw `dict` or `Any` from public SDK methods; + - do not mix CLI, transport, auth, parsing, or domain logic; + - error messages in SDK code are Russian only; + - avoid dead code and unused aliases; + - keep core SDK free of Typer imports. + +## Proposed CLI Shape + +Use `avito/cli/` instead of the ticket's generic `sdk/cli/`: + +```text +avito/ + cli/ + __init__.py + app.py + accounts.py + config.py + ui.py +``` + +Register the console command as `avito` unless product naming requires another command: + +```toml +[tool.poetry.scripts] +avito = "avito.cli.app:app" +``` + +Also consider registering `avito-cli` as a compatibility alias if product naming wants a CLI-specific command: + +```toml +[tool.poetry.scripts] +avito = "avito.cli.app:app" +avito-cli = "avito.cli.app:app" +``` + +Keep `avito/__main__.py` compatible. A later implementation can either leave it as the existing smoke check or route `python -m avito` to the Typer app if that is desired. + +## Data Model + +Persist CLI-local account records under an Avito-specific home directory: + +```text +~/.avito-py/ + config.json + accounts.json +``` + +Support override: + +```bash +MY_SDK_HOME=/custom/path avito account list +``` + +`MY_SDK_HOME` is required because the ticket names it explicitly. Also support `AVITO_PY_HOME` as the project-specific alias, with this precedence: + +1. `AVITO_PY_HOME` +2. `MY_SDK_HOME` +3. `Path.home() / ".avito-py"` + +Document both variables, but make clear that `MY_SDK_HOME` exists for ticket compatibility and `AVITO_PY_HOME` is the Avito-specific name. + +Suggested stored account fields: + +- `name: str` +- `client_id: str` +- `client_secret: str` +- `base_url: str` stored internally, exposed in CLI as both `--base-url` and ticket-compatible `--endpoint` +- `user_id: int | None` +- optional OAuth fields already supported by `AuthSettings`: `scope`, `refresh_token`, `token_url`, `alternate_token_url`, `autoteka_token_url`, `autoteka_client_id`, `autoteka_client_secret`, `autoteka_scope` + +The generic ticket example uses `--api-key`. Avito uses OAuth `client_id` and `client_secret`, so the canonical Avito flags should be `--client-id` and `--client-secret`. To satisfy the ticket's CLI shape without weakening the SDK contract, support `--api-key` as an alias for `--client-secret` and still require `--client-id` unless a future Avito auth mode removes that requirement. + +Do not print full secret values. Mask values such as `client_secret`, `api_key`, refresh tokens, and API-like tokens in CLI output. + +## Implementation Plan + +1. Add Typer dependency + - Add `typer` to `[tool.poetry.dependencies]`. + - Do not add `rich` unless Typer pulls it in or it is explicitly approved. + - Update the lock file through Poetry if dependency locking is part of the branch workflow. + +2. Add CLI package skeleton + - Create `avito/cli/__init__.py`. + - Create `avito/cli/app.py` with the root Typer app. + - Create `avito/cli/accounts.py` with an `account` subcommand app. + - Create `avito/cli/config.py` for CLI home resolution and JSON persistence. + - Create `avito/cli/ui.py` for shared output helpers. + +3. Add config/home resolver + - Implement `get_cli_home(env: Mapping[str, str] | None = None) -> Path`. + - Default to `Path.home() / ".avito-py"`. + - Respect `MY_SDK_HOME` for ticket compatibility. + - Respect `AVITO_PY_HOME` as a project-specific alias with higher precedence. + - Keep this logic independent from Typer so tests can call it directly. + - Create directories lazily when saving data, not on import. + +4. Add account storage layer + - Use frozen dataclasses for CLI account records where practical. + - Implement load/save functions or an `AccountStore` class in `avito/cli/config.py`. + - Store `accounts.json` and `config.json` separately: + - `accounts.json` contains named account records. + - `config.json` contains the active account name. + - Validate duplicate account names, missing active accounts, and malformed JSON. + - Keep messages and exceptions consistent with repository conventions. + +5. Add account commands + - `avito account add` + - Accept canonical flags: `--name`, `--client-id`, `--client-secret`, `--base-url`, and optional `--user-id`. + - Accept ticket-compatible aliases: `--api-key` for `--client-secret` and `--endpoint` for `--base-url`. + - Prompt interactively for missing required fields. + - Default base URL to `https://api.avito.ru`. + - `avito account list` + - Display all accounts with base URL and active marker. + - Mask sensitive fields if shown. + - `avito account use ` + - Set the active account in `config.json`. + - `avito account current` + - Display the currently active account. + - `avito account remove ` + - Confirm removal unless a `--yes` flag is supplied. + - If removing the active account, clear the active account. + +6. Add SDK client factory helper for future CLI commands + - Add a CLI-only helper that converts the active account record to `AvitoSettings`. + - This helper belongs in `avito/cli/config.py` or a future `avito/cli/client.py`, not in `avito/config.py`. + - Use `AvitoClient(settings)` from the CLI; do not duplicate SDK behavior. + - Any future CLI command that calls Avito API must use the active account automatically unless the command provides an explicit account override. + +7. Add UI helpers + - Implement: + - `success(message: str) -> None` + - `error(message: str) -> None` + - `warning(message: str) -> None` + - `info(message: str) -> None` + - `print_table(rows: list[dict[str, object]]) -> None` + - `confirm(message: str) -> bool` + - Prefer `typer.echo`, `typer.secho`, `typer.confirm`, and `typer.prompt`. + - Avoid raw `print()` in command modules. + +8. Register console command + - Add Poetry script entry: + + ```toml + [tool.poetry.scripts] + avito = "avito.cli.app:app" + ``` + + - Optionally add the alias if desired: + + ```toml + avito-cli = "avito.cli.app:app" + ``` + + - Verify: + + ```bash + poetry run avito --help + poetry run avito account --help + ``` + +9. Add tests + - Add focused tests under `tests/cli/`. + - Cover config home resolution: + - default home; + - `MY_SDK_HOME` override; + - `AVITO_PY_HOME` override; + - precedence when both variables are present. + - Cover account storage: + - add and reload account; + - set/get active account; + - remove inactive account; + - remove active account clears active config; + - sensitive value masking does not reveal full secrets. + - Cover CLI command surface with Typer's `CliRunner`: + - `avito --help`; + - `avito account --help`; + - non-interactive `account add --name dev --client-id ... --client-secret ... --endpoint ...`; + - non-interactive ticket-compatible `account add --name dev --client-id ... --api-key ... --endpoint ...`; + - `account use`, `account current`, `account list`, and `account remove --yes`. + - Prefer direct tests of the config/account storage layer for persistence edge cases. + +10. Update README + - Add a short CLI section: + + ```bash + avito account add --name dev --client-id ... --client-secret ... + avito account add --name dev --client-id ... --api-key ... --endpoint https://api.avito.ru + avito account use dev + avito account list + ``` + + - Document `MY_SDK_HOME` and `AVITO_PY_HOME`. + - Mention that secrets are stored locally and masked in output. + +11. Verification + - Minimum for this non-API-surface change: + + ```bash + poetry run pytest tests/cli + poetry run mypy avito + poetry run ruff check . + ``` + + - Before completing the branch, run: + + ```bash + make check + ``` + +## Acceptance Checklist + +- [ ] Typer dependency added. +- [ ] `avito/cli/` package exists and is isolated from SDK core. +- [ ] Console command is registered in `pyproject.toml`. +- [ ] `avito --help` works. +- [ ] `avito account --help` shows account commands. +- [ ] `account add` stores account data. +- [ ] `account list` lists accounts without exposing secrets. +- [ ] `account use` switches active account. +- [ ] `account current` displays active account. +- [ ] `account remove` deletes accounts and handles active account removal. +- [ ] Config is stored under `~/.avito-py/` by default. +- [ ] Config directory can be overridden with `MY_SDK_HOME`. +- [ ] Config directory can be overridden with `AVITO_PY_HOME`. +- [ ] `account add` supports ticket-compatible `--api-key` and `--endpoint` aliases. +- [ ] CLI output uses `avito/cli/ui.py` helpers. +- [ ] Existing SDK import and runtime behavior remain unchanged. +- [ ] Basic tests cover config/account storage logic. +- [ ] Basic CLI tests cover help output and account command flow. +- [ ] README contains CLI usage examples. From 91d80168c702664dac8d531f4177254415aecf45 Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sun, 10 May 2026 11:55:13 +0300 Subject: [PATCH 04/38] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BD=D0=B0=D0=B4=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.md | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 137 insertions(+), 12 deletions(-) diff --git a/todo.md b/todo.md index 9f462ef..a6c6881 100644 --- a/todo.md +++ b/todo.md @@ -28,6 +28,7 @@ avito/ app.py accounts.py config.py + errors.py ui.py ``` @@ -38,7 +39,7 @@ Register the console command as `avito` unless product naming requires another c avito = "avito.cli.app:app" ``` -Also consider registering `avito-cli` as a compatibility alias if product naming wants a CLI-specific command: +Do not register `avito-cli` unless product naming explicitly requires a CLI-specific compatibility alias: ```toml [tool.poetry.scripts] @@ -46,7 +47,56 @@ avito = "avito.cli.app:app" avito-cli = "avito.cli.app:app" ``` -Keep `avito/__main__.py` compatible. A later implementation can either leave it as the existing smoke check or route `python -m avito` to the Typer app if that is desired. +Route `python -m avito` to the Typer app so the module entry point and console script expose the same CLI behavior. + +Add version commands: + +```bash +avito --version +avito version +``` + +The version output should include the installed SDK version. Build commit and Avito API compatibility can be omitted until the project has those values. + +## CLI Contract + +CLI commands are public product surface. They must follow `.ai/cli-guidelines.md`: + +- default output is human-readable; +- machine-readable output is available through `--json`; +- quiet automation output is available through `--quiet`; +- prompts are disabled by `--no-input`; +- command results go to stdout; +- errors, warnings, progress, and deprecation notices go to stderr; +- no command exposes secrets in normal, JSON, verbose, debug, or error output; +- color must not be the only source of meaning; +- `NO_COLOR=1` and `--no-color` disable colored output. + +Supported global flags: + +```text +--json +--quiet +--no-input +--no-color +--verbose +--debug +--version +``` + +Baseline exit codes: + +```text +0 success +1 general error +2 invalid usage +3 not found +5 authentication/config required +6 conflict +7 validation failed +``` + +Every CLI error should include a stable error code such as `CONFIG_INVALID`, `ACCOUNT_NOT_FOUND`, `ACCOUNT_EXISTS`, `AUTH_REQUIRED`, or `VALIDATION_FAILED`. ## Data Model @@ -72,6 +122,14 @@ MY_SDK_HOME=/custom/path avito account list Document both variables, but make clear that `MY_SDK_HOME` exists for ticket compatibility and `AVITO_PY_HOME` is the Avito-specific name. +File-system requirements: + +- create the CLI home directory lazily with `0700` permissions; +- write `accounts.json` and `config.json` with `0600` permissions; +- save JSON atomically through a temporary file and replace; +- never create files or directories on import; +- report permission and malformed JSON errors as CLI errors without stack traces by default. + Suggested stored account fields: - `name: str` @@ -85,6 +143,18 @@ The generic ticket example uses `--api-key`. Avito uses OAuth `client_id` and `c Do not print full secret values. Mask values such as `client_secret`, `api_key`, refresh tokens, and API-like tokens in CLI output. +Human output uses stable tables or grouped key-value output. JSON output must use stable top-level object shapes, for example: + +```json +{"accounts": [{"name": "dev", "base_url": "https://api.avito.ru", "active": true}]} +``` + +```json +{"account": {"name": "dev", "base_url": "https://api.avito.ru", "active": true}} +``` + +Secret fields must be omitted or masked in JSON output; do not emit raw stored credentials. + ## Implementation Plan 1. Add Typer dependency @@ -97,6 +167,7 @@ Do not print full secret values. Mask values such as `client_secret`, `api_key`, - Create `avito/cli/app.py` with the root Typer app. - Create `avito/cli/accounts.py` with an `account` subcommand app. - Create `avito/cli/config.py` for CLI home resolution and JSON persistence. + - Create `avito/cli/errors.py` for CLI error types, stable error codes, and exit-code mapping. - Create `avito/cli/ui.py` for shared output helpers. 3. Add config/home resolver @@ -106,6 +177,8 @@ Do not print full secret values. Mask values such as `client_secret`, `api_key`, - Respect `AVITO_PY_HOME` as a project-specific alias with higher precedence. - Keep this logic independent from Typer so tests can call it directly. - Create directories lazily when saving data, not on import. + - Create directories with `0700` and config files with `0600`. + - Persist JSON atomically. 4. Add account storage layer - Use frozen dataclasses for CLI account records where practical. @@ -114,6 +187,8 @@ Do not print full secret values. Mask values such as `client_secret`, `api_key`, - `accounts.json` contains named account records. - `config.json` contains the active account name. - Validate duplicate account names, missing active accounts, and malformed JSON. + - `account add` must fail with a conflict error when the account already exists. + - Do not add overwrite behavior unless a separate `account update` command is introduced. - Keep messages and exceptions consistent with repository conventions. 5. Add account commands @@ -121,6 +196,7 @@ Do not print full secret values. Mask values such as `client_secret`, `api_key`, - Accept canonical flags: `--name`, `--client-id`, `--client-secret`, `--base-url`, and optional `--user-id`. - Accept ticket-compatible aliases: `--api-key` for `--client-secret` and `--endpoint` for `--base-url`. - Prompt interactively for missing required fields. + - Never prompt when `--no-input` is supplied or stdin is not a TTY; fail with a validation error instead. - Default base URL to `https://api.avito.ru`. - `avito account list` - Display all accounts with base URL and active marker. @@ -129,9 +205,12 @@ Do not print full secret values. Mask values such as `client_secret`, `api_key`, - Set the active account in `config.json`. - `avito account current` - Display the currently active account. - - `avito account remove ` + - `avito account delete ` - Confirm removal unless a `--yes` flag is supplied. - If removing the active account, clear the active account. + - `avito account remove ` + - Keep as a ticket-compatible alias for `account delete` only if the ticket requires the exact verb. + - If implemented, document it as an alias and keep all behavior delegated to `account delete`. 6. Add SDK client factory helper for future CLI commands - Add a CLI-only helper that converts the active account record to `AvitoSettings`. @@ -145,12 +224,22 @@ Do not print full secret values. Mask values such as `client_secret`, `api_key`, - `error(message: str) -> None` - `warning(message: str) -> None` - `info(message: str) -> None` - - `print_table(rows: list[dict[str, object]]) -> None` - - `confirm(message: str) -> bool` + - `print_table(rows: Sequence[Mapping[str, object]]) -> None` + - `print_json(payload: Mapping[str, object]) -> None` + - `confirm(message: str, *, expected: str | None = None) -> bool` + - `mask_secret(value: str | None) -> str | None` - Prefer `typer.echo`, `typer.secho`, `typer.confirm`, and `typer.prompt`. - Avoid raw `print()` in command modules. + - Use stdout for command results and stderr for errors/warnings. + - Support `--no-color` and `NO_COLOR=1`. + +8. Add CLI error handling + - Map CLI-specific errors to documented exit codes. + - Hide stack traces unless `--debug` is enabled. + - Emit stable error codes in human and JSON output. + - Keep user-facing SDK/CLI error text in one language; prefer Russian to match repository conventions. -8. Register console command +9. Register console command - Add Poetry script entry: ```toml @@ -158,7 +247,7 @@ Do not print full secret values. Mask values such as `client_secret`, `api_key`, avito = "avito.cli.app:app" ``` - - Optionally add the alias if desired: + - Do not add the alias unless explicitly required: ```toml avito-cli = "avito.cli.app:app" @@ -169,9 +258,11 @@ Do not print full secret values. Mask values such as `client_secret`, `api_key`, ```bash poetry run avito --help poetry run avito account --help + poetry run avito --version + poetry run python -m avito --help ``` -9. Add tests +10. Add tests - Add focused tests under `tests/cli/`. - Cover config home resolution: - default home; @@ -183,16 +274,25 @@ Do not print full secret values. Mask values such as `client_secret`, `api_key`, - set/get active account; - remove inactive account; - remove active account clears active config; + - duplicate account names fail with conflict; + - malformed JSON fails with a CLI configuration error; + - atomic save does not leave partial config on write failure where practical; - sensitive value masking does not reveal full secrets. - Cover CLI command surface with Typer's `CliRunner`: - `avito --help`; - `avito account --help`; + - `avito --version`; + - `python -m avito --help` behavior through the module entry point; - non-interactive `account add --name dev --client-id ... --client-secret ... --endpoint ...`; - non-interactive ticket-compatible `account add --name dev --client-id ... --api-key ... --endpoint ...`; - - `account use`, `account current`, `account list`, and `account remove --yes`. + - `account add --no-input` fails instead of prompting when required values are missing; + - `account use`, `account current`, `account list`, and `account delete --yes`; + - `account remove --yes` only if the compatibility alias is implemented; + - `--json` output is valid JSON and contains no raw secrets; + - `--quiet` suppresses non-essential success output. - Prefer direct tests of the config/account storage layer for persistence edge cases. -10. Update README +11. Update documentation - Add a short CLI section: ```bash @@ -200,18 +300,25 @@ Do not print full secret values. Mask values such as `client_secret`, `api_key`, avito account add --name dev --client-id ... --api-key ... --endpoint https://api.avito.ru avito account use dev avito account list + avito account current --json + avito account delete dev --yes ``` - Document `MY_SDK_HOME` and `AVITO_PY_HOME`. - Mention that secrets are stored locally and masked in output. + - Document `--json`, `--quiet`, `--no-input`, `--no-color`, `--version`, and public exit codes. + - Add or update a docs how-to page if CLI is considered part of public user workflow, not just README examples. -11. Verification +12. Verification - Minimum for this non-API-surface change: ```bash poetry run pytest tests/cli poetry run mypy avito poetry run ruff check . + poetry run python scripts/lint_python_guidelines.py + poetry run python scripts/lint_architecture.py + poetry build ``` - Before completing the branch, run: @@ -226,18 +333,36 @@ Do not print full secret values. Mask values such as `client_secret`, `api_key`, - [ ] `avito/cli/` package exists and is isolated from SDK core. - [ ] Console command is registered in `pyproject.toml`. - [ ] `avito --help` works. +- [ ] `avito --version` works. +- [ ] `avito version` works. +- [ ] `python -m avito --help` exposes the same CLI entry point. - [ ] `avito account --help` shows account commands. - [ ] `account add` stores account data. +- [ ] `account add --no-input` never prompts and fails on missing required values. +- [ ] `account add` rejects duplicate names with a conflict error. - [ ] `account list` lists accounts without exposing secrets. +- [ ] `account list --json` emits valid JSON without exposing secrets. - [ ] `account use` switches active account. - [ ] `account current` displays active account. -- [ ] `account remove` deletes accounts and handles active account removal. +- [ ] `account current --json` emits valid JSON without exposing secrets. +- [ ] `account delete` deletes accounts and handles active account removal. +- [ ] `account remove` is either omitted or implemented only as a documented alias for `account delete`. - [ ] Config is stored under `~/.avito-py/` by default. - [ ] Config directory can be overridden with `MY_SDK_HOME`. - [ ] Config directory can be overridden with `AVITO_PY_HOME`. +- [ ] CLI home directory is created lazily with `0700` permissions. +- [ ] `accounts.json` and `config.json` are written with `0600` permissions. +- [ ] Config writes are atomic. - [ ] `account add` supports ticket-compatible `--api-key` and `--endpoint` aliases. - [ ] CLI output uses `avito/cli/ui.py` helpers. +- [ ] CLI errors use stable error codes and documented exit codes. +- [ ] CLI results use stdout; errors and warnings use stderr. +- [ ] `--quiet`, `--json`, `--no-input`, `--no-color`, `--verbose`, and `--debug` behavior is documented. - [ ] Existing SDK import and runtime behavior remain unchanged. - [ ] Basic tests cover config/account storage logic. - [ ] Basic CLI tests cover help output and account command flow. +- [ ] Tests cover JSON output, quiet output, no-input behavior, duplicate names, malformed JSON, and secret masking. - [ ] README contains CLI usage examples. +- [ ] Public docs mention CLI usage if the CLI is part of public workflow. +- [ ] Minimum verification commands pass. +- [ ] `make check` passes before completing the branch. From c2fb67d528500103bd54cf281903778c7b84c3b0 Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sun, 10 May 2026 11:58:05 +0300 Subject: [PATCH 05/38] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BD=D0=B0=D0=B4=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.md | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/todo.md b/todo.md index a6c6881..762784d 100644 --- a/todo.md +++ b/todo.md @@ -91,12 +91,34 @@ Baseline exit codes: 1 general error 2 invalid usage 3 not found +4 permission denied 5 authentication/config required 6 conflict 7 validation failed +8 external dependency unavailable ``` Every CLI error should include a stable error code such as `CONFIG_INVALID`, `ACCOUNT_NOT_FOUND`, `ACCOUNT_EXISTS`, `AUTH_REQUIRED`, or `VALIDATION_FAILED`. +User-facing CLI error messages must be written in Russian only, matching the SDK styleguide. Do not mix Russian and English in one error message. Stable error codes remain uppercase English identifiers. + +Global flags must have deterministic precedence and conflict behavior: + +- `--json` makes successful command output and CLI errors machine-readable JSON. +- `--quiet` suppresses non-essential success output; when combined with `--json`, JSON output remains the contract for commands that return data. +- `--debug` may include diagnostic details for human errors, but must not leak secrets; in `--json` mode diagnostics must be placed in stable JSON fields. +- `--verbose` is user-facing detail and must not override `--quiet`. +- `--no-color` and `NO_COLOR=1` disable color everywhere. +- Global options should work consistently at the root command level and for subcommands through shared CLI context. + +The root CLI must support help in both common forms: + +```bash +avito --help +avito account --help +avito help account +``` + +If Typer's built-in help behavior makes `avito help account` impractical, document the explicit exception and cover `--help` behavior instead. ## Data Model @@ -178,7 +200,13 @@ Secret fields must be omitted or masked in JSON output; do not emit raw stored c - Keep this logic independent from Typer so tests can call it directly. - Create directories lazily when saving data, not on import. - Create directories with `0700` and config files with `0600`. - - Persist JSON atomically. + - Persist JSON atomically: + - create the temporary file in the same directory as the target file; + - ensure the temporary file is not world-readable; + - write and flush the full JSON document before replacement; + - replace the target with `os.replace`; + - remove leftover temporary files on write failures where practical. + - Map permission failures to CLI errors with exit code `4` and stable error code `PERMISSION_DENIED`. 4. Add account storage layer - Use frozen dataclasses for CLI account records where practical. @@ -232,12 +260,15 @@ Secret fields must be omitted or masked in JSON output; do not emit raw stored c - Avoid raw `print()` in command modules. - Use stdout for command results and stderr for errors/warnings. - Support `--no-color` and `NO_COLOR=1`. + - Centralize global CLI state in a typed context object so command modules do not parse flags independently. 8. Add CLI error handling - Map CLI-specific errors to documented exit codes. - Hide stack traces unless `--debug` is enabled. - Emit stable error codes in human and JSON output. - - Keep user-facing SDK/CLI error text in one language; prefer Russian to match repository conventions. + - Keep user-facing SDK/CLI error text in Russian only; do not mix languages. + - Ensure `--json` errors are valid JSON and still go to stderr. + - Ensure `--debug` never exposes `client_secret`, `api_key`, refresh tokens, access tokens, authorization headers, or token-like values. 9. Register console command - Add Poetry script entry: @@ -281,6 +312,7 @@ Secret fields must be omitted or masked in JSON output; do not emit raw stored c - Cover CLI command surface with Typer's `CliRunner`: - `avito --help`; - `avito account --help`; + - `avito help account` or the documented exception if this form is intentionally unsupported; - `avito --version`; - `python -m avito --help` behavior through the module entry point; - non-interactive `account add --name dev --client-id ... --client-secret ... --endpoint ...`; @@ -290,6 +322,10 @@ Secret fields must be omitted or masked in JSON output; do not emit raw stored c - `account remove --yes` only if the compatibility alias is implemented; - `--json` output is valid JSON and contains no raw secrets; - `--quiet` suppresses non-essential success output. + - `--json` errors are valid JSON on stderr; + - `--debug` does not reveal secrets; + - `--verbose` does not override `--quiet`; + - `--no-color` and `NO_COLOR=1` disable color output. - Prefer direct tests of the config/account storage layer for persistence edge cases. 11. Update documentation @@ -305,7 +341,7 @@ Secret fields must be omitted or masked in JSON output; do not emit raw stored c ``` - Document `MY_SDK_HOME` and `AVITO_PY_HOME`. - - Mention that secrets are stored locally and masked in output. + - Mention that secrets are stored locally in plaintext JSON files protected with `0600` permissions and masked in output. - Document `--json`, `--quiet`, `--no-input`, `--no-color`, `--version`, and public exit codes. - Add or update a docs how-to page if CLI is considered part of public user workflow, not just README examples. From 928a93f0b9612ef9caffd51dad31a5346e1d0e85 Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sun, 10 May 2026 12:05:51 +0300 Subject: [PATCH 06/38] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BD=D0=B0=D0=B4=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.md | 1047 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 737 insertions(+), 310 deletions(-) diff --git a/todo.md b/todo.md index 762784d..eb59651 100644 --- a/todo.md +++ b/todo.md @@ -1,90 +1,185 @@ # Task Plan: Add CLI Mode to avito-py -## Project Context +## Goal + +Build a convenient, stable, scriptable CLI for `avito-py` that covers every supported SDK domain method with maximum reuse of the existing SDK surface. + +The CLI must not become a second SDK implementation. It must call `AvitoClient` factories and public domain methods, reuse existing models and serialization, and keep all HTTP/auth/retry/mapping behavior inside the current SDK layers. + +Target outcome: + +- `avito` console command and `python -m avito` expose the same CLI. +- Account/profile management works without network calls. +- Every sync Swagger-bound public SDK method has a discoverable CLI route. +- Every supported non-Swagger helper workflow has either a CLI route or a documented intentional exclusion. +- CLI coverage is checked automatically against Swagger binding discovery. +- Human output is useful by default; JSON output is stable for automation. +- Secrets are never printed in normal, JSON, verbose, debug, or error output. +- Implementation is delivered in small stages with tests and verification gates after each stage. + +## Source Rules + +Normative documents: + +- `.ai/STYLEGUIDE.md` +- `.ai/cli-guidelines.md` +- `docs/site/explanations/domain-architecture-v2.md` +- `docs/site/explanations/swagger-binding-subsystem.md` + +Repository contracts: - Package name: `avito-py`. - Import package: `avito`. -- Current public facade: `avito.client.AvitoClient`. -- Current config contract: `avito.config.AvitoSettings`. -- Current auth config contract: `avito.auth.settings.AuthSettings`. -- Packaging: Poetry-style `[tool.poetry]` in `pyproject.toml`; no console script is currently registered. -- Existing module entry point: `avito/__main__.py` is only a smoke check that creates `AvitoClient`. -- Architecture rule: CLI code must stay outside SDK core/domain/transport/auth layers. -- Styleguide constraints: - - do not return raw `dict` or `Any` from public SDK methods; - - do not mix CLI, transport, auth, parsing, or domain logic; - - error messages in SDK code are Russian only; - - avoid dead code and unused aliases; - - keep core SDK free of Typer imports. - -## Proposed CLI Shape - -Use `avito/cli/` instead of the ticket's generic `sdk/cli/`: +- Public facade: `avito.client.AvitoClient`. +- Sync domain methods are the first CLI coverage target. +- Swagger/OpenAPI specs in `docs/avito/api/` are the API contract source. +- Swagger bindings discovered by `avito.core.swagger_discovery.discover_swagger_bindings()` are the canonical SDK coverage source. + +Important style constraints: + +- CLI code belongs under `avito/cli/`; do not put CLI behavior into core/domain/transport/auth layers. +- Keep core SDK free of Typer imports. +- Do not duplicate transport, auth, retry, request mapping, response mapping, pagination, or validation logic in CLI code. +- Do not return raw `dict` or `Any` from public SDK methods. +- Do not add public Avito API methods without `@swagger_operation(...)`. +- Error messages in SDK and CLI user-facing text are Russian only. Stable error codes remain uppercase English identifiers. +- Avoid dead code, unused aliases, and dynamic method injection. + +## CLI Architecture + +Use a small hand-written CLI shell plus generated/discovered command metadata. ```text avito/ cli/ __init__.py - app.py - accounts.py - config.py - errors.py - ui.py + app.py # root Typer app and global context + accounts.py # local account/profile commands + client.py # CLI-only AvitoClient construction + commands.py # generic invocation engine for SDK methods + config.py # CLI home, JSON persistence, account store + coverage.py # CLI coverage report and linter helpers + errors.py # CLI errors, exit-code mapping, secret sanitization + help.py # optional help command compatibility + registry.py # command registry built from SDK metadata + schemas.py # CLI input coercion from signatures/type hints + serialization.py # model/pagination result serialization + ui.py # stdout/stderr, table/json/plain output ``` -Register the console command as `avito` unless product naming requires another command: +Do not add domain-specific CLI modules for every API package unless a command needs custom UX. The default path must be metadata-driven to avoid hand-copying 204 operations. + +Register only the canonical command unless product naming explicitly requires an alias: ```toml [tool.poetry.scripts] avito = "avito.cli.app:app" ``` -Do not register `avito-cli` unless product naming explicitly requires a CLI-specific compatibility alias: +Route `python -m avito` to the same Typer app. -```toml -[tool.poetry.scripts] -avito = "avito.cli.app:app" -avito-cli = "avito.cli.app:app" +## Command Model + +Use the `.ai/cli-guidelines.md` grammar: + +```text +avito [primary arguments] [flags] ``` -Route `python -m avito` to the Typer app so the module entry point and console script expose the same CLI behavior. +Resource names should be derived from SDK factory names with kebab-case: + +- `account` +- `account-hierarchy` +- `ad` +- `ad-stats` +- `autoload-profile` +- `chat` +- `promotion-order` +- `target-action-pricing` +- `delivery-order` +- `realty-listing` + +Actions should be derived from public SDK method names with kebab-case: -Add version commands: +- `get-self` from `get_self` +- `list-services` from `list_services` +- `create-order` from `create_order` + +Default generated command shape: ```bash -avito --version -avito version +avito [factory args] [method args] ``` -The version output should include the installed SDK version. Build commit and Avito API compatibility can be omitted until the project has those values. +Examples: + +```bash +avito account get-self +avito account get-balance --user-id 123 +avito ad get --item-id 456 --user-id 123 +avito ad-stats get-item-stats --user-id 123 --item-ids 456,789 --date-from 2026-05-01 +avito promotion-order list-services --item-id 456 --json +``` -## CLI Contract +Rules: -CLI commands are public product surface. They must follow `.ai/cli-guidelines.md`: +- Factory arguments and method arguments become named flags by default. +- Positional arguments are allowed only for obvious single primary identifiers after explicit design review. +- Same SDK concept must use the same CLI flag everywhere: `--user-id`, `--item-id`, `--order-id`, `--chat-id`, `--date-from`, `--date-to`, `--limit`, `--offset`. +- `resource_id` must never appear. +- Generated commands must preserve one obvious path per operation. Compatibility aliases must delegate to the canonical command and be documented as aliases. -- default output is human-readable; -- machine-readable output is available through `--json`; -- quiet automation output is available through `--quiet`; -- prompts are disabled by `--no-input`; -- command results go to stdout; -- errors, warnings, progress, and deprecation notices go to stderr; -- no command exposes secrets in normal, JSON, verbose, debug, or error output; -- color must not be the only source of meaning; -- `NO_COLOR=1` and `--no-color` disable colored output. +## Global Flags -Supported global flags: +Supported from the root command and all subcommands through one typed CLI context: ```text +-h, --help +--version +--profile +--config --json +--plain +--table +--wide --quiet --no-input --no-color --verbose --debug ---version +--timeout +``` + +Write/destructive commands additionally support: + +```text +--dry-run +--yes +--confirm ``` -Baseline exit codes: +Precedence: + +1. CLI flags +2. Environment variables +3. Project config +4. User config +5. System config +6. Built-in defaults + +Initial implementation may support only user config, but the precedence contract must be documented and the config resolver must leave room for project/system config without breaking users. + +Flag behavior: + +- `--json` makes success output and CLI errors machine-readable JSON. +- `--quiet` suppresses non-essential success output; when combined with `--json`, commands returning data still emit their JSON result. +- `--plain`, `--table`, and `--wide` are mutually exclusive with `--json`; invalid combinations fail with exit code `2`. +- `--verbose` is user-facing extra detail and never overrides `--quiet`. +- `--debug` may include diagnostic fields but must never leak secrets. +- `--no-color` and `NO_COLOR=1` disable color everywhere. +- Commands must not prompt when `--no-input` is set or stdin is not a TTY. + +## Exit Codes ```text 0 success @@ -98,31 +193,22 @@ Baseline exit codes: 8 external dependency unavailable ``` -Every CLI error should include a stable error code such as `CONFIG_INVALID`, `ACCOUNT_NOT_FOUND`, `ACCOUNT_EXISTS`, `AUTH_REQUIRED`, or `VALIDATION_FAILED`. -User-facing CLI error messages must be written in Russian only, matching the SDK styleguide. Do not mix Russian and English in one error message. Stable error codes remain uppercase English identifiers. +Every CLI error includes a stable code such as: -Global flags must have deterministic precedence and conflict behavior: - -- `--json` makes successful command output and CLI errors machine-readable JSON. -- `--quiet` suppresses non-essential success output; when combined with `--json`, JSON output remains the contract for commands that return data. -- `--debug` may include diagnostic details for human errors, but must not leak secrets; in `--json` mode diagnostics must be placed in stable JSON fields. -- `--verbose` is user-facing detail and must not override `--quiet`. -- `--no-color` and `NO_COLOR=1` disable color everywhere. -- Global options should work consistently at the root command level and for subcommands through shared CLI context. - -The root CLI must support help in both common forms: - -```bash -avito --help -avito account --help -avito help account -``` +- `CONFIG_INVALID` +- `ACCOUNT_NOT_FOUND` +- `ACCOUNT_EXISTS` +- `AUTH_REQUIRED` +- `PERMISSION_DENIED` +- `VALIDATION_FAILED` +- `COMMAND_UNSUPPORTED` +- `SDK_METHOD_FAILED` -If Typer's built-in help behavior makes `avito help account` impractical, document the explicit exception and cover `--help` behavior instead. +Errors and warnings go to stderr. Command results go to stdout. JSON errors are valid JSON on stderr. -## Data Model +## Account and Config Model -Persist CLI-local account records under an Avito-specific home directory: +Persist CLI-local data under: ```text ~/.avito-py/ @@ -130,275 +216,616 @@ Persist CLI-local account records under an Avito-specific home directory: accounts.json ``` -Support override: - -```bash -MY_SDK_HOME=/custom/path avito account list -``` - -`MY_SDK_HOME` is required because the ticket names it explicitly. Also support `AVITO_PY_HOME` as the project-specific alias, with this precedence: +Home override precedence: 1. `AVITO_PY_HOME` 2. `MY_SDK_HOME` 3. `Path.home() / ".avito-py"` -Document both variables, but make clear that `MY_SDK_HOME` exists for ticket compatibility and `AVITO_PY_HOME` is the Avito-specific name. +`MY_SDK_HOME` is ticket compatibility. `AVITO_PY_HOME` is the project-specific name. File-system requirements: -- create the CLI home directory lazily with `0700` permissions; -- write `accounts.json` and `config.json` with `0600` permissions; -- save JSON atomically through a temporary file and replace; -- never create files or directories on import; -- report permission and malformed JSON errors as CLI errors without stack traces by default. +- Create the CLI home directory lazily with `0700` permissions. +- Write `accounts.json` and `config.json` with `0600` permissions. +- Save JSON atomically through a temporary file in the same directory and `os.replace`. +- Never create files or directories on import. +- Map permission failures to exit code `4` with `PERMISSION_DENIED`. +- Map malformed JSON to exit code `7` or `5` depending on whether the command can continue without config. -Suggested stored account fields: +Stored account fields: - `name: str` - `client_id: str` - `client_secret: str` -- `base_url: str` stored internally, exposed in CLI as both `--base-url` and ticket-compatible `--endpoint` +- `base_url: str` - `user_id: int | None` -- optional OAuth fields already supported by `AuthSettings`: `scope`, `refresh_token`, `token_url`, `alternate_token_url`, `autoteka_token_url`, `autoteka_client_id`, `autoteka_client_secret`, `autoteka_scope` +- OAuth fields already supported by `AuthSettings`: `scope`, `refresh_token`, `token_url`, `alternate_token_url`, `autoteka_token_url`, `autoteka_client_id`, `autoteka_client_secret`, `autoteka_scope` + +Canonical flags: + +- `--client-id` +- `--client-secret` +- `--base-url` +- `--user-id` + +Ticket-compatible aliases: + +- `--api-key` as an alias for `--client-secret` +- `--endpoint` as an alias for `--base-url` + +Secrets must be omitted or masked in every output format, including JSON and debug diagnostics. + +## SDK Reuse Strategy -The generic ticket example uses `--api-key`. Avito uses OAuth `client_id` and `client_secret`, so the canonical Avito flags should be `--client-id` and `--client-secret`. To satisfy the ticket's CLI shape without weakening the SDK contract, support `--api-key` as an alias for `--client-secret` and still require `--client-id` unless a future Avito auth mode removes that requirement. +The CLI invokes SDK methods through a generic pipeline: -Do not print full secret values. Mask values such as `client_secret`, `api_key`, refresh tokens, and API-like tokens in CLI output. +1. Resolve profile/account and build `AvitoSettings`. +2. Create `AvitoClient(settings)` in a context manager. +3. Resolve the CLI resource to an `AvitoClient` factory. +4. Coerce CLI strings into the factory arguments and method arguments using public signatures/type hints. +5. Call the SDK factory. +6. Call the public domain method. +7. Serialize the SDK return value through existing `model_dump()` / `to_dict()` / pagination materialization helpers. +8. Render as table, grouped text, plain value, or JSON. -Human output uses stable tables or grouped key-value output. JSON output must use stable top-level object shapes, for example: +Do not use `OperationSpec` directly from CLI commands. Operation specs remain internal SDK metadata. + +The generic invocation engine must support: + +- primitive values: `str`, `int`, `float`, `bool` +- `date` and `datetime` strings with validation +- enums by value/name with clear validation errors +- optional values +- list values from repeated flags or comma-separated values where documented +- public dataclass input models only when they are already public SDK input models +- `PaginatedList[T]` with explicit materialization limits or streaming-safe iteration +- file inputs only for methods whose public signature already accepts file/path-like public inputs + +If a method cannot be safely exposed by the generic engine, add it to a typed exception list with a reason and a tracked follow-up. The acceptance target for final completion is zero unsupported sync Swagger-bound methods unless an operation is intentionally not usable from CLI and documented. + +## Registry and Coverage + +Build a CLI registry from existing SDK metadata: + +- `discover_swagger_bindings(registry=SwaggerRegistry.load(...))` +- `binding.factory` +- `binding.factory_args` +- `binding.method_name` +- `binding.method_args` +- public Python signatures and type hints + +Coverage invariant: + +```text +each sync discovered Swagger binding -> exactly one canonical CLI command +each canonical API CLI command -> exactly one sync discovered Swagger binding +each supported public non-Swagger helper -> CLI command or documented exclusion +``` + +Compatibility aliases are allowed but must not count as separate canonical coverage. + +Non-Swagger public helpers are not part of the Swagger one-to-one invariant, but they are part of the user-facing SDK. Track them separately: + +- `AvitoClient` summary/workflow helpers such as account health, chat/order/review/promotion summaries, and capability discovery; +- public domain helper methods without Swagger bindings, if any exist; +- local CLI-only workflows such as `account`, `config`, `status`, `doctor`, and `completion`. + +The coverage report must show separate counts: + +- `api_bound_commands` +- `api_bound_missing_commands` +- `helper_commands` +- `helper_exclusions` +- `local_cli_commands` + +Add a CLI coverage linter: + +```bash +poetry run python scripts/lint_cli_coverage.py +``` + +The linter must fail when: + +- a sync discovered Swagger binding has no canonical CLI command; +- a canonical API CLI command has no binding; +- two canonical CLI commands map to the same binding; +- a public supported helper has neither a command nor an explicit exclusion; +- a command exposes a forbidden `resource-id` flag; +- a command exposes a secret in an output schema; +- a command uses a non-kebab-case resource/action/flag name. + +Add `make cli-lint` and include it in `make check` after the CLI reaches full coverage. + +## Output Contract + +Default output: + +- Human-readable. +- Tables for collections. +- Grouped key-value output for one object. +- Concise success text for writes. +- Next-step hints only when helpful and not noisy. + +Machine output: + +- `--json` emits stable, undecorated JSON. +- Top-level objects are stable and named by resource/action. +- SDK models are serialized via their public serialization contract. +- Pagination output includes enough metadata when available. + +Examples: ```json {"accounts": [{"name": "dev", "base_url": "https://api.avito.ru", "active": true}]} ``` ```json -{"account": {"name": "dev", "base_url": "https://api.avito.ru", "active": true}} +{"result": {"operation": "account.get_self", "data": {"id": 123}}} +``` + +Avoid raw secrets in all output: + +- `client_secret` +- `api_key` +- `refresh_token` +- `access_token` +- `Authorization` +- token-like fields + +## Help and Completion + +Help requirements: + +```bash +avito --help +avito account --help +avito account get-self --help +avito help account +avito help account get-self +``` + +If Typer makes `avito help account` impractical, implement a small `help` command that delegates to the registry metadata instead of relying only on Typer internals. + +Help must include: + +- description in Russian; +- usage; +- at least one minimal example; +- one automation-friendly `--json --no-input` example where relevant; +- flags with stable names; +- related commands when useful. + +Completion commands: + +```bash +avito completion bash +avito completion zsh +avito completion fish +``` + +Completion can start with static command/flag completion and later add profile/account names. + +## Implementation Stages + +Each stage must be small enough to review independently and must end with tests and verification. + +### Stage 0: Baseline Audit + +Deliverables: + +- Record current sync Swagger binding count. +- Record current `AvitoClient` factory mapping count. +- Confirm which factory names exist in `AvitoClient` but not in bindings. +- Confirm whether every sync binding has `factory` metadata. +- Record public non-Swagger helper methods and decide command vs exclusion. + +Verification: + +```bash +poetry run python -c "from avito.core.swagger_discovery import discover_swagger_bindings; print(len(discover_swagger_bindings().canonical_map))" +poetry run pytest tests/core/test_swagger_linter.py tests/contracts/test_swagger_contracts.py ``` -Secret fields must be omitted or masked in JSON output; do not emit raw stored credentials. - -## Implementation Plan - -1. Add Typer dependency - - Add `typer` to `[tool.poetry.dependencies]`. - - Do not add `rich` unless Typer pulls it in or it is explicitly approved. - - Update the lock file through Poetry if dependency locking is part of the branch workflow. - -2. Add CLI package skeleton - - Create `avito/cli/__init__.py`. - - Create `avito/cli/app.py` with the root Typer app. - - Create `avito/cli/accounts.py` with an `account` subcommand app. - - Create `avito/cli/config.py` for CLI home resolution and JSON persistence. - - Create `avito/cli/errors.py` for CLI error types, stable error codes, and exit-code mapping. - - Create `avito/cli/ui.py` for shared output helpers. - -3. Add config/home resolver - - Implement `get_cli_home(env: Mapping[str, str] | None = None) -> Path`. - - Default to `Path.home() / ".avito-py"`. - - Respect `MY_SDK_HOME` for ticket compatibility. - - Respect `AVITO_PY_HOME` as a project-specific alias with higher precedence. - - Keep this logic independent from Typer so tests can call it directly. - - Create directories lazily when saving data, not on import. - - Create directories with `0700` and config files with `0600`. - - Persist JSON atomically: - - create the temporary file in the same directory as the target file; - - ensure the temporary file is not world-readable; - - write and flush the full JSON document before replacement; - - replace the target with `os.replace`; - - remove leftover temporary files on write failures where practical. - - Map permission failures to CLI errors with exit code `4` and stable error code `PERMISSION_DENIED`. - -4. Add account storage layer - - Use frozen dataclasses for CLI account records where practical. - - Implement load/save functions or an `AccountStore` class in `avito/cli/config.py`. - - Store `accounts.json` and `config.json` separately: - - `accounts.json` contains named account records. - - `config.json` contains the active account name. - - Validate duplicate account names, missing active accounts, and malformed JSON. - - `account add` must fail with a conflict error when the account already exists. - - Do not add overwrite behavior unless a separate `account update` command is introduced. - - Keep messages and exceptions consistent with repository conventions. - -5. Add account commands - - `avito account add` - - Accept canonical flags: `--name`, `--client-id`, `--client-secret`, `--base-url`, and optional `--user-id`. - - Accept ticket-compatible aliases: `--api-key` for `--client-secret` and `--endpoint` for `--base-url`. - - Prompt interactively for missing required fields. - - Never prompt when `--no-input` is supplied or stdin is not a TTY; fail with a validation error instead. - - Default base URL to `https://api.avito.ru`. - - `avito account list` - - Display all accounts with base URL and active marker. - - Mask sensitive fields if shown. - - `avito account use ` - - Set the active account in `config.json`. - - `avito account current` - - Display the currently active account. - - `avito account delete ` - - Confirm removal unless a `--yes` flag is supplied. - - If removing the active account, clear the active account. - - `avito account remove ` - - Keep as a ticket-compatible alias for `account delete` only if the ticket requires the exact verb. - - If implemented, document it as an alias and keep all behavior delegated to `account delete`. - -6. Add SDK client factory helper for future CLI commands - - Add a CLI-only helper that converts the active account record to `AvitoSettings`. - - This helper belongs in `avito/cli/config.py` or a future `avito/cli/client.py`, not in `avito/config.py`. - - Use `AvitoClient(settings)` from the CLI; do not duplicate SDK behavior. - - Any future CLI command that calls Avito API must use the active account automatically unless the command provides an explicit account override. - -7. Add UI helpers - - Implement: - - `success(message: str) -> None` - - `error(message: str) -> None` - - `warning(message: str) -> None` - - `info(message: str) -> None` - - `print_table(rows: Sequence[Mapping[str, object]]) -> None` - - `print_json(payload: Mapping[str, object]) -> None` - - `confirm(message: str, *, expected: str | None = None) -> bool` - - `mask_secret(value: str | None) -> str | None` - - Prefer `typer.echo`, `typer.secho`, `typer.confirm`, and `typer.prompt`. - - Avoid raw `print()` in command modules. - - Use stdout for command results and stderr for errors/warnings. - - Support `--no-color` and `NO_COLOR=1`. - - Centralize global CLI state in a typed context object so command modules do not parse flags independently. - -8. Add CLI error handling - - Map CLI-specific errors to documented exit codes. - - Hide stack traces unless `--debug` is enabled. - - Emit stable error codes in human and JSON output. - - Keep user-facing SDK/CLI error text in Russian only; do not mix languages. - - Ensure `--json` errors are valid JSON and still go to stderr. - - Ensure `--debug` never exposes `client_secret`, `api_key`, refresh tokens, access tokens, authorization headers, or token-like values. - -9. Register console command - - Add Poetry script entry: - - ```toml - [tool.poetry.scripts] - avito = "avito.cli.app:app" - ``` - - - Do not add the alias unless explicitly required: - - ```toml - avito-cli = "avito.cli.app:app" - ``` - - - Verify: - - ```bash - poetry run avito --help - poetry run avito account --help - poetry run avito --version - poetry run python -m avito --help - ``` - -10. Add tests - - Add focused tests under `tests/cli/`. - - Cover config home resolution: - - default home; - - `MY_SDK_HOME` override; - - `AVITO_PY_HOME` override; - - precedence when both variables are present. - - Cover account storage: - - add and reload account; - - set/get active account; - - remove inactive account; - - remove active account clears active config; - - duplicate account names fail with conflict; - - malformed JSON fails with a CLI configuration error; - - atomic save does not leave partial config on write failure where practical; - - sensitive value masking does not reveal full secrets. - - Cover CLI command surface with Typer's `CliRunner`: - - `avito --help`; - - `avito account --help`; - - `avito help account` or the documented exception if this form is intentionally unsupported; - - `avito --version`; - - `python -m avito --help` behavior through the module entry point; - - non-interactive `account add --name dev --client-id ... --client-secret ... --endpoint ...`; - - non-interactive ticket-compatible `account add --name dev --client-id ... --api-key ... --endpoint ...`; - - `account add --no-input` fails instead of prompting when required values are missing; - - `account use`, `account current`, `account list`, and `account delete --yes`; - - `account remove --yes` only if the compatibility alias is implemented; - - `--json` output is valid JSON and contains no raw secrets; - - `--quiet` suppresses non-essential success output. - - `--json` errors are valid JSON on stderr; - - `--debug` does not reveal secrets; - - `--verbose` does not override `--quiet`; - - `--no-color` and `NO_COLOR=1` disable color output. - - Prefer direct tests of the config/account storage layer for persistence edge cases. - -11. Update documentation - - Add a short CLI section: - - ```bash - avito account add --name dev --client-id ... --client-secret ... - avito account add --name dev --client-id ... --api-key ... --endpoint https://api.avito.ru - avito account use dev - avito account list - avito account current --json - avito account delete dev --yes - ``` - - - Document `MY_SDK_HOME` and `AVITO_PY_HOME`. - - Mention that secrets are stored locally in plaintext JSON files protected with `0600` permissions and masked in output. - - Document `--json`, `--quiet`, `--no-input`, `--no-color`, `--version`, and public exit codes. - - Add or update a docs how-to page if CLI is considered part of public user workflow, not just README examples. - -12. Verification - - Minimum for this non-API-surface change: - - ```bash - poetry run pytest tests/cli - poetry run mypy avito - poetry run ruff check . - poetry run python scripts/lint_python_guidelines.py - poetry run python scripts/lint_architecture.py - poetry build - ``` - - - Before completing the branch, run: - - ```bash - make check - ``` +Exit criteria: + +- A short audit note is added to this plan or a linked implementation note. +- Any missing factory metadata is tracked before CLI generation starts. + +### Stage 1: CLI Dependency and Shell + +Deliverables: + +- Add `typer` dependency. +- Add `avito/cli/` package skeleton. +- Add root `avito` app with global context. +- Add `avito --help`, `avito --version`, `avito version`. +- Route `python -m avito` to the CLI. +- Register Poetry script. + +Tests: + +- `tests/cli/test_app.py` +- help output smoke tests; +- version command tests; +- global flag parsing tests. + +Verification: + +```bash +poetry run pytest tests/cli/test_app.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli +poetry build +``` + +### Stage 2: Errors, UI, and Safe Output + +Deliverables: + +- Add `CliContext`. +- Add `CliError` hierarchy and exit-code mapping. +- Add stdout/stderr output helpers. +- Add JSON/human error rendering. +- Add secret masking/sanitization. +- Add color handling for `--no-color` and `NO_COLOR=1`. + +Tests: + +- human errors go to stderr; +- JSON errors are valid JSON on stderr; +- `--debug` does not reveal secrets; +- `--quiet` suppresses non-essential success output; +- invalid flag combinations exit with code `2`. + +Verification: + +```bash +poetry run pytest tests/cli/test_errors.py tests/cli/test_ui.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli +``` + +### Stage 3: Account Store and Profile Commands + +Deliverables: + +- Implement CLI home resolver and atomic JSON persistence. +- Implement account dataclasses and `AccountStore`. +- Add account commands: + - `avito account add` + - `avito account list` + - `avito account use ` + - `avito account current` + - `avito account delete ` +- Add optional `account remove` only as a documented alias for `account delete`. +- Add CLI-only helper that converts the active account to `AvitoSettings`. + +Tests: + +- default home and environment override precedence; +- lazy directory creation; +- file permissions where the platform supports it; +- add/reload account; +- duplicate account conflict; +- active account set/get/clear; +- malformed JSON handling; +- no-input behavior; +- ticket aliases `--api-key` and `--endpoint`; +- JSON output contains no raw secrets. + +Verification: + +```bash +poetry run pytest tests/cli/test_config.py tests/cli/test_accounts.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli +``` + +### Stage 4: CLI Registry From SDK Metadata + +Deliverables: + +- Build `avito/cli/registry.py`. +- Convert sync discovered Swagger bindings into canonical resource/action commands. +- Preserve mapping to factory name, factory args, method name, method args, operation key, and domain. +- Register public non-Swagger helper commands in a separate helper registry. +- Add explicit alias support separate from canonical command records. +- Add registry JSON/debug report command: + - `avito cli coverage --json` + - or hidden internal command if public exposure is not desired. + +Tests: + +- registry includes all sync discovered bindings; +- registry accounts for public helper methods separately from API bindings; +- resource/action names are kebab-case; +- every command maps to exactly one binding; +- no duplicate canonical commands; +- aliases do not affect canonical coverage. + +Verification: + +```bash +poetry run pytest tests/cli/test_registry.py +poetry run python scripts/lint_cli_coverage.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py +``` + +### Stage 5: Generic Input Coercion and Invocation + +Deliverables: + +- Implement `avito/cli/schemas.py`. +- Implement `avito/cli/commands.py`. +- Coerce CLI flags from signatures/type hints. +- Build and call `AvitoClient` through active account/profile. +- Invoke public SDK factory and method. +- Map SDK exceptions to CLI errors. +- Add explicit unsupported-method registry only for cases with documented reasons. + +Tests: + +- coercion for primitives, bools, dates, datetimes, enums, optionals, lists; +- missing required values fail without prompt in `--no-input`; +- invalid values produce `VALIDATION_FAILED`; +- active profile is used by default; +- `--profile` overrides active profile; +- SDK `AuthenticationError`, `AuthorizationError`, `ValidationError`, `ConflictError`, and not-found equivalents map to documented exit codes. + +Verification: + +```bash +poetry run pytest tests/cli/test_schemas.py tests/cli/test_commands.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli +``` + +### Stage 6: Result Serialization and Pagination + +Deliverables: + +- Implement `avito/cli/serialization.py`. +- Serialize SDK models through `model_dump()` / `to_dict()`. +- Serialize dataclasses, enums, dates, datetimes, lists, and primitive values safely. +- Handle `PaginatedList[T]` with documented defaults. +- Add `--limit` or `--page-limit` only when needed to avoid accidentally materializing unbounded result sets. +- Render default tables for collections and grouped output for single models. + +Tests: + +- model serialization uses public model contract; +- paginated results do not fetch unbounded pages by default; +- JSON output is stable; +- tables have stable columns for repeated models; +- no secrets appear in serialized output. + +Verification: + +```bash +poetry run pytest tests/cli/test_serialization.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli +``` + +### Stage 7: First Vertical API Slice + +Deliverables: + +- Expose and test a small read-only vertical slice: + - `avito account get-self` + - `avito account get-balance` + - one paginated/list command if available. +- Use `SwaggerFakeTransport` or existing fake transport infrastructure. +- Do not make real network calls in tests. + +Tests: + +- command invokes the expected SDK method; +- request path/query/body match Swagger fake transport expectations; +- human output works; +- JSON output works; +- errors are mapped correctly. + +Verification: + +```bash +poetry run pytest tests/cli/test_account_api_commands.py +poetry run pytest tests/contracts/test_swagger_contracts.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli +``` + +### Stage 8: All-Domain Generated API Commands + +Deliverables: + +- Register generated commands for every sync Swagger-bound method. +- Add domain/resource help pages. +- Add generated examples where safe and meaningful. +- Add explicit command metadata for methods needing custom list/file/enum parsing. +- Eliminate or document every unsupported sync binding. + +Required domains: + +- `accounts` +- `ads` +- `autoteka` +- `cpa` +- `jobs` +- `messenger` +- `orders` +- `promotion` +- `ratings` +- `realty` +- `tariffs` + +Required helper workflows: + +- account health/business summary; +- chat summary; +- order summary; +- review summary; +- promotion summary; +- capability discovery. + +Tests: + +- one smoke invocation per domain with fake transport; +- one command metadata assertion per discovered sync binding; +- generic coverage test that fails on missing commands; +- helper command metadata or explicit exclusions are covered; +- no generated command exposes forbidden names or secret fields. + +Verification: + +```bash +poetry run pytest tests/cli/test_all_domains_metadata.py +poetry run pytest tests/cli/test_domain_smoke_commands.py +poetry run python scripts/lint_cli_coverage.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py +``` + +### Stage 9: Write Commands, Safety, and Dry Run + +Deliverables: + +- Classify write/destructive commands from HTTP method and/or SDK metadata. +- Require confirmation for destructive commands unless `--yes` or exact `--confirm` is supplied. +- Support `--dry-run` only when the SDK public method already supports `dry_run` or when the CLI can safely preview without changing SDK behavior. +- Do not fake dry-run for SDK methods that would still execute transport. +- Ensure write commands build the same SDK call in dry-run and apply modes where `dry_run` exists. + +Tests: + +- delete/reset-like commands require confirmation; +- `--no-input` fails instead of prompting; +- `--yes` and `--confirm` behave deterministically; +- dry-run methods do not call transport when SDK contract says they should not; +- non-dry-run write commands call transport exactly once. + +Verification: + +```bash +poetry run pytest tests/cli/test_write_safety.py +poetry run pytest tests/domains/promotion tests/domains/orders +poetry run mypy avito +poetry run ruff check avito/cli tests/cli +``` + +### Stage 10: Config, Status, Doctor, and Completion + +Deliverables: + +- Add explicit config commands: + - `avito config get` + - `avito config set` + - `avito config unset` + - `avito config list` + - `avito config list --show-source` +- Add `avito status` for profile/config/auth readiness without leaking secrets. +- Add `avito doctor` for local diagnostics. +- Add shell completion commands for bash, zsh, and fish. + +Tests: + +- config precedence and source display; +- status works without network where possible; +- doctor reports malformed config and permission issues; +- completion commands render scripts or clear instructions. + +Verification: + +```bash +poetry run pytest tests/cli/test_config_commands.py tests/cli/test_status_doctor.py tests/cli/test_completion.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli +``` + +### Stage 11: Documentation + +Deliverables: + +- README CLI quickstart. +- Docs how-to page for CLI account/profile setup. +- Docs reference for global flags, output formats, exit codes, config files, environment variables, and secret storage. +- Docs page explaining generated all-domain command grammar. +- Examples for human and JSON automation usage. +- Document that secrets are stored locally in plaintext JSON protected with `0600` permissions. + +Verification: + +```bash +poetry run mkdocs build --strict +make docs-check +``` + +### Stage 12: Final Gate + +Run the full gate before completing the branch: + +```bash +poetry run pytest tests/cli +poetry run pytest tests/core/test_swagger*.py tests/contracts/test_swagger_contracts.py +poetry run mypy avito +poetry run ruff check . +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py +poetry run python scripts/lint_cli_coverage.py +poetry build +make check +``` + +If generated docs, snippets, coverage pages, or reference output changed: + +```bash +make docs-strict +``` ## Acceptance Checklist -- [ ] Typer dependency added. -- [ ] `avito/cli/` package exists and is isolated from SDK core. -- [ ] Console command is registered in `pyproject.toml`. -- [ ] `avito --help` works. -- [ ] `avito --version` works. -- [ ] `avito version` works. -- [ ] `python -m avito --help` exposes the same CLI entry point. -- [ ] `avito account --help` shows account commands. -- [ ] `account add` stores account data. -- [ ] `account add --no-input` never prompts and fails on missing required values. -- [ ] `account add` rejects duplicate names with a conflict error. -- [ ] `account list` lists accounts without exposing secrets. -- [ ] `account list --json` emits valid JSON without exposing secrets. -- [ ] `account use` switches active account. -- [ ] `account current` displays active account. -- [ ] `account current --json` emits valid JSON without exposing secrets. -- [ ] `account delete` deletes accounts and handles active account removal. -- [ ] `account remove` is either omitted or implemented only as a documented alias for `account delete`. -- [ ] Config is stored under `~/.avito-py/` by default. -- [ ] Config directory can be overridden with `MY_SDK_HOME`. -- [ ] Config directory can be overridden with `AVITO_PY_HOME`. +- [ ] `typer` dependency added. +- [ ] `avito/cli/` exists and is isolated from SDK core/domain/transport/auth layers. +- [ ] Console command `avito` is registered in `pyproject.toml`. +- [ ] `python -m avito` exposes the same CLI. +- [ ] `avito --help`, `avito --version`, and `avito version` work. +- [ ] Global flags work consistently at root and subcommand levels. +- [ ] CLI home defaults to `~/.avito-py/`. +- [ ] `AVITO_PY_HOME` and `MY_SDK_HOME` override CLI home with documented precedence. - [ ] CLI home directory is created lazily with `0700` permissions. -- [ ] `accounts.json` and `config.json` are written with `0600` permissions. -- [ ] Config writes are atomic. -- [ ] `account add` supports ticket-compatible `--api-key` and `--endpoint` aliases. -- [ ] CLI output uses `avito/cli/ui.py` helpers. +- [ ] `accounts.json` and `config.json` are written atomically with `0600` permissions. +- [ ] Account commands add/list/use/current/delete accounts. +- [ ] `account remove` is omitted or implemented only as a documented alias for `account delete`. +- [ ] `account add` supports `--client-id`, `--client-secret`, `--base-url`, `--api-key`, and `--endpoint`. +- [ ] No CLI output leaks raw secrets. - [ ] CLI errors use stable error codes and documented exit codes. -- [ ] CLI results use stdout; errors and warnings use stderr. -- [ ] `--quiet`, `--json`, `--no-input`, `--no-color`, `--verbose`, and `--debug` behavior is documented. -- [ ] Existing SDK import and runtime behavior remain unchanged. -- [ ] Basic tests cover config/account storage logic. -- [ ] Basic CLI tests cover help output and account command flow. -- [ ] Tests cover JSON output, quiet output, no-input behavior, duplicate names, malformed JSON, and secret masking. -- [ ] README contains CLI usage examples. -- [ ] Public docs mention CLI usage if the CLI is part of public workflow. -- [ ] Minimum verification commands pass. -- [ ] `make check` passes before completing the branch. +- [ ] Results go to stdout; errors, warnings, progress, and debug diagnostics go to stderr. +- [ ] `--json` emits stable JSON for success and errors. +- [ ] `--quiet`, `--plain`, `--table`, `--wide`, `--no-input`, `--no-color`, `--verbose`, and `--debug` are documented and tested. +- [ ] CLI registry is built from SDK Swagger binding metadata. +- [ ] Every sync discovered Swagger binding has exactly one canonical CLI command. +- [ ] Every canonical API CLI command maps to exactly one sync discovered Swagger binding. +- [ ] Every supported public non-Swagger helper has a CLI command or documented exclusion. +- [ ] Compatibility aliases do not count as canonical coverage. +- [ ] Generated command names and flags are lowercase kebab-case. +- [ ] No command exposes `resource-id`. +- [ ] Generic invocation uses `AvitoClient` factories and public domain methods. +- [ ] CLI does not call `OperationSpec` or transport directly for API commands. +- [ ] Input coercion covers primitives, booleans, dates, datetimes, enums, optionals, and lists. +- [ ] Pagination behavior is bounded and documented. +- [ ] Destructive commands require confirmation unless `--yes` or `--confirm` is supplied. +- [ ] `--dry-run` is exposed only for SDK methods that safely support it. +- [ ] One smoke command per domain is tested through fake transport. +- [ ] CLI coverage linter exists and passes. +- [ ] README and docs include CLI usage, config, output, and exit-code contracts. +- [ ] Minimum stage verification commands pass during implementation. +- [ ] Final `make check` passes before completion. + +## Open Decisions + +- Whether to expose async SDK methods in CLI. Initial plan targets sync methods only because CLI processes are synchronous command executions and sync Swagger coverage is the canonical first product surface. +- Whether `avito cli coverage` should be public or hidden. The linter/report is required either way. +- Whether generated commands should materialize all pagination by default or require an explicit `--all`. Conservative default: bounded output with explicit opt-in for full materialization. +- Whether to add `avito-cli` as an alias. Conservative default: do not add it without a product requirement. From 08f40a0ee36684c11894d928b9b60e71b6ff9f37 Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sun, 10 May 2026 12:15:37 +0300 Subject: [PATCH 07/38] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BD=D0=B0=D0=B4=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.md | 689 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 506 insertions(+), 183 deletions(-) diff --git a/todo.md b/todo.md index eb59651..5871ee5 100644 --- a/todo.md +++ b/todo.md @@ -1,28 +1,80 @@ # Task Plan: Add CLI Mode to avito-py +## Developer Context and Execution Instructions + +Этот план рассчитан на разработчика, который впервые открыл задачу и должен довести CLI-режим до стабильного, покрытого тестами состояния без нарушения архитектуры SDK. + +Главная идея: CLI является тонкой оболочкой над публичным SDK. Он не знает, как устроены HTTP-запросы, авторизация, retry, Swagger operation specs, transport, response mapping и pagination internals. Для Avito API-команд CLI всегда идет через `AvitoClient` -> public factory -> public domain method -> public SDK model serialization. + +Как работать с планом: + +1. Перед любыми изменениями прочитать этот раздел, `Goal`, `Normative Rules`, `Current Baseline Findings`, `CLI Architecture`, `SDK Reuse Strategy`, `Registry and Coverage`. +2. Затем прочитать обязательные документы: + - `.ai/STYLEGUIDE.md` + - `.ai/cli-guidelines.md` + - `.ai/python-guidelines.md` + - `docs/site/explanations/domain-architecture-v2.md` + - `docs/site/explanations/swagger-binding-subsystem.md` +3. Выполнять этапы строго по порядку. Не начинать следующий stage, пока текущий stage не прошел свои tests, verification commands и stage checklist. +4. Делать маленькие изменения. Один stage должен быть отдельным reviewable increment: новая минимальная функциональность, тесты, проверки, обновленный checklist. +5. Если stage оказался слишком крупным, разделить его на подэтапы внутри того же stage, но не перепрыгивать к следующей архитектурной области. +6. После каждого stage оставлять репозиторий в рабочем состоянии: тесты stage проходят, mypy/ruff из verification проходят, нет временных обходов и мертвого кода. +7. При конфликте между удобством реализации и гайдами выбирать гайды. Если гайд мешает выполнить задачу, сначала зафиксировать архитектурное решение в плане или документации, а не обходить правило молча. + +Что обязательно проанализировать перед началом Stage 0: + +- Текущий `AvitoClient`: какие public factory methods существуют, какие являются helper/workflow methods, какие не должны становиться API-командами. +- Текущий Swagger discovery: количество sync bindings, наличие `factory`, `factory_args`, `method_args`, `operation_key`. +- Public domain methods: какие методы sync, какие async, какие legacy/deprecated, какие helper methods без Swagger binding. +- Текущие модели сериализации: где есть `model_dump()` / `to_dict()`, как устроены `PaginatedList`, enums, dates/datetimes. +- Текущие fake transport/testing helpers: что можно использовать в tests и что запрещено импортировать в production CLI. +- Текущий `Makefile` и scripts linters: куда должен встроиться `cli-lint`, какие команды уже входят в `make check`. +- Текущий `avito/__main__.py`: его smoke-поведение должно быть заменено на CLI handoff на Stage 1. + +Правила выполнения stage: + +- Каждый stage должен иметь production code только в нужных файлах, тесты для новой логики и прохождение verification. +- Каждый stage checklist заполняется только после фактической проверки, а не заранее. +- Если verification command не проходит по причине, не связанной с изменением stage, это фиксируется рядом с результатом stage с точной командой и ошибкой. +- Если нужно добавить exclusion, он должен содержать причину, область влияния и follow-up. Silent exclusions запрещены. +- Если появляется новый public command, он должен иметь kebab-case имя, стабильные flags, Russian help/error text, JSON behavior, secret masking и тесты. +- Если команда может изменить состояние или вызвать дорогую операцию, сначала классифицировать safety policy, затем добавлять `--dry-run`, `--yes`, `--confirm` только по правилам этого плана. + +Definition of done for the whole plan: + +- Все stage checklists выполнены. +- `avito` и `python -m avito` работают через один CLI app. +- Все sync Swagger-bound методы покрыты canonical CLI command или явным documented exclusion. +- Все supported helper workflows покрыты command или documented exclusion. +- Coverage linter проходит и включен в `make check`. +- CLI не дублирует SDK contracts и не обходит public `AvitoClient` surface. +- Секреты не появляются ни в одном output mode. +- Финальный gate из Stage 14 проходит. + ## Goal -Build a convenient, stable, scriptable CLI for `avito-py` that covers every supported SDK domain method with maximum reuse of the existing SDK surface. +Build a convenient, stable, scriptable CLI for `avito-py` that covers every supported sync SDK domain method with maximum reuse of the existing SDK surface. -The CLI must not become a second SDK implementation. It must call `AvitoClient` factories and public domain methods, reuse existing models and serialization, and keep all HTTP/auth/retry/mapping behavior inside the current SDK layers. +The CLI must be a thin product interface over the SDK, not a second SDK implementation. API commands must construct `AvitoClient`, call its public factories, call public domain methods, serialize public SDK models, and leave HTTP/auth/retry/pagination/mapping behavior inside the existing SDK layers. Target outcome: - `avito` console command and `python -m avito` expose the same CLI. -- Account/profile management works without network calls. -- Every sync Swagger-bound public SDK method has a discoverable CLI route. -- Every supported non-Swagger helper workflow has either a CLI route or a documented intentional exclusion. +- Local account/profile/config commands work without Avito network calls. +- Every sync Swagger-bound public SDK method has exactly one canonical CLI command, unless it has a documented intentional exclusion. +- Every supported public non-Swagger helper has a CLI command or a documented exclusion. - CLI coverage is checked automatically against Swagger binding discovery. - Human output is useful by default; JSON output is stable for automation. -- Secrets are never printed in normal, JSON, verbose, debug, or error output. -- Implementation is delivered in small stages with tests and verification gates after each stage. +- Secrets are never printed in human, JSON, verbose, debug, error, coverage, or diagnostic output. +- Implementation is delivered in small reviewable stages, each with tests and verification commands. -## Source Rules +## Normative Rules -Normative documents: +Mandatory documents: - `.ai/STYLEGUIDE.md` - `.ai/cli-guidelines.md` +- `.ai/python-guidelines.md`, through `.ai/STYLEGUIDE.md` - `docs/site/explanations/domain-architecture-v2.md` - `docs/site/explanations/swagger-binding-subsystem.md` @@ -30,37 +82,68 @@ Repository contracts: - Package name: `avito-py`. - Import package: `avito`. -- Public facade: `avito.client.AvitoClient`. -- Sync domain methods are the first CLI coverage target. +- Public sync facade: `avito.client.AvitoClient`. - Swagger/OpenAPI specs in `docs/avito/api/` are the API contract source. - Swagger bindings discovered by `avito.core.swagger_discovery.discover_swagger_bindings()` are the canonical SDK coverage source. -Important style constraints: +Hard constraints: + +- CLI code belongs under `avito/cli/`. +- Keep SDK core/domain/transport/auth layers free of Typer and CLI behavior. +- Production CLI code must not import domain `operations.py`, transport implementations, auth provider internals, or testing fake transports. +- API commands must not call `OperationSpec`, `OperationExecutor`, `Transport`, or `AuthProvider` directly. +- Do not duplicate Swagger contract data in CLI metadata. CLI metadata may store command names, examples, aliases, safety policy, output hints, and documented exclusions only. +- Do not add or change public Avito API SDK methods as part of CLI work unless the normal SDK rules are followed: typed model, operation spec, docstring, and `@swagger_operation(...)`. +- Human-facing CLI text is Russian only: help descriptions, prompts, warnings, errors, and success output. Stable error codes remain uppercase English identifiers. +- No `setattr`, `globals()`, monkey-patching, generated Python source, or dynamic SDK method injection. Deterministic Typer registration from typed registry records is allowed. +- No dead code, unused aliases, unused `TypeVar`s, broad `Any`, or layer mixing. + +Non-goals for the first complete release: + +- Async CLI surface. +- OS keychain integration. First release stores plaintext JSON files protected by permissions and documents that clearly. +- Reimplementing SDK validation in CLI. CLI only coerces shell strings into typed public method arguments and reports invalid CLI syntax early. +- A second public command alias such as `avito-cli`, unless there is a separate product requirement. + +## Current Baseline Findings + +Recorded on 2026-05-10 while preparing this plan: + +```text +sync Swagger bindings: 204 +AvitoClient public factory-like methods: 57 +sync bindings without factory metadata: 4 +``` + +Bindings without factory metadata: + +- `avito.auth.provider.AlternateTokenClient.request_client_credentials_token` +- `avito.auth.provider.AlternateTokenClient.request_refresh_token` +- `avito.auth.provider.TokenClient.request_autoteka_client_credentials_token` +- `avito.auth.provider.TokenClient.request_client_credentials_token` + +Implementation impact: -- CLI code belongs under `avito/cli/`; do not put CLI behavior into core/domain/transport/auth layers. -- Keep core SDK free of Typer imports. -- Do not duplicate transport, auth, retry, request mapping, response mapping, pagination, or validation logic in CLI code. -- Do not return raw `dict` or `Any` from public SDK methods. -- Do not add public Avito API methods without `@swagger_operation(...)`. -- Error messages in SDK and CLI user-facing text are Russian only. Stable error codes remain uppercase English identifiers. -- Avoid dead code, unused aliases, and dynamic method injection. +- These 4 auth-token bindings are not normal domain commands through `AvitoClient` factories. +- Stage 0 must decide whether they are intentionally excluded from generated API CLI coverage, exposed through explicit auth/config workflows, or given factory metadata through a separate SDK architecture change. +- The final coverage linter must count this decision explicitly, not silently treat missing factory metadata as success. ## CLI Architecture -Use a small hand-written CLI shell plus generated/discovered command metadata. +Use a small hand-written CLI shell plus registry/discovery-driven API commands: ```text avito/ cli/ __init__.py app.py # root Typer app and global context - accounts.py # local account/profile commands + accounts.py # local account/profile commands only client.py # CLI-only AvitoClient construction commands.py # generic invocation engine for SDK methods - config.py # CLI home, JSON persistence, account store + config.py # CLI home, JSON persistence, account/config store coverage.py # CLI coverage report and linter helpers errors.py # CLI errors, exit-code mapping, secret sanitization - help.py # optional help command compatibility + help.py # help command compatibility if Typer is insufficient registry.py # command registry built from SDK metadata schemas.py # CLI input coercion from signatures/type hints serialization.py # model/pagination result serialization @@ -69,43 +152,30 @@ avito/ Do not add domain-specific CLI modules for every API package unless a command needs custom UX. The default path must be metadata-driven to avoid hand-copying 204 operations. -Register only the canonical command unless product naming explicitly requires an alias: +Package boundary: + +- `avito/cli/*` may import `avito.client`, `avito.config`, public models, public exceptions, and Swagger discovery/reporting helpers. +- `avito/__main__.py` must contain only the CLI handoff after Stage 1. +- Tests may use `avito.testing.*`, `tests/fake_transport.py`, and public testing helpers; production CLI code must not. + +Register only the canonical command: ```toml [tool.poetry.scripts] avito = "avito.cli.app:app" ``` -Route `python -m avito` to the same Typer app. - ## Command Model -Use the `.ai/cli-guidelines.md` grammar: +Follow `.ai/cli-guidelines.md`: ```text avito [primary arguments] [flags] ``` -Resource names should be derived from SDK factory names with kebab-case: - -- `account` -- `account-hierarchy` -- `ad` -- `ad-stats` -- `autoload-profile` -- `chat` -- `promotion-order` -- `target-action-pricing` -- `delivery-order` -- `realty-listing` - -Actions should be derived from public SDK method names with kebab-case: - -- `get-self` from `get_self` -- `list-services` from `list_services` -- `create-order` from `create_order` +Resource names are derived from `AvitoClient` factory names with kebab-case. Actions are derived from public SDK method names with kebab-case. -Default generated command shape: +Default generated shape: ```bash avito [factory args] [method args] @@ -123,15 +193,18 @@ avito promotion-order list-services --item-id 456 --json Rules: -- Factory arguments and method arguments become named flags by default. +- Factory and method arguments become named flags by default. - Positional arguments are allowed only for obvious single primary identifiers after explicit design review. -- Same SDK concept must use the same CLI flag everywhere: `--user-id`, `--item-id`, `--order-id`, `--chat-id`, `--date-from`, `--date-to`, `--limit`, `--offset`. -- `resource_id` must never appear. -- Generated commands must preserve one obvious path per operation. Compatibility aliases must delegate to the canonical command and be documented as aliases. +- Same SDK concept uses the same flag everywhere: `--user-id`, `--item-id`, `--order-id`, `--chat-id`, `--date-from`, `--date-to`, `--limit`, `--offset`. +- `resource_id` and `--resource-id` are forbidden. +- Generated commands preserve one obvious path per operation. +- Compatibility aliases delegate to canonical commands and do not count toward coverage. +- Local workflows may share a resource with API commands only when the action is unambiguous. `avito account add` is local profile management; `avito account get-self` is an Avito API call. +- Registry construction fails if a local command and generated API command claim the same canonical `resource action`. ## Global Flags -Supported from the root command and all subcommands through one typed CLI context: +Supported from root and subcommands through one typed CLI context: ```text -h, --help @@ -167,15 +240,16 @@ Precedence: 5. System config 6. Built-in defaults -Initial implementation may support only user config, but the precedence contract must be documented and the config resolver must leave room for project/system config without breaking users. +Initial implementation may support only user config, but the resolver must reserve the full precedence contract without breaking users later. Flag behavior: -- `--json` makes success output and CLI errors machine-readable JSON. -- `--quiet` suppresses non-essential success output; when combined with `--json`, commands returning data still emit their JSON result. -- `--plain`, `--table`, and `--wide` are mutually exclusive with `--json`; invalid combinations fail with exit code `2`. +- `--json` emits stable undecorated JSON for success output and JSON errors on stderr. +- JSON stdout must not contain progress, warnings, hints, colors, or prose. +- `--quiet` suppresses non-essential success output; combined with `--json`, commands returning data still emit JSON. +- `--plain`, `--table`, and `--wide` are mutually exclusive with `--json`; invalid combinations exit with code `2`. - `--verbose` is user-facing extra detail and never overrides `--quiet`. -- `--debug` may include diagnostic fields but must never leak secrets. +- `--debug` may include diagnostics but must never leak secrets. - `--no-color` and `NO_COLOR=1` disable color everywhere. - Commands must not prompt when `--no-input` is set or stdin is not a TTY. @@ -193,7 +267,7 @@ Flag behavior: 8 external dependency unavailable ``` -Every CLI error includes a stable code such as: +Stable error codes include: - `CONFIG_INVALID` - `ACCOUNT_NOT_FOUND` @@ -204,7 +278,7 @@ Every CLI error includes a stable code such as: - `COMMAND_UNSUPPORTED` - `SDK_METHOD_FAILED` -Errors and warnings go to stderr. Command results go to stdout. JSON errors are valid JSON on stderr. +Errors, warnings, progress, and debug diagnostics go to stderr. Command results go to stdout. JSON errors are valid JSON on stderr. ## Account and Config Model @@ -226,7 +300,7 @@ Home override precedence: File-system requirements: -- Create the CLI home directory lazily with `0700` permissions. +- Create the CLI home lazily with `0700` permissions. - Write `accounts.json` and `config.json` with `0600` permissions. - Save JSON atomically through a temporary file in the same directory and `os.replace`. - Never create files or directories on import. @@ -242,6 +316,8 @@ Stored account fields: - `user_id: int | None` - OAuth fields already supported by `AuthSettings`: `scope`, `refresh_token`, `token_url`, `alternate_token_url`, `autoteka_token_url`, `autoteka_client_id`, `autoteka_client_secret`, `autoteka_scope` +Active account belongs in config as one profile name. Do not store contradictory `active: bool` flags on each account. + Canonical flags: - `--client-id` @@ -254,35 +330,40 @@ Ticket-compatible aliases: - `--api-key` as an alias for `--client-secret` - `--endpoint` as an alias for `--base-url` -Secrets must be omitted or masked in every output format, including JSON and debug diagnostics. - ## SDK Reuse Strategy -The CLI invokes SDK methods through a generic pipeline: +Generic API invocation pipeline: + +1. Resolve global flags and validate CLI mode. +2. Resolve profile/account and build `AvitoSettings`. +3. Create `AvitoClient(settings)` in a context manager. +4. Resolve CLI resource to an `AvitoClient` factory. +5. Coerce CLI strings into factory and method arguments using public signatures/type hints. +6. Call the SDK factory. +7. Call the public domain method. +8. Serialize the SDK return value through `model_dump()` / `to_dict()` / bounded pagination helpers. +9. Render as table, grouped text, plain value, or JSON. -1. Resolve profile/account and build `AvitoSettings`. -2. Create `AvitoClient(settings)` in a context manager. -3. Resolve the CLI resource to an `AvitoClient` factory. -4. Coerce CLI strings into the factory arguments and method arguments using public signatures/type hints. -5. Call the SDK factory. -6. Call the public domain method. -7. Serialize the SDK return value through existing `model_dump()` / `to_dict()` / pagination materialization helpers. -8. Render as table, grouped text, plain value, or JSON. +Constraints: -Do not use `OperationSpec` directly from CLI commands. Operation specs remain internal SDK metadata. +- Build `AvitoClient` only after command syntax, config/profile resolution, and secret masking context are ready. +- Never instantiate domain objects directly in CLI. +- Never call operation specs directly from CLI commands. +- CLI may inspect public signatures and type hints, but not private domain object attributes. +- Dataclass serialization fallback is allowed only for CLI-local dataclasses, not SDK response models. -The generic invocation engine must support: +The coercion engine must support: -- primitive values: `str`, `int`, `float`, `bool` +- `str`, `int`, `float`, `bool` - `date` and `datetime` strings with validation - enums by value/name with clear validation errors - optional values -- list values from repeated flags or comma-separated values where documented +- list values from repeated flags or documented comma-separated values - public dataclass input models only when they are already public SDK input models - `PaginatedList[T]` with explicit materialization limits or streaming-safe iteration - file inputs only for methods whose public signature already accepts file/path-like public inputs -If a method cannot be safely exposed by the generic engine, add it to a typed exception list with a reason and a tracked follow-up. The acceptance target for final completion is zero unsupported sync Swagger-bound methods unless an operation is intentionally not usable from CLI and documented. +If a method cannot be safely exposed by the generic engine, add it to a typed exclusion list with reason, owner, and follow-up. Final acceptance target is zero unsupported sync Swagger-bound methods unless intentionally excluded and documented. ## Registry and Coverage @@ -295,53 +376,62 @@ Build a CLI registry from existing SDK metadata: - `binding.method_args` - public Python signatures and type hints +Registry records contain: + +- stable canonical command id, for example `account.get-self`; +- resource and action in lowercase kebab-case; +- binding operation key for Swagger-bound API commands; +- SDK factory name and public method name; +- factory and method argument metadata from discovery/signatures; +- safety classification: read, write, destructive, expensive, local; +- output hint: object, collection, mutation result, plain value, unknown; +- examples and related commands for help; +- aliases stored separately from canonical records; +- exclusions stored separately with reason and follow-up. + Coverage invariant: ```text -each sync discovered Swagger binding -> exactly one canonical CLI command +each sync discovered Swagger binding -> exactly one canonical CLI command or documented intentional exclusion each canonical API CLI command -> exactly one sync discovered Swagger binding each supported public non-Swagger helper -> CLI command or documented exclusion ``` -Compatibility aliases are allowed but must not count as separate canonical coverage. - -Non-Swagger public helpers are not part of the Swagger one-to-one invariant, but they are part of the user-facing SDK. Track them separately: - -- `AvitoClient` summary/workflow helpers such as account health, chat/order/review/promotion summaries, and capability discovery; -- public domain helper methods without Swagger bindings, if any exist; -- local CLI-only workflows such as `account`, `config`, `status`, `doctor`, and `completion`. - -The coverage report must show separate counts: +Coverage report fields: - `api_bound_commands` - `api_bound_missing_commands` +- `api_bound_exclusions` - `helper_commands` - `helper_exclusions` - `local_cli_commands` +- `aliases` -Add a CLI coverage linter: +Add a linter: ```bash poetry run python scripts/lint_cli_coverage.py ``` -The linter must fail when: +The linter fails when: -- a sync discovered Swagger binding has no canonical CLI command; +- a sync discovered Swagger binding has no canonical CLI command or explicit exclusion; - a canonical API CLI command has no binding; - two canonical CLI commands map to the same binding; -- a public supported helper has neither a command nor an explicit exclusion; -- a command exposes a forbidden `resource-id` flag; +- a supported public helper has neither a command nor an exclusion; +- a local command conflicts with a generated API command; +- a command exposes `resource-id`; - a command exposes a secret in an output schema; -- a command uses a non-kebab-case resource/action/flag name. +- a command uses non-kebab-case resource/action/flag names; +- an exclusion lacks reason and follow-up. -Add `make cli-lint` and include it in `make check` after the CLI reaches full coverage. +Add `make cli-lint` and include it in `make check` after full CLI coverage is implemented. ## Output Contract Default output: -- Human-readable. +- Human-readable Russian text. - Tables for collections. - Grouped key-value output for one object. - Concise success text for writes. @@ -350,28 +440,16 @@ Default output: Machine output: - `--json` emits stable, undecorated JSON. -- Top-level objects are stable and named by resource/action. +- Top-level objects are stable and named by resource/action where useful. - SDK models are serialized via their public serialization contract. - Pagination output includes enough metadata when available. -Examples: +Secret masking: -```json -{"accounts": [{"name": "dev", "base_url": "https://api.avito.ru", "active": true}]} -``` - -```json -{"result": {"operation": "account.get_self", "data": {"id": 123}}} -``` - -Avoid raw secrets in all output: - -- `client_secret` -- `api_key` -- `refresh_token` -- `access_token` -- `Authorization` -- token-like fields +- Mask by key name and value pattern where practical. +- Use the same sanitizer for human output, JSON output, errors, verbose/debug output, and coverage/debug reports. +- Cover nested structures, lists, exception metadata, and debug mode in tests. +- Never print raw `client_secret`, `api_key`, `refresh_token`, `access_token`, `Authorization`, or token-like fields. ## Help and Completion @@ -385,11 +463,9 @@ avito help account avito help account get-self ``` -If Typer makes `avito help account` impractical, implement a small `help` command that delegates to the registry metadata instead of relying only on Typer internals. - Help must include: -- description in Russian; +- Russian description; - usage; - at least one minimal example; - one automation-friendly `--json --no-input` example where relevant; @@ -408,7 +484,13 @@ Completion can start with static command/flag completion and later add profile/a ## Implementation Stages -Each stage must be small enough to review independently and must end with tests and verification. +Stage policy: + +- Each stage must leave the branch in a releasable state. +- Every behavior stage includes tests in the same change. +- After Stage 4, CLI coverage report changes must be intentional in every CLI metadata change. +- After Stage 10, `scripts/lint_cli_coverage.py` is a required gate for all CLI changes. +- Do not broaden command coverage before the previous stage's verification passes. ### Stage 0: Baseline Audit @@ -418,7 +500,9 @@ Deliverables: - Record current `AvitoClient` factory mapping count. - Confirm which factory names exist in `AvitoClient` but not in bindings. - Confirm whether every sync binding has `factory` metadata. -- Record public non-Swagger helper methods and decide command vs exclusion. +- Record public non-Swagger helpers and decide command vs exclusion. +- Record current `python -m avito` behavior and mark it for replacement. +- Record existing `Makefile` gates that CLI work must integrate with. Verification: @@ -429,8 +513,15 @@ poetry run pytest tests/core/test_swagger_linter.py tests/contracts/test_swagger Exit criteria: -- A short audit note is added to this plan or a linked implementation note. -- Any missing factory metadata is tracked before CLI generation starts. +- Audit note includes exact counts and reproducible commands. +- Missing factory metadata is tracked before CLI generation starts. + +Stage checklist: + +- [ ] Baseline command output is pasted into this plan or a linked implementation note. +- [ ] Sync binding count, factory-like method count, and missing factory metadata list are recorded. +- [ ] The 4 auth-token bindings have an explicit planned treatment: exclusion, auth workflow, or SDK change. +- [ ] Stage verification commands pass. ### Stage 1: CLI Dependency and Shell @@ -438,10 +529,11 @@ Deliverables: - Add `typer` dependency. - Add `avito/cli/` package skeleton. -- Add root `avito` app with global context. +- Add root `avito` app with typed global context. - Add `avito --help`, `avito --version`, `avito version`. -- Route `python -m avito` to the CLI. +- Route `python -m avito` to the same CLI app. - Register Poetry script. +- Use Russian help text from the beginning. Tests: @@ -459,6 +551,20 @@ poetry run ruff check avito/cli tests/cli poetry build ``` +Exit criteria: + +- Help/version commands do not touch network, config, or account files. +- `python -m avito --help` and `avito --help` exercise the same app. + +Stage checklist: + +- [ ] `typer` is added as a runtime dependency. +- [ ] `avito/cli/` package exists with only the minimal shell files. +- [ ] `avito --help`, `avito --version`, `avito version`, and `python -m avito --help` work. +- [ ] No config directory or account file is created by help/version commands. +- [ ] `tests/cli/test_app.py` covers the shell behavior. +- [ ] Stage verification commands pass. + ### Stage 2: Errors, UI, and Safe Output Deliverables: @@ -467,8 +573,9 @@ Deliverables: - Add `CliError` hierarchy and exit-code mapping. - Add stdout/stderr output helpers. - Add JSON/human error rendering. -- Add secret masking/sanitization. +- Add one reusable sanitizer used by all renderers. - Add color handling for `--no-color` and `NO_COLOR=1`. +- Add invalid global flag-combination validation. Tests: @@ -486,26 +593,41 @@ poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` +Exit criteria: + +- Every CLI error has a Russian message, stable uppercase code, and documented exit code. +- No traceback is printed by default; diagnostics are sanitized. + +Stage checklist: + +- [ ] `CliContext` is typed and shared by commands through one code path. +- [ ] `CliError` maps to documented exit codes. +- [ ] Human and JSON errors use the same sanitized error payload. +- [ ] Invalid output flag combinations exit with code `2`. +- [ ] `--quiet`, `--debug`, `--verbose`, `--no-color`, and `NO_COLOR=1` are covered by tests. +- [ ] Stage verification commands pass. + ### Stage 3: Account Store and Profile Commands Deliverables: - Implement CLI home resolver and atomic JSON persistence. -- Implement account dataclasses and `AccountStore`. +- Implement account/config dataclasses and stores. - Add account commands: - `avito account add` - `avito account list` - `avito account use ` - `avito account current` - `avito account delete ` -- Add optional `account remove` only as a documented alias for `account delete`. -- Add CLI-only helper that converts the active account to `AvitoSettings`. +- Add optional `account remove` only as documented alias for `account delete`. +- Convert active account to `AvitoSettings`. +- Store active account name in config, not per-account flags. Tests: - default home and environment override precedence; - lazy directory creation; -- file permissions where the platform supports it; +- file permissions where platform supports it; - add/reload account; - duplicate account conflict; - active account set/get/clear; @@ -522,27 +644,43 @@ poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` +Exit criteria: + +- Account commands perform no Avito API network calls. +- Secret fields are masked in every output mode. + +Stage checklist: + +- [ ] CLI home resolution follows `AVITO_PY_HOME`, `MY_SDK_HOME`, then `~/.avito-py`. +- [ ] Directory/file creation is lazy and uses required permissions where supported. +- [ ] JSON writes are atomic through same-directory temp files and `os.replace`. +- [ ] Account add/list/use/current/delete commands work without network. +- [ ] Active account is stored once in config, not as per-account boolean state. +- [ ] `--api-key` and `--endpoint` aliases are tested. +- [ ] Stage verification commands pass. + ### Stage 4: CLI Registry From SDK Metadata Deliverables: - Build `avito/cli/registry.py`. -- Convert sync discovered Swagger bindings into canonical resource/action commands. -- Preserve mapping to factory name, factory args, method name, method args, operation key, and domain. -- Register public non-Swagger helper commands in a separate helper registry. -- Add explicit alias support separate from canonical command records. -- Add registry JSON/debug report command: - - `avito cli coverage --json` - - or hidden internal command if public exposure is not desired. +- Convert sync discovered Swagger bindings into canonical resource/action records. +- Preserve factory name, factory args, method name, method args, operation key, and domain. +- Register local commands and public non-Swagger helpers in separate categories. +- Add alias support separate from canonical command records. +- Add deterministic collision detection for `resource action`. +- Add exclusion record type. +- Add registry/coverage JSON report command or hidden internal report. Tests: - registry includes all sync discovered bindings; -- registry accounts for public helper methods separately from API bindings; +- registry accounts for helpers separately from API bindings; - resource/action names are kebab-case; - every command maps to exactly one binding; - no duplicate canonical commands; -- aliases do not affect canonical coverage. +- aliases do not affect canonical coverage; +- local/API collisions fail. Verification: @@ -553,44 +691,108 @@ poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` -### Stage 5: Generic Input Coercion and Invocation +Exit criteria: + +- Registry builds without creating `AvitoClient`. +- Registry tests fail if a new sync Swagger binding lacks a command or exclusion. + +Stage checklist: + +- [ ] Registry records are typed and deterministic. +- [ ] API, helper, local, alias, and exclusion records are separate categories. +- [ ] Canonical API commands map one-to-one to sync Swagger bindings. +- [ ] Local/API command collisions fail during registry construction. +- [ ] `scripts/lint_cli_coverage.py` exists and exercises the registry. +- [ ] Stage verification commands pass. + +### Stage 5: Generic Input Coercion Deliverables: - Implement `avito/cli/schemas.py`. +- Implement typed CLI parameter metadata. +- Coerce CLI strings from signatures/type hints. +- Support repeated flags and documented comma-separated list parsing. +- Validate enum names/values and date/datetime formats with Russian errors. + +Tests: + +- coercion for primitives, bools, dates, datetimes, enums, optionals, and lists; +- missing required values fail without prompt in `--no-input`; +- invalid values produce `VALIDATION_FAILED`; +- kebab-case flag generation is stable; +- `resource-id` is rejected. + +Verification: + +```bash +poetry run pytest tests/cli/test_schemas.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli +``` + +Exit criteria: + +- Input coercion is testable without constructing `AvitoClient` or invoking network code. + +Stage checklist: + +- [ ] CLI parameter metadata is typed and independent from Typer internals where practical. +- [ ] Primitive, bool, date, datetime, enum, optional, and list coercion are tested. +- [ ] Repeated flags and documented comma-separated values behave consistently. +- [ ] Invalid values produce Russian `VALIDATION_FAILED` errors. +- [ ] Generated flag names are kebab-case and never `--resource-id`. +- [ ] Stage verification commands pass. + +### Stage 6: Generic Invocation Engine + +Deliverables: + - Implement `avito/cli/commands.py`. -- Coerce CLI flags from signatures/type hints. - Build and call `AvitoClient` through active account/profile. - Invoke public SDK factory and method. - Map SDK exceptions to CLI errors. -- Add explicit unsupported-method registry only for cases with documented reasons. +- Add explicit unsupported-method registry only for documented exclusions. +- Add a fake-client/fake-domain test seam for invocation tests without real HTTP. Tests: -- coercion for primitives, bools, dates, datetimes, enums, optionals, lists; -- missing required values fail without prompt in `--no-input`; -- invalid values produce `VALIDATION_FAILED`; - active profile is used by default; - `--profile` overrides active profile; +- CLI invokes expected factory and public method with expected arguments; +- CLI never calls operation specs or transport directly; - SDK `AuthenticationError`, `AuthorizationError`, `ValidationError`, `ConflictError`, and not-found equivalents map to documented exit codes. Verification: ```bash -poetry run pytest tests/cli/test_schemas.py tests/cli/test_commands.py +poetry run pytest tests/cli/test_commands.py poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` -### Stage 6: Result Serialization and Pagination +Exit criteria: + +- API invocation path is a thin adapter over `AvitoClient` factories and public domain methods. + +Stage checklist: + +- [ ] API command invocation resolves profile/config before constructing `AvitoClient`. +- [ ] `AvitoClient` is always used as a context manager. +- [ ] Invocation calls factory method, then public domain method. +- [ ] Tests prove operation specs and transport are not called directly by CLI code. +- [ ] SDK exceptions map to documented CLI exit codes and sanitized messages. +- [ ] Stage verification commands pass. + +### Stage 7: Result Serialization and Pagination Deliverables: - Implement `avito/cli/serialization.py`. - Serialize SDK models through `model_dump()` / `to_dict()`. -- Serialize dataclasses, enums, dates, datetimes, lists, and primitive values safely. -- Handle `PaginatedList[T]` with documented defaults. -- Add `--limit` or `--page-limit` only when needed to avoid accidentally materializing unbounded result sets. +- Serialize CLI-local dataclasses, enums, dates, datetimes, lists, and primitive values safely. +- Handle `PaginatedList[T]` with documented bounded defaults. +- Add `--limit`, `--page-limit`, or `--all` only when needed to avoid unbounded materialization. - Render default tables for collections and grouped output for single models. Tests: @@ -609,24 +811,38 @@ poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` -### Stage 7: First Vertical API Slice +Exit criteria: + +- JSON output schema is stable for object, collection, primitive, and paginated values. +- Default pagination cannot accidentally materialize an unbounded result set. + +Stage checklist: + +- [ ] SDK models serialize through `model_dump()` or `to_dict()`. +- [ ] CLI-local dataclasses, enums, dates, datetimes, lists, and primitives serialize safely. +- [ ] Pagination defaults are bounded and documented in command help. +- [ ] Human table/grouped output and JSON output are both tested. +- [ ] Secret sanitizer is applied after serialization and before rendering. +- [ ] Stage verification commands pass. + +### Stage 8: First Vertical API Slice Deliverables: -- Expose and test a small read-only vertical slice: +- Expose and test a small read-only slice: - `avito account get-self` - `avito account get-balance` - one paginated/list command if available. -- Use `SwaggerFakeTransport` or existing fake transport infrastructure. +- Use `SwaggerFakeTransport` or existing fake transport infrastructure in tests only. - Do not make real network calls in tests. Tests: -- command invokes the expected SDK method; -- request path/query/body match Swagger fake transport expectations; +- command invokes expected SDK method; +- request path/query/body match fake transport expectations; - human output works; - JSON output works; -- errors are mapped correctly. +- errors map correctly. Verification: @@ -637,15 +853,28 @@ poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` -### Stage 8: All-Domain Generated API Commands +Exit criteria: + +- At least one registry-bound API command runs end to end through generic path and fake transport. + +Stage checklist: + +- [ ] `account get-self` runs through registry, coercion, invocation, serialization, and UI layers. +- [ ] `account get-balance` runs through the same generic path. +- [ ] At least one list/paginated command is covered if an account-domain candidate exists. +- [ ] Tests use fake transport only and make no real network calls. +- [ ] Human output and `--json` output are both covered. +- [ ] Stage verification commands pass. + +### Stage 9: Read-Only All-Domain API Coverage Deliverables: -- Register generated commands for every sync Swagger-bound method. -- Add domain/resource help pages. +- Register generated read/list/get commands for every sync Swagger-bound read method supported by the generic engine. +- Add domain/resource help pages for read-only commands. - Add generated examples where safe and meaningful. -- Add explicit command metadata for methods needing custom list/file/enum parsing. -- Eliminate or document every unsupported sync binding. +- Add command metadata for methods needing custom list/file/enum parsing. +- Document every temporarily unsupported read-only sync binding. Required domains: @@ -661,21 +890,11 @@ Required domains: - `realty` - `tariffs` -Required helper workflows: - -- account health/business summary; -- chat summary; -- order summary; -- review summary; -- promotion summary; -- capability discovery. - Tests: -- one smoke invocation per domain with fake transport; -- one command metadata assertion per discovered sync binding; -- generic coverage test that fails on missing commands; -- helper command metadata or explicit exclusions are covered; +- one read-only smoke invocation per domain with fake transport; +- one metadata assertion per discovered read-only sync binding; +- coverage test fails on missing read-only commands; - no generated command exposes forbidden names or secret fields. Verification: @@ -688,15 +907,31 @@ poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` -### Stage 9: Write Commands, Safety, and Dry Run +Exit criteria: + +- All sync Swagger-bound read-only methods are exposed or documented with temporary exclusions. +- Coverage report separates complete read coverage from pending write/destructive coverage. + +Stage checklist: + +- [ ] Every required domain has at least one read-only smoke command test. +- [ ] Metadata tests cover every discovered read-only sync binding. +- [ ] Unsupported read-only bindings have explicit temporary exclusions with follow-up. +- [ ] Domain/resource help exists for generated read-only commands. +- [ ] Coverage linter distinguishes read coverage from pending write coverage. +- [ ] Stage verification commands pass. + +### Stage 10: Write Commands, Safety, and Dry Run Deliverables: - Classify write/destructive commands from HTTP method and/or SDK metadata. - Require confirmation for destructive commands unless `--yes` or exact `--confirm` is supplied. -- Support `--dry-run` only when the SDK public method already supports `dry_run` or when the CLI can safely preview without changing SDK behavior. +- Support `--dry-run` only when the SDK public method already supports `dry_run` or when CLI can safely preview without changing SDK behavior. - Do not fake dry-run for SDK methods that would still execute transport. - Ensure write commands build the same SDK call in dry-run and apply modes where `dry_run` exists. +- Register generated commands for remaining write sync Swagger-bound methods. +- Eliminate or document every unsupported sync binding. Tests: @@ -704,18 +939,76 @@ Tests: - `--no-input` fails instead of prompting; - `--yes` and `--confirm` behave deterministically; - dry-run methods do not call transport when SDK contract says they should not; -- non-dry-run write commands call transport exactly once. +- non-dry-run write commands call transport exactly once; +- one write smoke invocation per write-capable domain with fake transport; +- coverage test fails on missing write commands. Verification: ```bash poetry run pytest tests/cli/test_write_safety.py +poetry run pytest tests/cli/test_all_domains_metadata.py tests/cli/test_domain_smoke_commands.py poetry run pytest tests/domains/promotion tests/domains/orders +poetry run python scripts/lint_cli_coverage.py poetry run mypy avito -poetry run ruff check avito/cli tests/cli +poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py +``` + +Exit criteria: + +- Every sync discovered Swagger binding has exactly one canonical CLI command or documented intentional exclusion. +- No destructive command can run accidentally in non-interactive mode. +- `make cli-lint` can now be added to `make check`. + +Stage checklist: + +- [ ] Write/destructive classification is deterministic and tested. +- [ ] Destructive commands require prompt, `--yes`, or exact `--confirm`. +- [ ] `--no-input` never hangs and fails safely when confirmation is required. +- [ ] `--dry-run` is exposed only for SDK methods that safely support it. +- [ ] Remaining sync Swagger bindings are covered or intentionally excluded. +- [ ] `make cli-lint` is added to `make check`. +- [ ] Stage verification commands pass. + +### Stage 11: Public Helper Workflows + +Deliverables: + +- Expose supported non-Swagger helper workflows or document exclusions: + - account health/business summary; + - chat summary; + - order summary; + - review summary; + - promotion summary; + - capability discovery. +- Keep helper commands out of the Swagger one-to-one coverage count. +- Ensure helper commands use public `AvitoClient`/SDK methods only. + +Tests: + +- helper command metadata or explicit exclusions are covered; +- helper commands do not conflict with API-bound commands; +- helper outputs are sanitized and support `--json`. + +Verification: + +```bash +poetry run pytest tests/cli/test_helper_workflows.py +poetry run python scripts/lint_cli_coverage.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` -### Stage 10: Config, Status, Doctor, and Completion +Stage checklist: + +- [ ] Each supported helper workflow has a command or explicit exclusion. +- [ ] Helper commands are excluded from Swagger one-to-one coverage counts. +- [ ] Helper commands use only public `AvitoClient`/SDK methods. +- [ ] Helper commands do not collide with generated API commands. +- [ ] Helper outputs support human and JSON modes and are sanitized. +- [ ] Stage verification commands pass. + +### Stage 12: Config, Status, Doctor, and Completion Deliverables: @@ -744,7 +1037,16 @@ poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` -### Stage 11: Documentation +Stage checklist: + +- [ ] `config get/set/unset/list/list --show-source` work and are tested. +- [ ] Config source precedence is visible in debug/source output. +- [ ] `status` reports local readiness without leaking secrets. +- [ ] `doctor` reports malformed config and permission problems. +- [ ] Completion commands exist for bash, zsh, and fish. +- [ ] Stage verification commands pass. + +### Stage 13: Documentation Deliverables: @@ -754,6 +1056,9 @@ Deliverables: - Docs page explaining generated all-domain command grammar. - Examples for human and JSON automation usage. - Document that secrets are stored locally in plaintext JSON protected with `0600` permissions. +- Document sync Swagger-bound coverage guarantee and exclusion policy. +- Document command naming algorithm and compatibility alias policy. +- Document config precedence order, including reserved project/system config slots if they remain unimplemented. Verification: @@ -762,7 +1067,16 @@ poetry run mkdocs build --strict make docs-check ``` -### Stage 12: Final Gate +Stage checklist: + +- [ ] README includes a CLI quickstart. +- [ ] Docs explain account/profile setup and local plaintext secret storage. +- [ ] Docs list global flags, output modes, exit codes, config files, and environment variables. +- [ ] Docs explain generated command naming and alias policy. +- [ ] Docs state sync Swagger-bound coverage guarantees and exclusion policy. +- [ ] Stage verification commands pass. + +### Stage 14: Final Gate Run the full gate before completing the branch: @@ -784,6 +1098,17 @@ If generated docs, snippets, coverage pages, or reference output changed: make docs-strict ``` +Stage checklist: + +- [ ] `poetry run pytest tests/cli` passes. +- [ ] Swagger registry/contract tests pass. +- [ ] `poetry run mypy avito` passes. +- [ ] `poetry run ruff check .` passes. +- [ ] Python guidelines, architecture, and CLI coverage linters pass. +- [ ] `poetry build` passes. +- [ ] `make check` passes. +- [ ] `make docs-strict` passes when docs/reference output changed. + ## Acceptance Checklist - [ ] `typer` dependency added. @@ -797,15 +1122,14 @@ make docs-strict - [ ] CLI home directory is created lazily with `0700` permissions. - [ ] `accounts.json` and `config.json` are written atomically with `0600` permissions. - [ ] Account commands add/list/use/current/delete accounts. -- [ ] `account remove` is omitted or implemented only as a documented alias for `account delete`. +- [ ] `account remove` is omitted or implemented only as documented alias for `account delete`. - [ ] `account add` supports `--client-id`, `--client-secret`, `--base-url`, `--api-key`, and `--endpoint`. - [ ] No CLI output leaks raw secrets. - [ ] CLI errors use stable error codes and documented exit codes. - [ ] Results go to stdout; errors, warnings, progress, and debug diagnostics go to stderr. - [ ] `--json` emits stable JSON for success and errors. -- [ ] `--quiet`, `--plain`, `--table`, `--wide`, `--no-input`, `--no-color`, `--verbose`, and `--debug` are documented and tested. - [ ] CLI registry is built from SDK Swagger binding metadata. -- [ ] Every sync discovered Swagger binding has exactly one canonical CLI command. +- [ ] Every sync discovered Swagger binding has exactly one canonical CLI command or documented intentional exclusion. - [ ] Every canonical API CLI command maps to exactly one sync discovered Swagger binding. - [ ] Every supported public non-Swagger helper has a CLI command or documented exclusion. - [ ] Compatibility aliases do not count as canonical coverage. @@ -818,14 +1142,13 @@ make docs-strict - [ ] Destructive commands require confirmation unless `--yes` or `--confirm` is supplied. - [ ] `--dry-run` is exposed only for SDK methods that safely support it. - [ ] One smoke command per domain is tested through fake transport. -- [ ] CLI coverage linter exists and passes. +- [ ] CLI coverage linter exists, passes, and is included in `make check` after full coverage. - [ ] README and docs include CLI usage, config, output, and exit-code contracts. - [ ] Minimum stage verification commands pass during implementation. - [ ] Final `make check` passes before completion. ## Open Decisions -- Whether to expose async SDK methods in CLI. Initial plan targets sync methods only because CLI processes are synchronous command executions and sync Swagger coverage is the canonical first product surface. - Whether `avito cli coverage` should be public or hidden. The linter/report is required either way. -- Whether generated commands should materialize all pagination by default or require an explicit `--all`. Conservative default: bounded output with explicit opt-in for full materialization. -- Whether to add `avito-cli` as an alias. Conservative default: do not add it without a product requirement. +- Whether paginated commands should default to first page, bounded page count, or require explicit `--all`. Conservative default: bounded output with explicit opt-in for full materialization. +- Whether generated API commands should support custom positional primary IDs after the first release. Conservative default: named flags only. From 66f39e71f3490a354f5f41dd4dbe6491bf5e7d6c Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sun, 10 May 2026 12:24:00 +0300 Subject: [PATCH 08/38] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BD=D0=B0=D0=B4=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.md | 220 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 190 insertions(+), 30 deletions(-) diff --git a/todo.md b/todo.md index 5871ee5..b47a21d 100644 --- a/todo.md +++ b/todo.md @@ -91,7 +91,9 @@ Hard constraints: - CLI code belongs under `avito/cli/`. - Keep SDK core/domain/transport/auth layers free of Typer and CLI behavior. - Production CLI code must not import domain `operations.py`, transport implementations, auth provider internals, or testing fake transports. +- Production CLI code must not import from `tests`, `avito.testing`, `tests/fake_transport.py`, or `avito.core.operations`. - API commands must not call `OperationSpec`, `OperationExecutor`, `Transport`, or `AuthProvider` directly. +- API commands must not instantiate domain objects directly. - Do not duplicate Swagger contract data in CLI metadata. CLI metadata may store command names, examples, aliases, safety policy, output hints, and documented exclusions only. - Do not add or change public Avito API SDK methods as part of CLI work unless the normal SDK rules are followed: typed model, operation spec, docstring, and `@swagger_operation(...)`. - Human-facing CLI text is Russian only: help descriptions, prompts, warnings, errors, and success output. Stable error codes remain uppercase English identifiers. @@ -111,10 +113,28 @@ Recorded on 2026-05-10 while preparing this plan: ```text sync Swagger bindings: 204 -AvitoClient public factory-like methods: 57 +AvitoClient public callable methods, excluding close/from_env/auth/debug_info: 56 +sync Swagger binding factories with factory metadata: 48 sync bindings without factory metadata: 4 ``` +Sync binding count by Swagger domain: + +```text +accounts: 8 +ads: 28 +auth: 4 +autoteka: 26 +cpa: 14 +jobs: 25 +messenger: 18 +orders: 45 +promotion: 24 +ratings: 4 +realty: 7 +tariffs: 1 +``` + Bindings without factory metadata: - `avito.auth.provider.AlternateTokenClient.request_client_credentials_token` @@ -125,9 +145,66 @@ Bindings without factory metadata: Implementation impact: - These 4 auth-token bindings are not normal domain commands through `AvitoClient` factories. -- Stage 0 must decide whether they are intentionally excluded from generated API CLI coverage, exposed through explicit auth/config workflows, or given factory metadata through a separate SDK architecture change. +- Treat these 4 auth-token bindings as intentional non-domain API exclusions for the first CLI release. +- Expose user-facing credential/account readiness through local `account`, `status`, and `doctor` workflows, not by turning token client methods into generic API commands. +- If a future release exposes direct token exchange commands, it must be a separate SDK architecture change with explicit public facade design; CLI must not call `TokenClient` or `AlternateTokenClient` directly. - The final coverage linter must count this decision explicitly, not silently treat missing factory metadata as success. +Current public non-Swagger helper/workflow candidates on `AvitoClient`: + +- `account_health` +- `business_summary` compatibility wrapper for `account_health` +- `listing_health` +- `chat_summary` +- `order_summary` +- `review_summary` +- `promotion_summary` +- `capabilities` + +Initial helper policy: + +- Canonical CLI commands may cover `account_health`, `listing_health`, `chat_summary`, `order_summary`, `review_summary`, `promotion_summary`, and `capabilities`. +- `business_summary` is a compatibility helper and should not receive a second canonical command unless product requirements explicitly demand an alias. If exposed, it is an alias and does not count as helper coverage. +- `auth()` and `debug_info()` remain SDK support surfaces, not API coverage commands. Their CLI equivalents are `status` and `doctor`. + +## Documentation Structure Findings + +Current documentation uses MkDocs Material with `docs_dir: docs/site`. +Navigation is controlled by `awesome-pages` through `.pages` files: + +- `docs/site/.pages` is the top-level nav: `Главная`, `Tutorials`, `How-to`, `Reference`, `Explanations`, `Changelog`. +- `docs/site/tutorials/.pages` contains onboarding tutorials. +- `docs/site/how-to/.pages` contains task-oriented recipes. +- `docs/site/reference/.pages` contains stable public contracts and generated reference pages. +- `docs/site/explanations/.pages` contains architecture and rationale pages. + +Generated reference pages are produced by `docs/site/assets/_gen_reference.py` during MkDocs builds: + +- `reference/coverage.md` +- `reference/api-report.md` +- `reference/operations.md` +- `reference/domains/*.md` +- `reference/enums.md` + +CLI documentation must follow this structure instead of adding an isolated page: + +- README: short CLI quickstart only, with link to full docs. +- `docs/site/index.md`: add CLI as a first-class entry point after CLI release. +- `docs/site/tutorials/getting-started.md`: add the shortest first CLI call path, or a short cross-link if the page would become noisy. +- `docs/site/how-to/cli.md`: practical CLI setup and daily workflows. +- `docs/site/reference/cli.md`: stable CLI contract: grammar, global flags, output formats, exit codes, config files, environment variables, safety flags, command coverage policy. +- `docs/site/explanations/cli-architecture.md`: design rationale: thin wrapper over `AvitoClient`, registry/discovery, coverage linter phases, exclusions, secret masking, pagination policy. +- `docs/site/explanations/security-and-redaction.md`: add CLI secret-storage and output-redaction notes when account store lands. +- `docs/site/explanations/api-coverage-and-deprecations.md`: add CLI coverage guarantee and documented-exclusion policy after the coverage linter exists. +- `docs/site/how-to/auth-and-config.md`: link CLI profile/account setup to SDK env-based configuration without duplicating the whole config reference. + +Navigation updates required: + +- Add `cli.md` to `docs/site/how-to/.pages`. +- Add `cli.md` to `docs/site/reference/.pages`. +- Add `cli-architecture.md` to `docs/site/explanations/.pages`. +- If README/index/tutorial links are added before the CLI is usable, mark them clearly as planned only. Prefer adding public-facing docs after Stage 12 when commands exist. + ## CLI Architecture Use a small hand-written CLI shell plus registry/discovery-driven API commands: @@ -152,6 +229,15 @@ avito/ Do not add domain-specific CLI modules for every API package unless a command needs custom UX. The default path must be metadata-driven to avoid hand-copying 204 operations. +Command registration approach: + +- Use Typer for the root app, global options, local workflow commands, and help/version/status/doctor/config/account commands. +- Register generated API commands deterministically from registry records. +- If Typer's signature-based command model is too rigid for registry-built parameters, attach typed `click.Command` objects to the Typer app. This is allowed because it is deterministic command registration, not SDK method injection. +- Do not generate Python source files for commands. +- Do not use `setattr`, `globals()`, monkey-patching, or modifying SDK/domain classes to create commands. +- Generated command callbacks must all delegate to one invocation engine; command-specific behavior belongs in registry metadata only when the generic path cannot infer it safely. + Package boundary: - `avito/cli/*` may import `avito.client`, `avito.config`, public models, public exceptions, and Swagger discovery/reporting helpers. @@ -363,6 +449,14 @@ The coercion engine must support: - `PaginatedList[T]` with explicit materialization limits or streaming-safe iteration - file inputs only for methods whose public signature already accepts file/path-like public inputs +Complex input policy: + +- Do not expose raw Avito request bodies. +- Do not expose internal request DTOs. +- If a public SDK method already accepts a documented public input model, CLI may accept either explicit model-field flags or `--input-json ` that is parsed into that public model. +- `--input-json -` reads from stdin and is forbidden when stdin is not available or when another prompt would be required. +- JSON input errors are `VALIDATION_FAILED` with Russian messages and no echoed secrets. + If a method cannot be safely exposed by the generic engine, add it to a typed exclusion list with reason, owner, and follow-up. Final acceptance target is zero unsupported sync Swagger-bound methods unless intentionally excluded and documented. ## Registry and Coverage @@ -410,7 +504,7 @@ Coverage report fields: Add a linter: ```bash -poetry run python scripts/lint_cli_coverage.py +poetry run python scripts/lint_cli_coverage.py --strict ``` The linter fails when: @@ -491,13 +585,24 @@ Stage policy: - After Stage 4, CLI coverage report changes must be intentional in every CLI metadata change. - After Stage 10, `scripts/lint_cli_coverage.py` is a required gate for all CLI changes. - Do not broaden command coverage before the previous stage's verification passes. +- Keep each stage small enough for review. If a stage needs more than roughly 300-500 lines of production code or touches more than three production modules, split it into lettered sub-stages in this file before implementing. +- A sub-stage has its own deliverables, tests, verification commands, and checked-off exit criteria. +- Do not mark a checklist item complete from inspection alone when a command or test can verify it. + +Coverage linter phase policy: + +- Stage 4 introduces `scripts/lint_cli_coverage.py` in report/partial mode. It must validate registry invariants that exist at that stage, but it must not require full all-domain command coverage yet. +- Stages 8-9 use the linter in read-coverage mode. +- Stage 10 switches the linter to strict mode and adds `make cli-lint` to `make check`. +- Strict mode fails on every missing sync Swagger-bound command unless there is a documented intentional exclusion. +- Linter output must be deterministic and sanitized so it can be committed as an audit artifact when needed. ### Stage 0: Baseline Audit Deliverables: - Record current sync Swagger binding count. -- Record current `AvitoClient` factory mapping count. +- Record current `AvitoClient` public callable count and sync binding factory metadata count. - Confirm which factory names exist in `AvitoClient` but not in bindings. - Confirm whether every sync binding has `factory` metadata. - Record public non-Swagger helpers and decide command vs exclusion. @@ -508,6 +613,7 @@ Verification: ```bash poetry run python -c "from avito.core.swagger_discovery import discover_swagger_bindings; print(len(discover_swagger_bindings().canonical_map))" +poetry run python -c "from avito.core.swagger_discovery import discover_swagger_bindings; d=discover_swagger_bindings(); print(len([b for b in d.bindings if b.variant == 'sync' and b.operation_key is not None and b.factory is None]))" poetry run pytest tests/core/test_swagger_linter.py tests/contracts/test_swagger_contracts.py ``` @@ -519,7 +625,7 @@ Exit criteria: Stage checklist: - [ ] Baseline command output is pasted into this plan or a linked implementation note. -- [ ] Sync binding count, factory-like method count, and missing factory metadata list are recorded. +- [ ] Sync binding count, public callable count, sync binding factory count, and missing factory metadata list are recorded. - [ ] The 4 auth-token bindings have an explicit planned treatment: exclusion, auth workflow, or SDK change. - [ ] Stage verification commands pass. @@ -528,6 +634,7 @@ Stage checklist: Deliverables: - Add `typer` dependency. +- Use Typer/Click test utilities only in tests; do not add a custom subprocess harness unless behavior specifically requires `python -m avito`. - Add `avito/cli/` package skeleton. - Add root `avito` app with typed global context. - Add `avito --help`, `avito --version`, `avito version`. @@ -555,6 +662,7 @@ Exit criteria: - Help/version commands do not touch network, config, or account files. - `python -m avito --help` and `avito --help` exercise the same app. +- Importing `avito.cli.app` has no filesystem side effects and does not construct `AvitoClient`. Stage checklist: @@ -686,7 +794,7 @@ Verification: ```bash poetry run pytest tests/cli/test_registry.py -poetry run python scripts/lint_cli_coverage.py +poetry run python scripts/lint_cli_coverage.py --phase registry poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -694,7 +802,8 @@ poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py Exit criteria: - Registry builds without creating `AvitoClient`. -- Registry tests fail if a new sync Swagger binding lacks a command or exclusion. +- Registry tests fail on duplicate records, invalid names, local/API collisions, or missing required exclusion metadata. +- Full missing-command failures are deferred to read/full coverage phases, not silently skipped. Stage checklist: @@ -753,7 +862,8 @@ Deliverables: - Invoke public SDK factory and method. - Map SDK exceptions to CLI errors. - Add explicit unsupported-method registry only for documented exclusions. -- Add a fake-client/fake-domain test seam for invocation tests without real HTTP. +- Add a typed client factory protocol for tests so invocation behavior can be verified without real HTTP. +- Production code must default to constructing `AvitoClient`; tests may inject a fake client through the protocol. Tests: @@ -780,6 +890,7 @@ Stage checklist: - [ ] API command invocation resolves profile/config before constructing `AvitoClient`. - [ ] `AvitoClient` is always used as a context manager. - [ ] Invocation calls factory method, then public domain method. +- [ ] Test-only fake clients are injected through typed protocols and are not imported by production CLI modules. - [ ] Tests prove operation specs and transport are not called directly by CLI code. - [ ] SDK exceptions map to documented CLI exit codes and sanitized messages. - [ ] Stage verification commands pass. @@ -792,7 +903,9 @@ Deliverables: - Serialize SDK models through `model_dump()` / `to_dict()`. - Serialize CLI-local dataclasses, enums, dates, datetimes, lists, and primitive values safely. - Handle `PaginatedList[T]` with documented bounded defaults. -- Add `--limit`, `--page-limit`, or `--all` only when needed to avoid unbounded materialization. +- Add `--limit`, `--page-limit`, and `--all` consistently for paginated commands when needed to avoid unbounded materialization. +- Default paginated output must be bounded. Conservative default: first page only or at most the SDK/default page size when the operation exposes a page size. +- `--all` must require an explicit opt-in and should show progress on stderr for long materialization. - Render default tables for collections and grouped output for single models. Tests: @@ -902,7 +1015,7 @@ Verification: ```bash poetry run pytest tests/cli/test_all_domains_metadata.py poetry run pytest tests/cli/test_domain_smoke_commands.py -poetry run python scripts/lint_cli_coverage.py +poetry run python scripts/lint_cli_coverage.py --phase read poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -949,7 +1062,7 @@ Verification: poetry run pytest tests/cli/test_write_safety.py poetry run pytest tests/cli/test_all_domains_metadata.py tests/cli/test_domain_smoke_commands.py poetry run pytest tests/domains/promotion tests/domains/orders -poetry run python scripts/lint_cli_coverage.py +poetry run python scripts/lint_cli_coverage.py --strict poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -960,6 +1073,12 @@ Exit criteria: - No destructive command can run accidentally in non-interactive mode. - `make cli-lint` can now be added to `make check`. +Makefile integration: + +- Add `cli-lint` as `poetry run python scripts/lint_cli_coverage.py --strict`. +- Include `cli-lint` in `quality` after `swagger-lint` and before `architecture-lint`. +- Do not add strict `cli-lint` to `make check` before Stage 10; earlier stages use explicit phase commands only. + Stage checklist: - [ ] Write/destructive classification is deterministic and tested. @@ -994,7 +1113,7 @@ Verification: ```bash poetry run pytest tests/cli/test_helper_workflows.py -poetry run python scripts/lint_cli_coverage.py +poetry run python scripts/lint_cli_coverage.py --strict poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -1050,30 +1169,70 @@ Stage checklist: Deliverables: -- README CLI quickstart. -- Docs how-to page for CLI account/profile setup. -- Docs reference for global flags, output formats, exit codes, config files, environment variables, and secret storage. -- Docs page explaining generated all-domain command grammar. -- Examples for human and JSON automation usage. -- Document that secrets are stored locally in plaintext JSON protected with `0600` permissions. -- Document sync Swagger-bound coverage guarantee and exclusion policy. -- Document command naming algorithm and compatibility alias policy. -- Document config precedence order, including reserved project/system config slots if they remain unimplemented. +- Update `README.md` with a short CLI quickstart and links to docs. +- Update `docs/site/index.md` so CLI is visible as a first-class usage mode. +- Update `docs/site/tutorials/getting-started.md` with the shortest first CLI call path or a concise link to the CLI how-to. +- Add `docs/site/how-to/cli.md` for practical account/profile setup and daily CLI workflows: + - install and verify `avito --version`; + - add/use/list/delete local accounts; + - run read-only API commands; + - run JSON automation commands with `--json --no-input`; + - use `status`, `doctor`, and completion commands; + - explain safe handling of local plaintext secrets. +- Add `docs/site/reference/cli.md` for stable CLI contracts: + - command grammar `avito `; + - global flags; + - output modes and stdout/stderr split; + - exit codes and stable error codes; + - config files, CLI home resolution, environment variables, and profile precedence; + - safety flags `--dry-run`, `--yes`, and `--confirm`; + - generated command naming algorithm and compatibility alias policy; + - sync Swagger-bound coverage guarantee and documented exclusion policy. +- Add `docs/site/explanations/cli-architecture.md` for design rationale: + - CLI as a thin wrapper over `AvitoClient`; + - registry/discovery-driven command generation; + - coverage linter phases and strict gate; + - auth-token binding exclusions; + - secret masking and no raw SDK internals in CLI; + - bounded pagination and `--all` policy. +- Update `docs/site/how-to/auth-and-config.md` with a short cross-link to CLI account/profile setup. +- Update `docs/site/explanations/security-and-redaction.md` with CLI secret-storage and output-redaction notes. +- Update `docs/site/explanations/api-coverage-and-deprecations.md` with CLI coverage and exclusion policy. +- Update navigation files: + - add `cli.md` to `docs/site/how-to/.pages`; + - add `cli.md` to `docs/site/reference/.pages`; + - add `cli-architecture.md` to `docs/site/explanations/.pages`. +- Do not add generated CLI reference pages to `docs/site/assets/_gen_reference.py` unless the implementation has a stable CLI registry JSON/report that can be generated deterministically during MkDocs builds. +- If CLI docs mention commands that are not implemented yet, keep them in this plan only. Public docs must describe only implemented commands by the time Stage 13 is complete. + +Documentation style requirements: + +- Keep docs in Russian, with command names/flags/error codes unchanged. +- Keep how-to pages task-oriented; do not duplicate the entire reference contract there. +- Keep reference pages exhaustive and stable; avoid marketing language. +- Link to existing config, security, pagination, and API coverage pages instead of copying large sections. +- Include both human output and JSON automation examples. +- Never show real-looking secrets; examples must use placeholders such as `client-secret`. Verification: ```bash poetry run mkdocs build --strict make docs-check +rg -n "client_secret|access_token|Authorization: Bearer|api_key" README.md docs/site ``` Stage checklist: - [ ] README includes a CLI quickstart. -- [ ] Docs explain account/profile setup and local plaintext secret storage. -- [ ] Docs list global flags, output modes, exit codes, config files, and environment variables. -- [ ] Docs explain generated command naming and alias policy. -- [ ] Docs state sync Swagger-bound coverage guarantees and exclusion policy. +- [ ] `docs/site/index.md` links to the CLI docs. +- [ ] `docs/site/tutorials/getting-started.md` has a first CLI path or a clear CLI how-to link. +- [ ] `docs/site/how-to/cli.md` explains account/profile setup, daily workflows, automation, status/doctor, completion, and local plaintext secret storage. +- [ ] `docs/site/reference/cli.md` lists global flags, output modes, exit codes, config files, environment variables, safety flags, command grammar, naming, alias, and coverage contracts. +- [ ] `docs/site/explanations/cli-architecture.md` explains SDK reuse, registry/discovery, coverage linter phases, exclusions, secret masking, and pagination policy. +- [ ] Existing auth/config, security/redaction, and API coverage/deprecation pages link to or describe relevant CLI behavior. +- [ ] `.pages` navigation files include the new CLI pages in the correct sections. +- [ ] Docs examples do not contain real-looking secrets or bearer tokens. - [ ] Stage verification commands pass. ### Stage 14: Final Gate @@ -1087,7 +1246,7 @@ poetry run mypy avito poetry run ruff check . poetry run python scripts/lint_python_guidelines.py poetry run python scripts/lint_architecture.py -poetry run python scripts/lint_cli_coverage.py +poetry run python scripts/lint_cli_coverage.py --strict poetry build make check ``` @@ -1147,8 +1306,9 @@ Stage checklist: - [ ] Minimum stage verification commands pass during implementation. - [ ] Final `make check` passes before completion. -## Open Decisions +## Resolved Defaults -- Whether `avito cli coverage` should be public or hidden. The linter/report is required either way. -- Whether paginated commands should default to first page, bounded page count, or require explicit `--all`. Conservative default: bounded output with explicit opt-in for full materialization. -- Whether generated API commands should support custom positional primary IDs after the first release. Conservative default: named flags only. +- `avito cli coverage` is hidden/internal for the first release. The supported public surface is the script `scripts/lint_cli_coverage.py --strict` and documented coverage guarantees. +- Paginated commands default to bounded output: first page only or the SDK/default page size when applicable. Full materialization requires explicit `--all`. +- Generated API commands use named flags only in the first release. Positional primary IDs can be added later as additive aliases after command stability is proven. +- The 4 auth-token Swagger bindings are documented intentional exclusions for the first release and are represented by local account/status/doctor workflows instead of direct token-client commands. From 3c142a76d04689758e95575da905193a6e416a8e Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sun, 10 May 2026 12:27:06 +0300 Subject: [PATCH 09/38] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BD=D0=B0=D0=B4=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.md | 136 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 121 insertions(+), 15 deletions(-) diff --git a/todo.md b/todo.md index b47a21d..c44b6b2 100644 --- a/todo.md +++ b/todo.md @@ -92,6 +92,7 @@ Hard constraints: - Keep SDK core/domain/transport/auth layers free of Typer and CLI behavior. - Production CLI code must not import domain `operations.py`, transport implementations, auth provider internals, or testing fake transports. - Production CLI code must not import from `tests`, `avito.testing`, `tests/fake_transport.py`, or `avito.core.operations`. +- Production CLI code must not import private SDK modules or private names unless the import is explicitly documented as a CLI-only compatibility exception in this plan and covered by an architecture lint rule. - API commands must not call `OperationSpec`, `OperationExecutor`, `Transport`, or `AuthProvider` directly. - API commands must not instantiate domain objects directly. - Do not duplicate Swagger contract data in CLI metadata. CLI metadata may store command names, examples, aliases, safety policy, output hints, and documented exclusions only. @@ -99,6 +100,8 @@ Hard constraints: - Human-facing CLI text is Russian only: help descriptions, prompts, warnings, errors, and success output. Stable error codes remain uppercase English identifiers. - No `setattr`, `globals()`, monkey-patching, generated Python source, or dynamic SDK method injection. Deterministic Typer registration from typed registry records is allowed. - No dead code, unused aliases, unused `TypeVar`s, broad `Any`, or layer mixing. +- No dynamic imports for optional CLI dependencies. Runtime dependency failures must fail at import/install time and be fixed in `pyproject.toml`. +- No broad `except Exception` in CLI command flow unless the handler sanitizes output and immediately re-raises or converts to a typed `CliError`. Non-goals for the first complete release: @@ -205,6 +208,18 @@ Navigation updates required: - Add `cli-architecture.md` to `docs/site/explanations/.pages`. - If README/index/tutorial links are added before the CLI is usable, mark them clearly as planned only. Prefer adding public-facing docs after Stage 12 when commands exist. +## Plan Review Findings + +Additional findings from reviewing this plan against `.ai/STYLEGUIDE.md`, `.ai/cli-guidelines.md`, and `.ai/python-guidelines.md`: + +- The plan must treat CLI commands as public contracts. Renames, output schema changes, exit-code changes, and flag removals need deprecation, not silent replacement. +- The CLI must have static architecture enforcement, not only review discipline. Import boundaries for `avito/cli/` must be covered by `scripts/lint_architecture.py` or a dedicated CLI architecture linter before broad command generation starts. +- Python guideline compliance must be part of every stage that changes Python code. `ruff` and `mypy` are necessary but not sufficient. +- The write-command rollout is too large as a single stage. It is split into safety primitives, domain coverage waves, and strict coverage gate so each increment remains reviewable and testable. +- Coverage must distinguish three statuses: implemented canonical command, documented temporary exclusion, and documented intentional permanent exclusion. Temporary exclusions require an owner/follow-up and must fail after the configured target stage if still present. +- Generated command registration must be deterministic and snapshot-testable without constructing `AvitoClient`, reading account files, or touching the network. +- Public docs must only describe implemented commands. Future commands stay in this plan until the implementation exists. + ## CLI Architecture Use a small hand-written CLI shell plus registry/discovery-driven API commands: @@ -583,17 +598,22 @@ Stage policy: - Each stage must leave the branch in a releasable state. - Every behavior stage includes tests in the same change. - After Stage 4, CLI coverage report changes must be intentional in every CLI metadata change. -- After Stage 10, `scripts/lint_cli_coverage.py` is a required gate for all CLI changes. +- After Stage 10C, `scripts/lint_cli_coverage.py --strict` is a required gate for all CLI changes. - Do not broaden command coverage before the previous stage's verification passes. - Keep each stage small enough for review. If a stage needs more than roughly 300-500 lines of production code or touches more than three production modules, split it into lettered sub-stages in this file before implementing. - A sub-stage has its own deliverables, tests, verification commands, and checked-off exit criteria. - Do not mark a checklist item complete from inspection alone when a command or test can verify it. +- Every stage that changes Python code must run `poetry run python scripts/lint_python_guidelines.py`. +- Every stage that adds or changes CLI production imports must run `poetry run python scripts/lint_architecture.py` or the dedicated CLI architecture lint command introduced by that stage. +- Every stage that changes command metadata must run the current `scripts/lint_cli_coverage.py` phase, even before strict mode is enabled. +- Every stage that changes persisted config/account JSON shape must include migration/backward-compatibility tests or explicitly state why no existing persisted shape exists yet. +- Every stage that changes user-visible CLI text, flags, output fields, or exit codes must update `docs/site/reference/cli.md` once that reference page exists. Coverage linter phase policy: - Stage 4 introduces `scripts/lint_cli_coverage.py` in report/partial mode. It must validate registry invariants that exist at that stage, but it must not require full all-domain command coverage yet. - Stages 8-9 use the linter in read-coverage mode. -- Stage 10 switches the linter to strict mode and adds `make cli-lint` to `make check`. +- Stage 10C switches the linter to strict mode and adds `make cli-lint` to `make check`. - Strict mode fails on every missing sync Swagger-bound command unless there is a documented intentional exclusion. - Linter output must be deterministic and sanitized so it can be committed as an audit artifact when needed. @@ -779,6 +799,7 @@ Deliverables: - Add deterministic collision detection for `resource action`. - Add exclusion record type. - Add registry/coverage JSON report command or hidden internal report. +- Extend `scripts/lint_architecture.py` or add a dedicated CLI architecture lint rule that forbids production `avito/cli/` imports from `tests`, `avito.testing`, domain operation modules, transport implementations, auth provider internals, and `avito.core.operations`. Tests: @@ -795,6 +816,8 @@ Verification: ```bash poetry run pytest tests/cli/test_registry.py poetry run python scripts/lint_cli_coverage.py --phase registry +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -812,6 +835,7 @@ Stage checklist: - [ ] Canonical API commands map one-to-one to sync Swagger bindings. - [ ] Local/API command collisions fail during registry construction. - [ ] `scripts/lint_cli_coverage.py` exists and exercises the registry. +- [ ] CLI production import boundaries are statically checked. - [ ] Stage verification commands pass. ### Stage 5: Generic Input Coercion @@ -1034,7 +1058,7 @@ Stage checklist: - [ ] Coverage linter distinguishes read coverage from pending write coverage. - [ ] Stage verification commands pass. -### Stage 10: Write Commands, Safety, and Dry Run +### Stage 10A: Write Safety Primitives Deliverables: @@ -1043,8 +1067,8 @@ Deliverables: - Support `--dry-run` only when the SDK public method already supports `dry_run` or when CLI can safely preview without changing SDK behavior. - Do not fake dry-run for SDK methods that would still execute transport. - Ensure write commands build the same SDK call in dry-run and apply modes where `dry_run` exists. -- Register generated commands for remaining write sync Swagger-bound methods. -- Eliminate or document every unsupported sync binding. +- Add write/destructive command metadata fields without broadening all-domain write coverage yet. +- Add safety help text and examples for commands that can modify state or trigger expensive operations. Tests: @@ -1053,16 +1077,103 @@ Tests: - `--yes` and `--confirm` behave deterministically; - dry-run methods do not call transport when SDK contract says they should not; - non-dry-run write commands call transport exactly once; -- one write smoke invocation per write-capable domain with fake transport; -- coverage test fails on missing write commands. +- safety metadata cannot be absent for write/destructive/expensive records. Verification: +```bash +poetry run pytest tests/cli/test_write_safety.py +poetry run python scripts/lint_cli_coverage.py --phase write-safety +poetry run python scripts/lint_python_guidelines.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py +``` + +Exit criteria: + +- No destructive command can run accidentally in non-interactive mode. +- `--dry-run` is exposed only where the SDK method can actually avoid transport or apply mode can be proven equivalent by tests. + +Stage checklist: + +- [ ] Write/destructive/expensive classification is deterministic and tested. +- [ ] Destructive commands require prompt, `--yes`, or exact `--confirm`. +- [ ] `--no-input` never hangs and fails safely when confirmation is required. +- [ ] `--dry-run` is exposed only for SDK methods that safely support it. +- [ ] Safety behavior is reflected in command help. +- [ ] Stage verification commands pass. + +### Stage 10B: Write Command Coverage by Domain Waves + +Deliverables: + +- Register generated commands for remaining write sync Swagger-bound methods in small domain waves. +- Use these waves unless actual binding counts show a better split: + - Wave 1: low-count domains and isolated writes: `ratings`, `realty`, `tariffs`, `accounts`. + - Wave 2: medium domains: `ads`, `cpa`, `jobs`, `messenger`. + - Wave 3: large/high-risk domains: `orders`, `promotion`, `autoteka`. +- Each wave must update command metadata, smoke tests, exclusions, and coverage report together. +- Eliminate or document every unsupported sync binding in the wave before moving to the next wave. +- Temporary exclusions are allowed only inside a wave and must include owner, reason, target stage, and follow-up. + +Tests: + +- one write smoke invocation per write-capable domain in the current wave with fake transport; +- coverage test fails on missing write commands for completed waves; +- command metadata assertions cover every write sync binding in completed waves; +- safety tests run for at least one destructive or expensive command when the wave contains one. + +Verification for each wave: + ```bash poetry run pytest tests/cli/test_write_safety.py poetry run pytest tests/cli/test_all_domains_metadata.py tests/cli/test_domain_smoke_commands.py -poetry run pytest tests/domains/promotion tests/domains/orders +poetry run python scripts/lint_cli_coverage.py --phase write --domain +poetry run python scripts/lint_python_guidelines.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py +``` + +Exit criteria: + +- Every write sync binding in completed waves has a canonical command or explicit temporary/intentional exclusion. +- Domain smoke tests use fake transport only and make no real network calls. +- No broad write coverage change lands without matching tests. + +Stage checklist: + +- [ ] Wave 1 write commands are covered or explicitly excluded. +- [ ] Wave 2 write commands are covered or explicitly excluded. +- [ ] Wave 3 write commands are covered or explicitly excluded. +- [ ] Every completed wave has fake-transport smoke tests. +- [ ] Temporary exclusions have owner, reason, target stage, and follow-up. +- [ ] Stage verification commands pass for each wave. + +### Stage 10C: Strict CLI Coverage Gate + +Deliverables: + +- Switch `scripts/lint_cli_coverage.py --strict` to fail on every missing sync Swagger-bound command unless it has a documented intentional exclusion. +- Fail strict mode on expired temporary exclusions. +- Add `make cli-lint` and include it in `quality` after `swagger-lint` and before `architecture-lint`. +- Ensure the strict report is deterministic, sanitized, and suitable for CI output. + +Tests: + +- strict linter fails on a missing binding; +- strict linter fails on duplicate canonical commands for one binding; +- strict linter fails on a canonical API command without a binding; +- strict linter fails on expired temporary exclusions; +- strict linter passes with only implemented commands and intentional exclusions. + +Verification: + +```bash +poetry run pytest tests/cli/test_all_domains_metadata.py tests/cli/test_domain_smoke_commands.py poetry run python scripts/lint_cli_coverage.py --strict +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py +make cli-lint poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -1070,21 +1181,16 @@ poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py Exit criteria: - Every sync discovered Swagger binding has exactly one canonical CLI command or documented intentional exclusion. -- No destructive command can run accidentally in non-interactive mode. -- `make cli-lint` can now be added to `make check`. +- `make cli-lint` is part of `make check` through `quality`. Makefile integration: - Add `cli-lint` as `poetry run python scripts/lint_cli_coverage.py --strict`. - Include `cli-lint` in `quality` after `swagger-lint` and before `architecture-lint`. -- Do not add strict `cli-lint` to `make check` before Stage 10; earlier stages use explicit phase commands only. +- Do not add strict `cli-lint` to `make check` before Stage 10C; earlier stages use explicit phase commands only. Stage checklist: -- [ ] Write/destructive classification is deterministic and tested. -- [ ] Destructive commands require prompt, `--yes`, or exact `--confirm`. -- [ ] `--no-input` never hangs and fails safely when confirmation is required. -- [ ] `--dry-run` is exposed only for SDK methods that safely support it. - [ ] Remaining sync Swagger bindings are covered or intentionally excluded. - [ ] `make cli-lint` is added to `make check`. - [ ] Stage verification commands pass. From abae6a3b9646830a3b3fef5372ef805cf4d57c57 Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sun, 10 May 2026 12:34:21 +0300 Subject: [PATCH 10/38] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BD=D0=B0=D0=B4=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.md | 261 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 199 insertions(+), 62 deletions(-) diff --git a/todo.md b/todo.md index c44b6b2..1868bde 100644 --- a/todo.md +++ b/todo.md @@ -112,30 +112,87 @@ Non-goals for the first complete release: ## Current Baseline Findings -Recorded on 2026-05-10 while preparing this plan: +Recorded and re-verified on 2026-05-10 while preparing this plan: ```text sync Swagger bindings: 204 +sync Swagger canonical map entries: 204 AvitoClient public callable methods, excluding close/from_env/auth/debug_info: 56 sync Swagger binding factories with factory metadata: 48 sync bindings without factory metadata: 4 ``` -Sync binding count by Swagger domain: +Reproducible verification commands: + +```bash +poetry run python -c "from avito.core.swagger_discovery import discover_swagger_bindings; d=discover_swagger_bindings(); sync=[b for b in d.bindings if b.variant == 'sync' and b.operation_key is not None]; print(len(sync)); print(len(d.canonical_map)); print(len([b for b in sync if b.factory is None]))" +poetry run python -c "import inspect; from avito.client import AvitoClient; excluded={'close','from_env','auth','debug_info'}; print(len([name for name, value in inspect.getmembers(AvitoClient) if not name.startswith('_') and callable(value) and name not in excluded]))" +poetry run pytest tests/core/test_swagger_linter.py tests/contracts/test_swagger_contracts.py +``` + +Verification result: ```text -accounts: 8 -ads: 28 -auth: 4 -autoteka: 26 -cpa: 14 -jobs: 25 -messenger: 18 -orders: 45 -promotion: 24 -ratings: 4 -realty: 7 -tariffs: 1 +tests/core/test_swagger_linter.py tests/contracts/test_swagger_contracts.py: +1913 passed +``` + +Do not use Swagger tag/domain labels as canonical CLI coverage buckets. Current +Swagger labels are human-facing and may be localized. CLI coverage and wave +planning must use discovered `factory` metadata as the stable grouping key. + +Sync binding count by discovered factory: + +```text +: 4 +account: 3 +account_hierarchy: 5 +ad: 3 +ad_promotion: 4 +ad_stats: 4 +application: 5 +autoload_archive: 4 +autoload_profile: 5 +autoload_report: 8 +autostrategy_campaign: 7 +autoteka_monitoring: 4 +autoteka_report: 7 +autoteka_scoring: 2 +autoteka_valuation: 1 +autoteka_vehicle: 12 +bbip_promotion: 3 +call_tracking_call: 3 +chat: 4 +chat_media: 2 +chat_message: 4 +chat_webhook: 3 +cpa_archive: 3 +cpa_auction: 2 +cpa_call: 2 +cpa_chat: 4 +cpa_lead: 2 +delivery_order: 5 +delivery_task: 1 +job_dictionary: 2 +job_webhook: 4 +order: 9 +order_label: 3 +promotion_order: 4 +rating_profile: 1 +realty_analytics_report: 2 +realty_booking: 2 +realty_listing: 2 +realty_pricing: 1 +resume: 3 +review: 1 +review_answer: 2 +sandbox_delivery: 25 +special_offer_campaign: 5 +stock: 2 +target_action_pricing: 5 +tariff: 1 +trx_promotion: 3 +vacancy: 11 ``` Bindings without factory metadata: @@ -214,12 +271,41 @@ Additional findings from reviewing this plan against `.ai/STYLEGUIDE.md`, `.ai/c - The plan must treat CLI commands as public contracts. Renames, output schema changes, exit-code changes, and flag removals need deprecation, not silent replacement. - The CLI must have static architecture enforcement, not only review discipline. Import boundaries for `avito/cli/` must be covered by `scripts/lint_architecture.py` or a dedicated CLI architecture linter before broad command generation starts. +- CLI coverage grouping must be based on discovered `factory` metadata, not localized Swagger tag/domain labels. Tags may be useful in reports, but they are not stable enough to drive command coverage gates. - Python guideline compliance must be part of every stage that changes Python code. `ruff` and `mypy` are necessary but not sufficient. - The write-command rollout is too large as a single stage. It is split into safety primitives, domain coverage waves, and strict coverage gate so each increment remains reviewable and testable. - Coverage must distinguish three statuses: implemented canonical command, documented temporary exclusion, and documented intentional permanent exclusion. Temporary exclusions require an owner/follow-up and must fail after the configured target stage if still present. -- Generated command registration must be deterministic and snapshot-testable without constructing `AvitoClient`, reading account files, or touching the network. +- Generated command registration must be deterministic and inspectable by the CLI coverage linter without constructing `AvitoClient`, reading account files, or touching the network. - Public docs must only describe implemented commands. Future commands stay in this plan until the implementation exists. +## Test and Lint Boundaries + +`.ai/STYLEGUIDE.md` has a closed testing policy. CLI work must follow it from +Stage 1 instead of using pytest as a general policy checker. + +Use pytest only for runtime behavior that a user or integration can observe: + +- CLI command execution, exit codes, stdout/stderr routing, and output formats; +- profile/account/config persistence behavior through temporary directories; +- secret masking on success, error, verbose, debug, and JSON paths; +- generic invocation through public `AvitoClient` factories and public domain methods; +- fake-transport API smoke flows with request/response behavior; +- pagination materialization behavior and dry-run transport behavior. + +Use linters/scripts, not pytest, for static or inventory checks: + +- architecture/import boundaries for `avito/cli/`; +- generated command naming, kebab-case resources/actions/flags, and forbidden `resource-id`; +- duplicate canonical commands, local/API collisions, alias policy, and exclusion metadata completeness; +- coverage inventory: missing bindings, extra commands, expired temporary exclusions, and strict one-to-one mapping; +- report determinism and sanitized CLI coverage report content. + +Do not add pytest tests whose only purpose is to exercise the CLI coverage linter +with synthetic broken inputs. The linter is verified by running it against the +real repository in each stage gate. If a linter rule needs implementation-level +confidence, keep its parser/checker simple and cover it through deterministic +real-code fixtures or move the check into an existing static lint script. + ## CLI Architecture Use a small hand-written CLI shell plus registry/discovery-driven API commands: @@ -625,6 +711,7 @@ Deliverables: - Record current `AvitoClient` public callable count and sync binding factory metadata count. - Confirm which factory names exist in `AvitoClient` but not in bindings. - Confirm whether every sync binding has `factory` metadata. +- Record sync binding counts grouped by discovered `factory`; do not use localized Swagger tags as the canonical CLI coverage grouping. - Record public non-Swagger helpers and decide command vs exclusion. - Record current `python -m avito` behavior and mark it for replacement. - Record existing `Makefile` gates that CLI work must integrate with. @@ -646,6 +733,7 @@ Stage checklist: - [ ] Baseline command output is pasted into this plan or a linked implementation note. - [ ] Sync binding count, public callable count, sync binding factory count, and missing factory metadata list are recorded. +- [ ] Factory-grouped binding counts are recorded and selected as the coverage wave planning basis. - [ ] The 4 auth-token bindings have an explicit planned treatment: exclusion, auth workflow, or SDK change. - [ ] Stage verification commands pass. @@ -673,6 +761,8 @@ Verification: ```bash poetry run pytest tests/cli/test_app.py +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli poetry build @@ -717,6 +807,8 @@ Verification: ```bash poetry run pytest tests/cli/test_errors.py tests/cli/test_ui.py +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` @@ -768,6 +860,8 @@ Verification: ```bash poetry run pytest tests/cli/test_config.py tests/cli/test_accounts.py +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` @@ -803,13 +897,15 @@ Deliverables: Tests: -- registry includes all sync discovered bindings; -- registry accounts for helpers separately from API bindings; -- resource/action names are kebab-case; -- every command maps to exactly one binding; -- no duplicate canonical commands; -- aliases do not affect canonical coverage; -- local/API collisions fail. +- registry can be built without constructing `AvitoClient`, reading account files, or touching the network; +- local helper command metadata is visible to help/registration code separately from API command metadata; +- aliases delegate to canonical command records at runtime and do not produce duplicate callbacks. + +Static lint responsibilities introduced in this stage: + +- `scripts/lint_cli_coverage.py --phase registry` verifies that the registry includes all sync discovered bindings in report mode; +- the same phase verifies kebab-case resource/action names, duplicate canonical commands, one-to-one binding ownership, alias policy, local/API collisions, forbidden `resource-id`, and required exclusion metadata; +- `scripts/lint_architecture.py` verifies CLI production import boundaries. Verification: @@ -825,7 +921,8 @@ poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py Exit criteria: - Registry builds without creating `AvitoClient`. -- Registry tests fail on duplicate records, invalid names, local/API collisions, or missing required exclusion metadata. +- Registry pytest tests cover runtime behavior only. +- CLI coverage and architecture linters fail on duplicate records, invalid names, local/API collisions, forbidden imports, or missing required exclusion metadata. - Full missing-command failures are deferred to read/full coverage phases, not silently skipped. Stage checklist: @@ -853,13 +950,20 @@ Tests: - coercion for primitives, bools, dates, datetimes, enums, optionals, and lists; - missing required values fail without prompt in `--no-input`; - invalid values produce `VALIDATION_FAILED`; -- kebab-case flag generation is stable; -- `resource-id` is rejected. +- supported repeated flags and comma-separated values coerce to the same typed list result. + +Static lint responsibilities: + +- generated flag names are lowercase kebab-case; +- generated flags never expose `--resource-id`. Verification: ```bash poetry run pytest tests/cli/test_schemas.py +poetry run python scripts/lint_cli_coverage.py --phase registry +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` @@ -874,7 +978,7 @@ Stage checklist: - [ ] Primitive, bool, date, datetime, enum, optional, and list coercion are tested. - [ ] Repeated flags and documented comma-separated values behave consistently. - [ ] Invalid values produce Russian `VALIDATION_FAILED` errors. -- [ ] Generated flag names are kebab-case and never `--resource-id`. +- [ ] Generated flag names are checked by the CLI coverage linter for kebab-case and absence of `--resource-id`. - [ ] Stage verification commands pass. ### Stage 6: Generic Invocation Engine @@ -894,13 +998,18 @@ Tests: - active profile is used by default; - `--profile` overrides active profile; - CLI invokes expected factory and public method with expected arguments; -- CLI never calls operation specs or transport directly; - SDK `AuthenticationError`, `AuthorizationError`, `ValidationError`, `ConflictError`, and not-found equivalents map to documented exit codes. +Static lint responsibilities: + +- CLI production code does not import or call operation specs, operation executor, transport implementations, auth provider internals, or testing fake transports. + Verification: ```bash poetry run pytest tests/cli/test_commands.py +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` @@ -915,7 +1024,7 @@ Stage checklist: - [ ] `AvitoClient` is always used as a context manager. - [ ] Invocation calls factory method, then public domain method. - [ ] Test-only fake clients are injected through typed protocols and are not imported by production CLI modules. -- [ ] Tests prove operation specs and transport are not called directly by CLI code. +- [ ] Architecture lint proves operation specs and transport are not called directly by CLI production code. - [ ] SDK exceptions map to documented CLI exit codes and sanitized messages. - [ ] Stage verification commands pass. @@ -944,6 +1053,8 @@ Verification: ```bash poetry run pytest tests/cli/test_serialization.py +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` @@ -986,6 +1097,9 @@ Verification: ```bash poetry run pytest tests/cli/test_account_api_commands.py poetry run pytest tests/contracts/test_swagger_contracts.py +poetry run python scripts/lint_cli_coverage.py --phase read +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` @@ -1013,33 +1127,33 @@ Deliverables: - Add command metadata for methods needing custom list/file/enum parsing. - Document every temporarily unsupported read-only sync binding. -Required domains: +Required coverage groups: -- `accounts` -- `ads` -- `autoteka` -- `cpa` -- `jobs` -- `messenger` -- `orders` -- `promotion` -- `ratings` -- `realty` -- `tariffs` +- Use discovered `factory` names as the canonical grouping key. +- Keep smoke-test grouping human-sized by clustering related factories only for + test organization, not for coverage accounting. +- Every factory that owns at least one read-only sync binding must have either a + smoke invocation in this stage or an explicit temporary exclusion with follow-up. Tests: -- one read-only smoke invocation per domain with fake transport; -- one metadata assertion per discovered read-only sync binding; -- coverage test fails on missing read-only commands; -- no generated command exposes forbidden names or secret fields. +- one read-only smoke invocation per completed factory group with fake transport; +- human and JSON output for representative object and collection commands; +- fake-transport behavior proves no real network calls are made. + +Static lint responsibilities: + +- every discovered read-only sync binding has a canonical command or explicit temporary exclusion; +- generated read-only commands do not expose forbidden names or secret fields; +- local/API command collisions and alias policy remain valid. Verification: ```bash -poetry run pytest tests/cli/test_all_domains_metadata.py poetry run pytest tests/cli/test_domain_smoke_commands.py poetry run python scripts/lint_cli_coverage.py --phase read +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -1051,8 +1165,8 @@ Exit criteria: Stage checklist: -- [ ] Every required domain has at least one read-only smoke command test. -- [ ] Metadata tests cover every discovered read-only sync binding. +- [ ] Every completed factory group has at least one read-only smoke command test. +- [ ] CLI coverage linter covers every discovered read-only sync binding. - [ ] Unsupported read-only bindings have explicit temporary exclusions with follow-up. - [ ] Domain/resource help exists for generated read-only commands. - [ ] Coverage linter distinguishes read coverage from pending write coverage. @@ -1076,8 +1190,12 @@ Tests: - `--no-input` fails instead of prompting; - `--yes` and `--confirm` behave deterministically; - dry-run methods do not call transport when SDK contract says they should not; -- non-dry-run write commands call transport exactly once; -- safety metadata cannot be absent for write/destructive/expensive records. +- non-dry-run write commands call transport exactly once. + +Static lint responsibilities: + +- safety metadata cannot be absent for write/destructive/expensive records; +- destructive/expensive command help includes required safety flags and examples. Verification: @@ -1085,6 +1203,7 @@ Verification: poetry run pytest tests/cli/test_write_safety.py poetry run python scripts/lint_cli_coverage.py --phase write-safety poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -1108,28 +1227,35 @@ Stage checklist: Deliverables: - Register generated commands for remaining write sync Swagger-bound methods in small domain waves. -- Use these waves unless actual binding counts show a better split: - - Wave 1: low-count domains and isolated writes: `ratings`, `realty`, `tariffs`, `accounts`. - - Wave 2: medium domains: `ads`, `cpa`, `jobs`, `messenger`. - - Wave 3: large/high-risk domains: `orders`, `promotion`, `autoteka`. +- Use discovered `factory` names as the wave unit. Suggested waves, based on the 2026-05-10 baseline: + - Wave 1: low-count/low-risk factories: `rating_profile`, `review`, `review_answer`, `realty_analytics_report`, `realty_booking`, `realty_listing`, `realty_pricing`, `tariff`, `account`, `account_hierarchy`. + - Wave 2: medium factories: `ad`, `ad_promotion`, `ad_stats`, `cpa_archive`, `cpa_auction`, `cpa_call`, `cpa_chat`, `cpa_lead`, `chat`, `chat_media`, `chat_message`, `chat_webhook`, `special_offer_campaign`. + - Wave 3: jobs and autoload factories: `application`, `resume`, `vacancy`, `job_dictionary`, `job_webhook`, `autoload_archive`, `autoload_profile`, `autoload_report`. + - Wave 4: large/high-risk commerce and promotion factories: `order`, `order_label`, `delivery_order`, `delivery_task`, `sandbox_delivery`, `stock`, `promotion_order`, `autostrategy_campaign`, `bbip_promotion`, `trx_promotion`, `target_action_pricing`. + - Wave 5: Autoteka factories: `autoteka_vehicle`, `autoteka_report`, `autoteka_monitoring`, `autoteka_scoring`, `autoteka_valuation`. - Each wave must update command metadata, smoke tests, exclusions, and coverage report together. - Eliminate or document every unsupported sync binding in the wave before moving to the next wave. - Temporary exclusions are allowed only inside a wave and must include owner, reason, target stage, and follow-up. Tests: -- one write smoke invocation per write-capable domain in the current wave with fake transport; -- coverage test fails on missing write commands for completed waves; -- command metadata assertions cover every write sync binding in completed waves; +- one write smoke invocation per write-capable factory group in the current wave with fake transport; - safety tests run for at least one destructive or expensive command when the wave contains one. +Static lint responsibilities: + +- coverage linter fails on missing write commands for completed waves; +- coverage linter covers every write sync binding in completed waves; +- coverage linter verifies command naming, alias policy, and exclusion metadata for completed waves. + Verification for each wave: ```bash poetry run pytest tests/cli/test_write_safety.py -poetry run pytest tests/cli/test_all_domains_metadata.py tests/cli/test_domain_smoke_commands.py +poetry run pytest tests/cli/test_domain_smoke_commands.py poetry run python scripts/lint_cli_coverage.py --phase write --domain poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -1145,6 +1271,8 @@ Stage checklist: - [ ] Wave 1 write commands are covered or explicitly excluded. - [ ] Wave 2 write commands are covered or explicitly excluded. - [ ] Wave 3 write commands are covered or explicitly excluded. +- [ ] Wave 4 write commands are covered or explicitly excluded. +- [ ] Wave 5 write commands are covered or explicitly excluded. - [ ] Every completed wave has fake-transport smoke tests. - [ ] Temporary exclusions have owner, reason, target stage, and follow-up. - [ ] Stage verification commands pass for each wave. @@ -1160,16 +1288,21 @@ Deliverables: Tests: -- strict linter fails on a missing binding; -- strict linter fails on duplicate canonical commands for one binding; -- strict linter fails on a canonical API command without a binding; -- strict linter fails on expired temporary exclusions; +- smoke command suite still passes for every completed factory group; +- representative strict-covered commands still run through fake transport with human and JSON output. + +Static lint responsibilities: + +- strict linter enforces that the real registry has no missing sync binding without an intentional exclusion; +- strict linter enforces that the real registry has no duplicate canonical command for one binding; +- strict linter enforces that the real registry has no canonical API command without a binding; +- strict linter enforces that the real registry has no expired temporary exclusions; - strict linter passes with only implemented commands and intentional exclusions. Verification: ```bash -poetry run pytest tests/cli/test_all_domains_metadata.py tests/cli/test_domain_smoke_commands.py +poetry run pytest tests/cli/test_domain_smoke_commands.py poetry run python scripts/lint_cli_coverage.py --strict poetry run python scripts/lint_python_guidelines.py poetry run python scripts/lint_architecture.py @@ -1220,6 +1353,8 @@ Verification: ```bash poetry run pytest tests/cli/test_helper_workflows.py poetry run python scripts/lint_cli_coverage.py --strict +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -1258,6 +1393,8 @@ Verification: ```bash poetry run pytest tests/cli/test_config_commands.py tests/cli/test_status_doctor.py tests/cli/test_completion.py +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli ``` From 070869d9e0ecb9a243e31aa0a6c189099520ce9d Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sun, 10 May 2026 13:12:17 +0300 Subject: [PATCH 11/38] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BD=D0=B0=D0=B4=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.md | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/todo.md b/todo.md index 1868bde..3b88521 100644 --- a/todo.md +++ b/todo.md @@ -277,6 +277,12 @@ Additional findings from reviewing this plan against `.ai/STYLEGUIDE.md`, `.ai/c - Coverage must distinguish three statuses: implemented canonical command, documented temporary exclusion, and documented intentional permanent exclusion. Temporary exclusions require an owner/follow-up and must fail after the configured target stage if still present. - Generated command registration must be deterministic and inspectable by the CLI coverage linter without constructing `AvitoClient`, reading account files, or touching the network. - Public docs must only describe implemented commands. Future commands stay in this plan until the implementation exists. +- The console entry point must be a stable callable, not the Typer application object itself. Use `avito.cli.app:main` for packaging and keep `app` as the reusable Typer application instance for tests and `python -m avito`. +- Root-level global options are the canonical syntax for the first release: `avito --profile main account get-self`. Supporting trailing global options such as `avito account get-self --profile main` is optional and must be implemented deliberately, not assumed from Typer/Click behavior. +- The generated API command layer should prefer deterministic Click command objects attached to the Typer root app when runtime-built parameters become too rigid for Typer's signature model. This keeps registration inspectable without generating Python source. +- Safety classification cannot rely on HTTP method alone. HTTP method may provide a default, but final command safety must come from explicit registry metadata and reviewed overrides for write, destructive, expensive, and local commands. +- CLI import-boundary checks should extend the existing `scripts/lint_architecture.py` unless there is a concrete reason to split them into a dedicated script. Avoid two overlapping architecture linters. +- The stable CLI contract should be documented as soon as the corresponding surface exists. Create or update `docs/site/reference/cli.md` from the first stage that introduces user-visible flags, output fields, exit codes, or command names; Stage 13 remains the full documentation pass. ## Test and Lint Boundaries @@ -334,7 +340,7 @@ Command registration approach: - Use Typer for the root app, global options, local workflow commands, and help/version/status/doctor/config/account commands. - Register generated API commands deterministically from registry records. -- If Typer's signature-based command model is too rigid for registry-built parameters, attach typed `click.Command` objects to the Typer app. This is allowed because it is deterministic command registration, not SDK method injection. +- For generated API commands, prefer typed `click.Command` objects attached to the Typer app when registry-built parameters are required. This is allowed because it is deterministic command registration, not SDK method injection. - Do not generate Python source files for commands. - Do not use `setattr`, `globals()`, monkey-patching, or modifying SDK/domain classes to create commands. - Generated command callbacks must all delegate to one invocation engine; command-specific behavior belongs in registry metadata only when the generic path cannot infer it safely. @@ -349,7 +355,7 @@ Register only the canonical command: ```toml [tool.poetry.scripts] -avito = "avito.cli.app:app" +avito = "avito.cli.app:main" ``` ## Command Model @@ -410,6 +416,16 @@ Supported from root and subcommands through one typed CLI context: --timeout ``` +Canonical invocation syntax for global options: + +```bash +avito --profile main account get-self +avito --json --no-input account get-self +``` + +The first release only guarantees root-level global options before the resource/action path. +If trailing global options are added later, they must be additive, tested, and documented. + Write/destructive commands additionally support: ```text @@ -747,7 +763,7 @@ Deliverables: - Add root `avito` app with typed global context. - Add `avito --help`, `avito --version`, `avito version`. - Route `python -m avito` to the same CLI app. -- Register Poetry script. +- Register Poetry script as `avito = "avito.cli.app:main"`; keep `app` as the reusable Typer application object. - Use Russian help text from the beginning. Tests: @@ -773,12 +789,15 @@ Exit criteria: - Help/version commands do not touch network, config, or account files. - `python -m avito --help` and `avito --help` exercise the same app. - Importing `avito.cli.app` has no filesystem side effects and does not construct `AvitoClient`. +- Root-level global options are parsed in the canonical position before subcommands. Stage checklist: - [ ] `typer` is added as a runtime dependency. - [ ] `avito/cli/` package exists with only the minimal shell files. - [ ] `avito --help`, `avito --version`, `avito version`, and `python -m avito --help` work. +- [ ] Poetry script points to `avito.cli.app:main`, not directly to the Typer app object. +- [ ] Canonical root-level global option syntax is covered by tests. - [ ] No config directory or account file is created by help/version commands. - [ ] `tests/cli/test_app.py` covers the shell behavior. - [ ] Stage verification commands pass. @@ -794,6 +813,8 @@ Deliverables: - Add one reusable sanitizer used by all renderers. - Add color handling for `--no-color` and `NO_COLOR=1`. - Add invalid global flag-combination validation. +- Create or update `docs/site/reference/cli.md` with the exit codes, global flags, output modes, stdout/stderr split, and current implemented commands. +- Add `cli.md` to `docs/site/reference/.pages` when the reference page is created. Tests: @@ -811,12 +832,14 @@ poetry run python scripts/lint_python_guidelines.py poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli +poetry run mkdocs build --strict ``` Exit criteria: - Every CLI error has a Russian message, stable uppercase code, and documented exit code. - No traceback is printed by default; diagnostics are sanitized. +- The reference CLI contract documents only implemented behavior. Stage checklist: @@ -825,6 +848,8 @@ Stage checklist: - [ ] Human and JSON errors use the same sanitized error payload. - [ ] Invalid output flag combinations exit with code `2`. - [ ] `--quiet`, `--debug`, `--verbose`, `--no-color`, and `NO_COLOR=1` are covered by tests. +- [ ] `docs/site/reference/cli.md` documents implemented global flags, output modes, and exit codes. +- [ ] `docs/site/reference/.pages` includes `cli.md` once the page exists. - [ ] Stage verification commands pass. ### Stage 3: Account Store and Profile Commands @@ -893,7 +918,7 @@ Deliverables: - Add deterministic collision detection for `resource action`. - Add exclusion record type. - Add registry/coverage JSON report command or hidden internal report. -- Extend `scripts/lint_architecture.py` or add a dedicated CLI architecture lint rule that forbids production `avito/cli/` imports from `tests`, `avito.testing`, domain operation modules, transport implementations, auth provider internals, and `avito.core.operations`. +- Extend `scripts/lint_architecture.py` with CLI import-boundary checks that forbid production `avito/cli/` imports from `tests`, `avito.testing`, domain operation modules, transport implementations, auth provider internals, and `avito.core.operations`. Add a dedicated CLI architecture linter only if the existing script becomes materially unsuitable. Tests: @@ -932,7 +957,7 @@ Stage checklist: - [ ] Canonical API commands map one-to-one to sync Swagger bindings. - [ ] Local/API command collisions fail during registry construction. - [ ] `scripts/lint_cli_coverage.py` exists and exercises the registry. -- [ ] CLI production import boundaries are statically checked. +- [ ] Existing `scripts/lint_architecture.py` statically checks CLI production import boundaries, unless a documented dedicated-linter exception exists. - [ ] Stage verification commands pass. ### Stage 5: Generic Input Coercion @@ -1176,7 +1201,7 @@ Stage checklist: Deliverables: -- Classify write/destructive commands from HTTP method and/or SDK metadata. +- Classify write/destructive commands from explicit registry safety metadata. HTTP method may provide defaults, but reviewed metadata is the source of truth. - Require confirmation for destructive commands unless `--yes` or exact `--confirm` is supplied. - Support `--dry-run` only when the SDK public method already supports `dry_run` or when CLI can safely preview without changing SDK behavior. - Do not fake dry-run for SDK methods that would still execute transport. @@ -1195,6 +1220,7 @@ Tests: Static lint responsibilities: - safety metadata cannot be absent for write/destructive/expensive records; +- HTTP-method-derived safety defaults must be reviewed into explicit registry metadata before a command is exposed; - destructive/expensive command help includes required safety flags and examples. Verification: @@ -1216,6 +1242,7 @@ Exit criteria: Stage checklist: - [ ] Write/destructive/expensive classification is deterministic and tested. +- [ ] Exposed write/destructive/expensive commands have explicit reviewed safety metadata. - [ ] Destructive commands require prompt, `--yes`, or exact `--confirm`. - [ ] `--no-input` never hangs and fails safely when confirmation is required. - [ ] `--dry-run` is exposed only for SDK methods that safely support it. @@ -1422,7 +1449,7 @@ Deliverables: - run JSON automation commands with `--json --no-input`; - use `status`, `doctor`, and completion commands; - explain safe handling of local plaintext secrets. -- Add `docs/site/reference/cli.md` for stable CLI contracts: +- Complete `docs/site/reference/cli.md` for stable CLI contracts: - command grammar `avito `; - global flags; - output modes and stdout/stderr split; @@ -1516,6 +1543,7 @@ Stage checklist: - [ ] `typer` dependency added. - [ ] `avito/cli/` exists and is isolated from SDK core/domain/transport/auth layers. - [ ] Console command `avito` is registered in `pyproject.toml`. +- [ ] Console command entry point is `avito.cli.app:main`. - [ ] `python -m avito` exposes the same CLI. - [ ] `avito --help`, `avito --version`, and `avito version` work. - [ ] Global flags work consistently at root and subcommand levels. From 365c9c6a43a3c4e3cea0685cab2822e10f92b6a9 Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sun, 10 May 2026 13:16:21 +0300 Subject: [PATCH 12/38] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BD=D0=B0=D0=B4=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.md | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/todo.md b/todo.md index 3b88521..f0f87f8 100644 --- a/todo.md +++ b/todo.md @@ -44,7 +44,8 @@ Definition of done for the whole plan: - Все stage checklists выполнены. - `avito` и `python -m avito` работают через один CLI app. -- Все sync Swagger-bound методы покрыты canonical CLI command или явным documented exclusion. +- Все sync Swagger-bound domain methods покрыты canonical CLI command или явным documented exclusion. +- Под "100% CLI coverage" в этом плане понимается: каждый sync Swagger-bound domain method имеет ровно одну canonical CLI command, а каждый не-доменный Swagger binding, deprecated/compatibility path или unsupported helper имеет documented exclusion с причиной. Прямое покрытие auth-token internals через CLI не входит в первый релиз и не считается дефектом покрытия. - Все supported helper workflows покрыты command или documented exclusion. - Coverage linter проходит и включен в `make check`. - CLI не дублирует SDK contracts и не обходит public `AvitoClient` surface. @@ -62,6 +63,7 @@ Target outcome: - `avito` console command and `python -m avito` expose the same CLI. - Local account/profile/config commands work without Avito network calls. - Every sync Swagger-bound public SDK method has exactly one canonical CLI command, unless it has a documented intentional exclusion. +- The first release coverage target is strict for sync domain API methods discovered through `AvitoClient` factory metadata. Non-domain auth-token bindings and compatibility-only wrappers are intentionally excluded unless a separate public SDK facade is designed for them. - Every supported public non-Swagger helper has a CLI command or a documented exclusion. - CLI coverage is checked automatically against Swagger binding discovery. - Human output is useful by default; JSON output is stable for automation. @@ -280,6 +282,7 @@ Additional findings from reviewing this plan against `.ai/STYLEGUIDE.md`, `.ai/c - The console entry point must be a stable callable, not the Typer application object itself. Use `avito.cli.app:main` for packaging and keep `app` as the reusable Typer application instance for tests and `python -m avito`. - Root-level global options are the canonical syntax for the first release: `avito --profile main account get-self`. Supporting trailing global options such as `avito account get-self --profile main` is optional and must be implemented deliberately, not assumed from Typer/Click behavior. - The generated API command layer should prefer deterministic Click command objects attached to the Typer root app when runtime-built parameters become too rigid for Typer's signature model. This keeps registration inspectable without generating Python source. +- If production code imports `click` directly, add `click` as an explicit runtime dependency instead of relying on Typer's transitive dependency. If `click` is used only through Typer testing utilities, keep it out of production imports. - Safety classification cannot rely on HTTP method alone. HTTP method may provide a default, but final command safety must come from explicit registry metadata and reviewed overrides for write, destructive, expensive, and local commands. - CLI import-boundary checks should extend the existing `scripts/lint_architecture.py` unless there is a concrete reason to split them into a dedicated script. Avoid two overlapping architecture linters. - The stable CLI contract should be documented as soon as the corresponding surface exists. Create or update `docs/site/reference/cli.md` from the first stage that introduces user-visible flags, output fields, exit codes, or command names; Stage 13 remains the full documentation pass. @@ -683,6 +686,12 @@ Help must include: - flags with stable names; - related commands when useful. +Implementation requirement: + +- `avito help` is a public compatibility command, not a private Typer behavior assumption. +- `avito help ` and `avito help ` must be tested no later than the stage that introduces nested generated commands. +- If Typer's default help cannot provide this shape, implement a small `help.py` adapter that reads the same command registry metadata used for command registration. + Completion commands: ```bash @@ -758,10 +767,12 @@ Stage checklist: Deliverables: - Add `typer` dependency. +- Add `click` as an explicit runtime dependency only if Stage 1 production code imports `click` directly. If Stage 1 uses Click only through Typer test utilities, do not add a separate direct dependency yet; revisit this when generated API commands start using `click.Command`. - Use Typer/Click test utilities only in tests; do not add a custom subprocess harness unless behavior specifically requires `python -m avito`. - Add `avito/cli/` package skeleton. - Add root `avito` app with typed global context. - Add `avito --help`, `avito --version`, `avito version`. +- Add `avito help` as the user-facing help entry point. At Stage 1 it may delegate to root help only; nested help such as `avito help account get-self` becomes mandatory once nested commands exist. - Route `python -m avito` to the same CLI app. - Register Poetry script as `avito = "avito.cli.app:main"`; keep `app` as the reusable Typer application object. - Use Russian help text from the beginning. @@ -794,8 +805,9 @@ Exit criteria: Stage checklist: - [ ] `typer` is added as a runtime dependency. +- [ ] `click` is either not imported by production code, or is added as an explicit runtime dependency. - [ ] `avito/cli/` package exists with only the minimal shell files. -- [ ] `avito --help`, `avito --version`, `avito version`, and `python -m avito --help` work. +- [ ] `avito --help`, `avito help`, `avito --version`, `avito version`, and `python -m avito --help` work. - [ ] Poetry script points to `avito.cli.app:main`, not directly to the Typer app object. - [ ] Canonical root-level global option syntax is covered by tests. - [ ] No config directory or account file is created by help/version commands. @@ -913,6 +925,10 @@ Deliverables: - Build `avito/cli/registry.py`. - Convert sync discovered Swagger bindings into canonical resource/action records. - Preserve factory name, factory args, method name, method args, operation key, and domain. +- Add nested help support for registry-backed resources and actions: + - `avito help `; + - `avito help `; + - generated help must use registry metadata and must not instantiate `AvitoClient`. - Register local commands and public non-Swagger helpers in separate categories. - Add alias support separate from canonical command records. - Add deterministic collision detection for `resource action`. @@ -925,6 +941,7 @@ Tests: - registry can be built without constructing `AvitoClient`, reading account files, or touching the network; - local helper command metadata is visible to help/registration code separately from API command metadata; - aliases delegate to canonical command records at runtime and do not produce duplicate callbacks. +- `avito help ` and `avito help ` render registry-backed help without constructing `AvitoClient`. Static lint responsibilities introduced in this stage: @@ -953,6 +970,7 @@ Exit criteria: Stage checklist: - [ ] Registry records are typed and deterministic. +- [ ] Registry-backed `avito help ` and `avito help ` are implemented and tested. - [ ] API, helper, local, alias, and exclusion records are separate categories. - [ ] Canonical API commands map one-to-one to sync Swagger bindings. - [ ] Local/API command collisions fail during registry construction. @@ -1546,7 +1564,8 @@ Stage checklist: - [ ] Console command entry point is `avito.cli.app:main`. - [ ] `python -m avito` exposes the same CLI. - [ ] `avito --help`, `avito --version`, and `avito version` work. -- [ ] Global flags work consistently at root and subcommand levels. +- [ ] Root-level global flags work in the documented canonical syntax, for example `avito --profile main account get-self`. +- [ ] Trailing/subcommand global flags are either deliberately implemented, tested, and documented as additive behavior, or explicitly documented as unsupported in the first release. - [ ] CLI home defaults to `~/.avito-py/`. - [ ] `AVITO_PY_HOME` and `MY_SDK_HOME` override CLI home with documented precedence. - [ ] CLI home directory is created lazily with `0700` permissions. @@ -1571,7 +1590,7 @@ Stage checklist: - [ ] Pagination behavior is bounded and documented. - [ ] Destructive commands require confirmation unless `--yes` or `--confirm` is supplied. - [ ] `--dry-run` is exposed only for SDK methods that safely support it. -- [ ] One smoke command per domain is tested through fake transport. +- [ ] Every completed factory group has at least one representative smoke command tested through fake transport. - [ ] CLI coverage linter exists, passes, and is included in `make check` after full coverage. - [ ] README and docs include CLI usage, config, output, and exit-code contracts. - [ ] Minimum stage verification commands pass during implementation. From 4455e6205d1174cc76dba6e0c8a0ce401cb731b8 Mon Sep 17 00:00:00 2001 From: Nikolay Baryshnikov Date: Sun, 10 May 2026 13:21:52 +0300 Subject: [PATCH 13/38] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BD=D0=B0=D0=B4=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todo.md | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 168 insertions(+), 15 deletions(-) diff --git a/todo.md b/todo.md index f0f87f8..51e3dce 100644 --- a/todo.md +++ b/todo.md @@ -286,6 +286,12 @@ Additional findings from reviewing this plan against `.ai/STYLEGUIDE.md`, `.ai/c - Safety classification cannot rely on HTTP method alone. HTTP method may provide a default, but final command safety must come from explicit registry metadata and reviewed overrides for write, destructive, expensive, and local commands. - CLI import-boundary checks should extend the existing `scripts/lint_architecture.py` unless there is a concrete reason to split them into a dedicated script. Avoid two overlapping architecture linters. - The stable CLI contract should be documented as soon as the corresponding surface exists. Create or update `docs/site/reference/cli.md` from the first stage that introduces user-visible flags, output fields, exit codes, or command names; Stage 13 remains the full documentation pass. +- Every stage that adds or changes user-visible CLI behavior must update `CHANGELOG.md`. The SDK styleguide treats CLI commands, flags, output fields, and exit codes as public contracts. +- Dependency stages must update both `pyproject.toml` and `poetry.lock`. Verification must include a lock consistency check after adding `typer` or direct `click` usage. +- Representative smoke tests by factory are not enough for the final "all methods" claim. Before strict coverage, every canonical CLI command must be registered, render help, and execute at least once through a fake client or `SwaggerFakeTransport` when safe synthetic arguments exist. Commands that cannot be executed generically need a documented exclusion or a custom adapter with tests. +- Secret input must not force users to put `client_secret` in shell history. `--client-secret` remains supported for explicit automation, but `account add` must also support a hidden TTY prompt and at least one non-interactive alternative such as `--client-secret-stdin` or documented environment/config input. +- Some Swagger-bound methods may need command-specific adapters for file input, multipart data, binary responses, or complex public input models. These adapters are allowed only inside `avito/cli/` and must still call public `AvitoClient` factories and public domain methods. +- New CLI scripts must be type-checked explicitly when they are outside the configured `avito` mypy package scope. Stage verification should include `poetry run mypy scripts/lint_cli_coverage.py` once that script exists. ## Test and Lint Boundaries @@ -528,6 +534,7 @@ Canonical flags: - `--client-id` - `--client-secret` +- `--client-secret-stdin` - `--base-url` - `--user-id` @@ -719,6 +726,11 @@ Stage policy: - Every stage that changes command metadata must run the current `scripts/lint_cli_coverage.py` phase, even before strict mode is enabled. - Every stage that changes persisted config/account JSON shape must include migration/backward-compatibility tests or explicitly state why no existing persisted shape exists yet. - Every stage that changes user-visible CLI text, flags, output fields, or exit codes must update `docs/site/reference/cli.md` once that reference page exists. +- Every stage that changes CLI public behavior must update `CHANGELOG.md` in the same change. +- Every stage that adds runtime dependencies must update `poetry.lock` and verify that dependency resolution is consistent. +- After `scripts/lint_cli_coverage.py` exists, every stage that touches CLI + metadata, adapters, coverage, or registration must run + `poetry run mypy scripts/lint_cli_coverage.py`. Coverage linter phase policy: @@ -768,6 +780,7 @@ Deliverables: - Add `typer` dependency. - Add `click` as an explicit runtime dependency only if Stage 1 production code imports `click` directly. If Stage 1 uses Click only through Typer test utilities, do not add a separate direct dependency yet; revisit this when generated API commands start using `click.Command`. +- Update `poetry.lock` after dependency changes. - Use Typer/Click test utilities only in tests; do not add a custom subprocess harness unless behavior specifically requires `python -m avito`. - Add `avito/cli/` package skeleton. - Add root `avito` app with typed global context. @@ -776,6 +789,7 @@ Deliverables: - Route `python -m avito` to the same CLI app. - Register Poetry script as `avito = "avito.cli.app:main"`; keep `app` as the reusable Typer application object. - Use Russian help text from the beginning. +- Add a `CHANGELOG.md` entry for the new CLI shell and public entry points. Tests: @@ -792,6 +806,7 @@ poetry run python scripts/lint_python_guidelines.py poetry run python scripts/lint_architecture.py poetry run mypy avito poetry run ruff check avito/cli tests/cli +poetry check --lock poetry build ``` @@ -806,6 +821,7 @@ Stage checklist: - [ ] `typer` is added as a runtime dependency. - [ ] `click` is either not imported by production code, or is added as an explicit runtime dependency. +- [ ] `poetry.lock` is updated and lock consistency is verified. - [ ] `avito/cli/` package exists with only the minimal shell files. - [ ] `avito --help`, `avito help`, `avito --version`, `avito version`, and `python -m avito --help` work. - [ ] Poetry script points to `avito.cli.app:main`, not directly to the Typer app object. @@ -827,6 +843,7 @@ Deliverables: - Add invalid global flag-combination validation. - Create or update `docs/site/reference/cli.md` with the exit codes, global flags, output modes, stdout/stderr split, and current implemented commands. - Add `cli.md` to `docs/site/reference/.pages` when the reference page is created. +- Update `CHANGELOG.md` with the first documented CLI contract: global flags, output modes, and exit codes. Tests: @@ -866,10 +883,60 @@ Stage checklist: ### Stage 3: Account Store and Profile Commands +Split Stage 3 into two reviewable sub-stages. Do not implement API invocation in +this stage; account/profile commands are local only. + +#### Stage 3A: Account Store Primitives + Deliverables: - Implement CLI home resolver and atomic JSON persistence. - Implement account/config dataclasses and stores. +- Implement safe store loading: missing files, malformed JSON, permission + failures, and schema-version handling. +- Implement conversion from stored account data to `AvitoSettings` without + constructing `AvitoClient`. +- Store active account name in config, not per-account flags. + +Tests: + +- default home and environment override precedence; +- lazy directory creation; +- file permissions where platform supports it; +- atomic JSON writes through same-directory temporary files and `os.replace`; +- malformed JSON handling; +- account/config dataclass serialization masks secrets in JSON output helpers; +- conversion to `AvitoSettings` uses only SDK public settings types. + +Verification: + +```bash +poetry run pytest tests/cli/test_config.py +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py +poetry run mypy avito +poetry run ruff check avito/cli tests/cli +``` + +Exit criteria: + +- Importing account/config modules creates no directories or files. +- Store code performs no Avito API network calls. +- Permission and malformed-file failures map to typed CLI errors. + +Stage checklist: + +- [ ] CLI home resolution follows `AVITO_PY_HOME`, `MY_SDK_HOME`, then `~/.avito-py`. +- [ ] Directory/file creation is lazy and uses required permissions where supported. +- [ ] JSON writes are atomic through same-directory temp files and `os.replace`. +- [ ] Active account is stored once in config, not as per-account boolean state. +- [ ] Store loading and malformed JSON behavior are tested. +- [ ] Stage 3A verification commands pass. + +#### Stage 3B: Account Commands and Secret Input + +Deliverables: + - Add account commands: - `avito account add` - `avito account list` @@ -877,26 +944,31 @@ Deliverables: - `avito account current` - `avito account delete ` - Add optional `account remove` only as documented alias for `account delete`. -- Convert active account to `AvitoSettings`. -- Store active account name in config, not per-account flags. +- Support safe secret entry for `client_secret`: hidden TTY prompt by default when input is allowed, plus a non-interactive path that does not require putting the secret directly in shell history. +- Add `--client-secret-stdin` for non-interactive secret input. It reads exactly + one secret value from stdin, strips one trailing newline, refuses TTY stdin, and + is mutually exclusive with `--client-secret` and `--api-key`. +- Keep `--client-secret` and ticket-compatible `--api-key` for explicit automation, but document the shell-history tradeoff. +- Ensure `--no-input` fails with `AUTH_REQUIRED`/`CONFIG_INVALID` instead of + prompting when no secret was provided. +- Update `CHANGELOG.md` for account/profile commands and local plaintext secret storage behavior. Tests: -- default home and environment override precedence; -- lazy directory creation; -- file permissions where platform supports it; - add/reload account; - duplicate account conflict; - active account set/get/clear; -- malformed JSON handling; - no-input behavior; - ticket aliases `--api-key` and `--endpoint`; +- hidden prompt path; +- `--client-secret-stdin` path; +- mutually exclusive secret flags; - JSON output contains no raw secrets. Verification: ```bash -poetry run pytest tests/cli/test_config.py tests/cli/test_accounts.py +poetry run pytest tests/cli/test_accounts.py poetry run python scripts/lint_python_guidelines.py poetry run python scripts/lint_architecture.py poetry run mypy avito @@ -907,16 +979,18 @@ Exit criteria: - Account commands perform no Avito API network calls. - Secret fields are masked in every output mode. +- Users have one interactive and one non-interactive way to provide secrets + without putting them in shell history. Stage checklist: -- [ ] CLI home resolution follows `AVITO_PY_HOME`, `MY_SDK_HOME`, then `~/.avito-py`. -- [ ] Directory/file creation is lazy and uses required permissions where supported. -- [ ] JSON writes are atomic through same-directory temp files and `os.replace`. - [ ] Account add/list/use/current/delete commands work without network. -- [ ] Active account is stored once in config, not as per-account boolean state. - [ ] `--api-key` and `--endpoint` aliases are tested. -- [ ] Stage verification commands pass. +- [ ] Hidden prompt secret input is tested. +- [ ] `--client-secret-stdin` is tested and refuses TTY stdin. +- [ ] `--client-secret`, `--api-key`, and `--client-secret-stdin` are mutually exclusive. +- [ ] Public docs or reference text clearly describe plaintext local storage and safe secret input. +- [ ] Stage 3B verification commands pass. ### Stage 4: CLI Registry From SDK Metadata @@ -947,6 +1021,8 @@ Static lint responsibilities introduced in this stage: - `scripts/lint_cli_coverage.py --phase registry` verifies that the registry includes all sync discovered bindings in report mode; - the same phase verifies kebab-case resource/action names, duplicate canonical commands, one-to-one binding ownership, alias policy, local/API collisions, forbidden `resource-id`, and required exclusion metadata; +- the same phase verifies that adapter references, if present, point to an + explicit adapter registry entry rather than ad hoc callback names; - `scripts/lint_architecture.py` verifies CLI production import boundaries. Verification: @@ -957,6 +1033,7 @@ poetry run python scripts/lint_cli_coverage.py --phase registry poetry run python scripts/lint_python_guidelines.py poetry run python scripts/lint_architecture.py poetry run mypy avito +poetry run mypy scripts/lint_cli_coverage.py poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -975,6 +1052,7 @@ Stage checklist: - [ ] Canonical API commands map one-to-one to sync Swagger bindings. - [ ] Local/API command collisions fail during registry construction. - [ ] `scripts/lint_cli_coverage.py` exists and exercises the registry. +- [ ] Registry records can reference named adapters without importing adapter implementation modules during discovery. - [ ] Existing `scripts/lint_architecture.py` statically checks CLI production import boundaries, unless a documented dedicated-linter exception exists. - [ ] Stage verification commands pass. @@ -1008,6 +1086,7 @@ poetry run python scripts/lint_cli_coverage.py --phase registry poetry run python scripts/lint_python_guidelines.py poetry run python scripts/lint_architecture.py poetry run mypy avito +poetry run mypy scripts/lint_cli_coverage.py poetry run ruff check avito/cli tests/cli ``` @@ -1051,9 +1130,11 @@ Verification: ```bash poetry run pytest tests/cli/test_commands.py +poetry run python scripts/lint_cli_coverage.py --phase registry poetry run python scripts/lint_python_guidelines.py poetry run python scripts/lint_architecture.py poetry run mypy avito +poetry run mypy scripts/lint_cli_coverage.py poetry run ruff check avito/cli tests/cli ``` @@ -1071,6 +1152,59 @@ Stage checklist: - [ ] SDK exceptions map to documented CLI exit codes and sanitized messages. - [ ] Stage verification commands pass. +### Stage 6B: Command Adapter Extension Point + +Deliverables: + +- Add `avito/cli/adapters.py` with a typed adapter protocol for commands whose + public SDK signature is valid but cannot be exposed safely by the fully generic + path. +- Restrict adapters to CLI input/output concerns: file opening, stdin handling, + multipart-friendly path arguments, binary result rendering, and public input + model construction. +- Require every adapter to call the same invocation engine or public + `AvitoClient` factory/domain method path. Adapters must not call operation + specs, operation executor, transport, auth provider internals, or domain object + constructors directly. +- Add adapter metadata to registry records by stable adapter id. Do not store raw + callables in metadata that must be serialized by the coverage report. +- Add linter checks that every adapter id referenced by a command exists, is + used by at least one command, and has an owner/reason note. + +Tests: + +- a simple adapter can transform CLI-only input and still invokes a public SDK + method through the shared path; +- adapter errors are sanitized and mapped to documented CLI exit codes; +- adapter registry rejects unknown adapter ids and duplicate adapter ids; +- an adapter-backed command still renders help and appears in coverage reports. + +Verification: + +```bash +poetry run pytest tests/cli/test_adapters.py tests/cli/test_commands.py +poetry run python scripts/lint_cli_coverage.py --phase registry +poetry run python scripts/lint_python_guidelines.py +poetry run python scripts/lint_architecture.py +poetry run mypy avito +poetry run mypy scripts/lint_cli_coverage.py +poetry run ruff check avito/cli tests/cli +``` + +Exit criteria: + +- Adapter support exists before any all-domain command wave needs it. +- Adapter-backed commands remain auditable by the coverage linter. + +Stage checklist: + +- [ ] Adapter protocol is typed and documented in code. +- [ ] Adapter metadata is stable and serializable in registry/coverage reports. +- [ ] Adapter-backed invocation still uses public SDK factories and methods only. +- [ ] Architecture lint prevents adapters from importing forbidden internal layers. +- [ ] Unknown, duplicate, or unused adapter ids fail lint. +- [ ] Stage 6B verification commands pass. + ### Stage 7: Result Serialization and Pagination Deliverables: @@ -1144,6 +1278,7 @@ poetry run python scripts/lint_cli_coverage.py --phase read poetry run python scripts/lint_python_guidelines.py poetry run python scripts/lint_architecture.py poetry run mypy avito +poetry run mypy scripts/lint_cli_coverage.py poetry run ruff check avito/cli tests/cli ``` @@ -1181,6 +1316,7 @@ Required coverage groups: Tests: - one read-only smoke invocation per completed factory group with fake transport; +- every canonical read-only command is registered, renders help, and has either a successful fake execution test or a documented temporary exclusion from execution smoke with reason and follow-up; - human and JSON output for representative object and collection commands; - fake-transport behavior proves no real network calls are made. @@ -1198,6 +1334,7 @@ poetry run python scripts/lint_cli_coverage.py --phase read poetry run python scripts/lint_python_guidelines.py poetry run python scripts/lint_architecture.py poetry run mypy avito +poetry run mypy scripts/lint_cli_coverage.py poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -1209,6 +1346,7 @@ Exit criteria: Stage checklist: - [ ] Every completed factory group has at least one read-only smoke command test. +- [ ] Every canonical read-only command has registration/help coverage and execution coverage or a documented temporary execution-smoke exclusion. - [ ] CLI coverage linter covers every discovered read-only sync binding. - [ ] Unsupported read-only bindings have explicit temporary exclusions with follow-up. - [ ] Domain/resource help exists for generated read-only commands. @@ -1249,6 +1387,7 @@ poetry run python scripts/lint_cli_coverage.py --phase write-safety poetry run python scripts/lint_python_guidelines.py poetry run python scripts/lint_architecture.py poetry run mypy avito +poetry run mypy scripts/lint_cli_coverage.py poetry run ruff check avito/cli tests/cli scripts/lint_cli_coverage.py ``` @@ -1285,6 +1424,7 @@ Deliverables: Tests: - one write smoke invocation per write-capable factory group in the current wave with fake transport; +- every canonical write command in the current wave is registered, renders help, and has either a successful fake execution test or a documented temporary execution-smoke exclusion with reason and follow-up; - safety tests run for at least one destructive or expensive command when the wave contains one. Static lint responsibilities: @@ -1302,6 +1442,7 @@ poetry run python scripts/lint_cli_coverage.py --phase write --domain