cited-cli 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. cited_cli-0.3.2/.github/workflows/ci.yml +37 -0
  2. cited_cli-0.3.2/.github/workflows/release.yml +55 -0
  3. cited_cli-0.3.2/.gitignore +19 -0
  4. cited_cli-0.3.2/CLAUDE.md +257 -0
  5. cited_cli-0.3.2/DEMO.md +132 -0
  6. cited_cli-0.3.2/FUTURE.md +197 -0
  7. cited_cli-0.3.2/LICENSE +4 -0
  8. cited_cli-0.3.2/PKG-INFO +515 -0
  9. cited_cli-0.3.2/README.md +494 -0
  10. cited_cli-0.3.2/TEST_RUNBOOK.md +736 -0
  11. cited_cli-0.3.2/cited-plugins/.claude-plugin/marketplace.json +27 -0
  12. cited_cli-0.3.2/cited-plugins/.claude-plugin/plugin.json +13 -0
  13. cited_cli-0.3.2/cited-plugins/.mcp.json +11 -0
  14. cited_cli-0.3.2/cited-plugins/agents/geo-analyst.md +41 -0
  15. cited_cli-0.3.2/cited-plugins/commands/audit.md +17 -0
  16. cited_cli-0.3.2/cited-plugins/commands/recommend.md +15 -0
  17. cited_cli-0.3.2/cited-plugins/commands/status.md +13 -0
  18. cited_cli-0.3.2/cited-plugins/settings.json +3 -0
  19. cited_cli-0.3.2/cited-plugins/skills/business-setup/SKILL.md +28 -0
  20. cited_cli-0.3.2/cited-plugins/skills/geo-audit/SKILL.md +30 -0
  21. cited_cli-0.3.2/cited-plugins/skills/geo-recommend/SKILL.md +33 -0
  22. cited_cli-0.3.2/packages/core/pyproject.toml +20 -0
  23. cited_cli-0.3.2/packages/core/src/cited_core/__init__.py +1 -0
  24. cited_cli-0.3.2/packages/core/src/cited_core/api/__init__.py +1 -0
  25. cited_cli-0.3.2/packages/core/src/cited_core/api/client.py +111 -0
  26. cited_cli-0.3.2/packages/core/src/cited_core/api/endpoints.py +90 -0
  27. cited_cli-0.3.2/packages/core/src/cited_core/auth/__init__.py +1 -0
  28. cited_cli-0.3.2/packages/core/src/cited_core/auth/oauth_server.py +122 -0
  29. cited_cli-0.3.2/packages/core/src/cited_core/auth/store.py +83 -0
  30. cited_cli-0.3.2/packages/core/src/cited_core/config/__init__.py +1 -0
  31. cited_cli-0.3.2/packages/core/src/cited_core/config/constants.py +50 -0
  32. cited_cli-0.3.2/packages/core/src/cited_core/config/manager.py +69 -0
  33. cited_cli-0.3.2/packages/core/src/cited_core/errors.py +32 -0
  34. cited_cli-0.3.2/packages/mcp/pyproject.toml +29 -0
  35. cited_cli-0.3.2/packages/mcp/src/cited_mcp/__init__.py +4 -0
  36. cited_cli-0.3.2/packages/mcp/src/cited_mcp/__main__.py +5 -0
  37. cited_cli-0.3.2/packages/mcp/src/cited_mcp/context.py +12 -0
  38. cited_cli-0.3.2/packages/mcp/src/cited_mcp/server.py +54 -0
  39. cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/__init__.py +1 -0
  40. cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/_helpers.py +33 -0
  41. cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/audit.py +186 -0
  42. cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/auth.py +59 -0
  43. cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/business.py +153 -0
  44. cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/job.py +57 -0
  45. cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/recommend.py +117 -0
  46. cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/solution.py +78 -0
  47. cited_cli-0.3.2/pyproject.toml +50 -0
  48. cited_cli-0.3.2/scripts/cleanup_dev.py +246 -0
  49. cited_cli-0.3.2/scripts/dev-install.sh +26 -0
  50. cited_cli-0.3.2/scripts/release.sh +240 -0
  51. cited_cli-0.3.2/src/cited_cli/__init__.py +1 -0
  52. cited_cli-0.3.2/src/cited_cli/__main__.py +3 -0
  53. cited_cli-0.3.2/src/cited_cli/api/__init__.py +0 -0
  54. cited_cli-0.3.2/src/cited_cli/api/client.py +111 -0
  55. cited_cli-0.3.2/src/cited_cli/api/endpoints.py +88 -0
  56. cited_cli-0.3.2/src/cited_cli/app.py +199 -0
  57. cited_cli-0.3.2/src/cited_cli/auth/__init__.py +0 -0
  58. cited_cli-0.3.2/src/cited_cli/auth/oauth_server.py +123 -0
  59. cited_cli-0.3.2/src/cited_cli/auth/store.py +83 -0
  60. cited_cli-0.3.2/src/cited_cli/commands/__init__.py +0 -0
  61. cited_cli-0.3.2/src/cited_cli/commands/agent.py +129 -0
  62. cited_cli-0.3.2/src/cited_cli/commands/analytics.py +83 -0
  63. cited_cli-0.3.2/src/cited_cli/commands/audit.py +177 -0
  64. cited_cli-0.3.2/src/cited_cli/commands/auth.py +353 -0
  65. cited_cli-0.3.2/src/cited_cli/commands/business.py +217 -0
  66. cited_cli-0.3.2/src/cited_cli/commands/config_cmd.py +111 -0
  67. cited_cli-0.3.2/src/cited_cli/commands/hq.py +125 -0
  68. cited_cli-0.3.2/src/cited_cli/commands/job.py +106 -0
  69. cited_cli-0.3.2/src/cited_cli/commands/mcp_cmd.py +30 -0
  70. cited_cli-0.3.2/src/cited_cli/commands/named_audit.py +244 -0
  71. cited_cli-0.3.2/src/cited_cli/commands/recommend.py +211 -0
  72. cited_cli-0.3.2/src/cited_cli/commands/solution.py +154 -0
  73. cited_cli-0.3.2/src/cited_cli/config/__init__.py +0 -0
  74. cited_cli-0.3.2/src/cited_cli/config/constants.py +48 -0
  75. cited_cli-0.3.2/src/cited_cli/config/manager.py +69 -0
  76. cited_cli-0.3.2/src/cited_cli/mcp/__init__.py +1 -0
  77. cited_cli-0.3.2/src/cited_cli/mcp/context.py +12 -0
  78. cited_cli-0.3.2/src/cited_cli/mcp/server.py +54 -0
  79. cited_cli-0.3.2/src/cited_cli/mcp/tools/__init__.py +1 -0
  80. cited_cli-0.3.2/src/cited_cli/mcp/tools/_helpers.py +33 -0
  81. cited_cli-0.3.2/src/cited_cli/mcp/tools/audit.py +129 -0
  82. cited_cli-0.3.2/src/cited_cli/mcp/tools/auth.py +23 -0
  83. cited_cli-0.3.2/src/cited_cli/mcp/tools/business.py +96 -0
  84. cited_cli-0.3.2/src/cited_cli/mcp/tools/job.py +57 -0
  85. cited_cli-0.3.2/src/cited_cli/mcp/tools/recommend.py +96 -0
  86. cited_cli-0.3.2/src/cited_cli/mcp/tools/solution.py +66 -0
  87. cited_cli-0.3.2/src/cited_cli/output/__init__.py +0 -0
  88. cited_cli-0.3.2/src/cited_cli/output/formatter.py +61 -0
  89. cited_cli-0.3.2/src/cited_cli/output/progress.py +56 -0
  90. cited_cli-0.3.2/src/cited_cli/output/tables.py +49 -0
  91. cited_cli-0.3.2/src/cited_cli/utils/__init__.py +0 -0
  92. cited_cli-0.3.2/src/cited_cli/utils/errors.py +31 -0
  93. cited_cli-0.3.2/src/cited_cli/utils/interactive.py +83 -0
  94. cited_cli-0.3.2/tests/__init__.py +0 -0
  95. cited_cli-0.3.2/tests/conftest.py +16 -0
  96. cited_cli-0.3.2/tests/test_api_client.py +81 -0
  97. cited_cli-0.3.2/tests/test_auth.py +377 -0
  98. cited_cli-0.3.2/tests/test_config.py +64 -0
  99. cited_cli-0.3.2/tests/test_mcp_server.py +61 -0
  100. cited_cli-0.3.2/tests/test_mcp_tools.py +261 -0
  101. cited_cli-0.3.2/tests/test_output.py +35 -0
  102. cited_cli-0.3.2/tests/test_pipeline.py +1381 -0
  103. cited_cli-0.3.2/tests/test_version.py +17 -0
@@ -0,0 +1,37 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint-and-test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: |
26
+ pip install -e packages/core
27
+ pip install -e packages/mcp
28
+ pip install -e ".[dev]"
29
+
30
+ - name: Lint
31
+ run: ruff check src/ packages/
32
+
33
+ - name: Type check
34
+ run: mypy packages/core/src packages/mcp/src src/cited_cli/ --ignore-missing-imports
35
+
36
+ - name: Test
37
+ run: pytest -v
@@ -0,0 +1,55 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ release:
13
+ runs-on: ubuntu-latest
14
+ environment: Prod
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.12"
22
+
23
+ - name: Install build tools
24
+ run: pip install build twine
25
+
26
+ # Build and publish in dependency order: core → mcp → cli
27
+ - name: Build and publish cited-core
28
+ run: |
29
+ cd packages/core
30
+ python -m build
31
+ twine upload dist/* -u __token__ -p ${{ secrets.PYPI_TOKEN }}
32
+ env:
33
+ TWINE_NON_INTERACTIVE: "1"
34
+
35
+ - name: Build and publish cited-mcp
36
+ run: |
37
+ cd packages/mcp
38
+ python -m build
39
+ twine upload dist/* -u __token__ -p ${{ secrets.PYPI_TOKEN }}
40
+ env:
41
+ TWINE_NON_INTERACTIVE: "1"
42
+
43
+ - name: Build cited-cli
44
+ run: python -m build
45
+
46
+ - name: Publish cited-cli to PyPI
47
+ run: twine upload dist/* -u __token__ -p ${{ secrets.PYPI_TOKEN }}
48
+ env:
49
+ TWINE_NON_INTERACTIVE: "1"
50
+
51
+ - name: Create GitHub Release
52
+ uses: softprops/action-gh-release@v2
53
+ with:
54
+ files: dist/*
55
+ generate_release_notes: true
@@ -0,0 +1,19 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ *.egg
9
+ .venv/
10
+ venv/
11
+ env/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ .pytest_cache/
15
+ *.so
16
+ .coverage
17
+ htmlcov/
18
+ .env
19
+ ~/.cited/
@@ -0,0 +1,257 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What This Is
6
+
7
+ Monorepo for the [Cited](https://youcited.com) GEO platform tooling. Contains three Python packages:
8
+
9
+ - **`cited-core`** (`packages/core/`) — Shared library: API client, auth, config, endpoints, errors. No CLI or MCP dependencies.
10
+ - **`cited-mcp`** (`packages/mcp/`) — Standalone MCP server with 28 tools. Depends on `cited-core` + `mcp` SDK. Installable via `pip install cited-mcp` or `uvx cited-mcp`.
11
+ - **`cited-cli`** (root `src/cited_cli/`) — Typer + Rich CLI. Depends on `cited-core` + `typer` + `rich`. Optional dep on `cited-mcp` for `cited mcp serve`.
12
+
13
+ Also contains `cited-plugins/` — a Claude Code plugin (skills, commands, agents) that references the MCP server via `uvx cited-mcp`.
14
+
15
+ The backend API lives in a separate monorepo at `~/repos/cited/`.
16
+
17
+ ## Commands
18
+
19
+ ```bash
20
+ # Dev setup (install all three packages in editable mode)
21
+ ./scripts/dev-install.sh
22
+
23
+ # Or manually:
24
+ pip install -e packages/core
25
+ pip install -e packages/mcp
26
+ pip install -e ".[dev]"
27
+
28
+ # Run tests, lint, type check
29
+ pytest -v # Run all tests
30
+ ruff check src/ packages/ # Lint all packages
31
+ mypy packages/core/src packages/mcp/src src/cited_cli/ --ignore-missing-imports
32
+
33
+ # Run the CLI
34
+ cited --help
35
+
36
+ # Run the standalone MCP server
37
+ cited-mcp # Standalone binary (from cited-mcp package)
38
+ cited mcp serve # Via CLI (requires cited-cli[mcp])
39
+
40
+ # Release (publishes cited-core → cited-mcp → cited-cli to PyPI)
41
+ ./scripts/release.sh 0.3.2
42
+ ```
43
+
44
+ ## Architecture
45
+
46
+ ### Package Structure
47
+
48
+ **`cited-core`** (`packages/core/src/cited_core/`) — Zero CLI/MCP dependencies:
49
+ - `api/client.py` — `CitedClient` (httpx sync)
50
+ - `api/endpoints.py` — API endpoint path constants
51
+ - `auth/store.py` — `TokenStore` (keyring + file fallback)
52
+ - `auth/oauth_server.py` — Temporary localhost OAuth callback server
53
+ - `config/manager.py` — `ConfigManager` (TOML config)
54
+ - `config/constants.py` — `ENVIRONMENTS`, `CONFIG_DIR`, `VALID_INDUSTRIES`, etc.
55
+ - `errors.py` — `CitedAPIError`, `ExitCode`, `exit_code_for_status()`
56
+
57
+ **`cited-mcp`** (`packages/mcp/src/cited_mcp/`) — MCP server + 28 tools:
58
+ - `server.py` — `FastMCP` instance, lifespan, `run_server()`
59
+ - `tools/` — auth, business, audit, recommend, solution, job tool modules
60
+ - Supports `CITED_TOKEN` and `CITED_AGENT_API_KEY` env vars
61
+ - Includes browser OAuth `login` tool for Claude Desktop users
62
+
63
+ **`cited-cli`** (`src/cited_cli/`) — Typer CLI:
64
+ - Imports core modules from `cited_core.*` (not `cited_cli.api.*`)
65
+ - `utils/errors.py` re-exports `CitedAPIError` etc. from `cited_core.errors` + adds `handle_api_error()` (Rich-based)
66
+ - `commands/mcp_cmd.py` lazy-imports from `cited_mcp` (optional dep)
67
+
68
+ ### CLI Architecture
69
+
70
+ **Entry point:** `src/cited_cli/app.py` — creates the root `typer.Typer`, registers subcommand groups via `app.add_typer()`, plus top-level `login` and `logout` commands (delegating to `do_login`/`do_logout` from `commands/auth.py`). Defines global flags (`--json`, `--env`, `--profile`, etc.) in the `main_callback`, and stores shared state in `typer.Context.obj`.
71
+
72
+ **Global state flow:** The callback populates `ctx.obj` with `OutputContext`, `ConfigManager`, profile name, and env override. Every command extracts these via a `_get_ctx()` or `_get_client()` helper at the top of each command module.
73
+
74
+ **Dual output mode:** Every command must support both human (Rich) and JSON (`--json`) output. The pattern is:
75
+ ```python
76
+ print_result(data, out, human_formatter=lambda d, c: render_kv("Title", d, c))
77
+ ```
78
+ `print_result` writes `json.dumps(data)` to stdout in JSON mode, or calls the `human_formatter` otherwise. Errors always go to stderr.
79
+
80
+ **Output format resolution:** The output format is resolved with this precedence: `--json` flag → `--text` flag → config value (`cited config set output json|text`) → default (`text`). This lets users set `output = "json"` in config for scripting workflows and override back to human-readable with `--text` / `-t` for a single invocation.
81
+
82
+ **Authentication:** Top-level `cited login` is the primary entry point (also available as `cited auth login` for backward compat). Both delegate to `do_login()` / `do_logout()` in `commands/auth.py`.
83
+
84
+ - **Browser login (default):** `cited login` opens the youcited login page in the browser. The CLI starts a temporary localhost HTTP server (`auth/oauth_server.py`) to receive the OAuth callback token. The success page includes a collapsible token-copy section for the paste fallback.
85
+ - **OAuth provider shortcut:** `cited login --provider google|microsoft|github` sends the user directly to that provider.
86
+ - **Password login:** `cited login --email user@example.com` prompts for password (or use `--password` for non-interactive/CI).
87
+ - **Password registration:** `cited register --email ... --name ... --password ...` is a two-step flow:
88
+ 1. `POST /auth/cli-register` — creates the account and sends a verification email; returns `{"message": "..."}` (201, **no token**).
89
+ 2. CLI prompts the user to paste the verification URL from their inbox (e.g. `https://app.youcited.com/complete-registration?token=<43-char-token>`).
90
+ 3. CLI parses the `?token=` query param and calls `POST /auth/cli-verify-email {"token": "..."}`.
91
+ 4. Backend marks email verified and returns `{"token": "<JWT>", "user": {...}}`; JWT is stored; prints "Email verified! Logged in as {email}".
92
+ - Endpoint constant: `endpoints.CLI_VERIFY_EMAIL = "/auth/cli-verify-email"`.
93
+ - Tests mock both endpoints and supply the verification URL via `input=` on `runner.invoke`.
94
+ - **Paste fallback:** If the localhost callback times out (firewall, WSL, SSH), the CLI prompts the user to paste the token shown on the browser success page.
95
+ - **Token storage:** Per-environment via OS keychain (`keyring`) with a file fallback (`~/.cited/credentials.json`). The token is sent back to the API as a cookie, not a Bearer header.
96
+ - **TTY detection:** `commands/auth.py` uses `_is_interactive()` (wrapping `sys.stdin.isatty()`) for all TTY checks — tests monkeypatch this helper.
97
+
98
+ **Agent API auth** uses a separate path: `X-Agent-API-Key` header, configured via `cited config set agent_api_key` or `CITED_AGENT_API_KEY` env var.
99
+
100
+ **Adding a new command group:**
101
+ 1. Create `src/cited_cli/commands/<name>.py` with a `typer.Typer()` instance
102
+ 2. Add endpoint paths to `src/cited_cli/api/endpoints.py`
103
+ 3. Register in `app.py` with `app.add_typer(..., name="<name>")`
104
+
105
+ **Adding a nested command group (sub-subcommands):**
106
+ Create the inner Typer in a separate file, then register it on the parent app — not on the root app. Example: `audit template` lives in `commands/named_audit.py` and is registered in `app.py` as:
107
+ ```python
108
+ audit_app.add_typer(named_audit_app, name="template")
109
+ ```
110
+ This gives `cited audit template list|get|create|update|delete` while `cited audit start|status|...` remain top-level on `audit_app`. Crucially, the nested registration must happen **before** `app.add_typer(audit_app, ...)` — in practice, put it at the top of the registration block in `app.py`.
111
+
112
+ **Adding a new command to an existing group:** Add a function decorated with `@<group>_app.command()` in the relevant command file. Follow the `_get_client()` → try/except `CitedAPIError` → `print_result()` pattern.
113
+
114
+ **Audit template workflow:** The typical flow is create → review → refine → run audit. Auto-generated questions are rarely perfect, so `audit template update` is a common operation. The `--question` flags on `update` **replace all** existing questions (matching the web editor's save behavior). Omit `--question` to keep existing questions unchanged while updating name/description. Backend endpoint: `PUT /named-audits/{id}` with `NamedAuditUpdate` schema.
115
+
116
+ ## Key Conventions
117
+
118
+ - **Exit codes:** 0=success, 1=error, 2=auth, 3=not found, 4=validation, 5=rate limited (defined in `utils/errors.py`)
119
+ - **Line length:** 100 chars (ruff enforced)
120
+ - **All files** start with `from __future__ import annotations`
121
+ - **Type hints** use `X | None` syntax, not `Optional[X]`
122
+ - **Valid config keys:** `environment`, `default_business_id`, `agent_api_key`, `output` (values: `json`, `text`)
123
+ - **Config module** is named `config_cmd.py` (not `config.py`) to avoid shadowing stdlib
124
+ - **Tests** use `typer.testing.CliRunner` (fixtures `runner` and `cli_app` in `conftest.py`), `respx` for HTTP mocking, and `monkeypatch` to redirect `CONFIG_DIR`/`CONFIG_FILE`/`CREDENTIALS_FILE` to `tmp_path`
125
+ - **User config** lives at `~/.cited/config.toml`; version is in `src/cited_cli/__init__.py`
126
+ - **Version** single source of truth is `packages/core/src/cited_core/__init__.py`. The CLI and MCP packages import it via `from cited_core import __version__`. The `version =` field in all three `pyproject.toml` files must also be bumped (the release script handles this).
127
+
128
+ ## Releasing
129
+
130
+ `scripts/release.sh <version>` handles the full release pipeline. It bumps the version in both `pyproject.toml` and `__init__.py`, commits, tags `v<version>`, pushes (triggering the GitHub Actions release workflow), waits for the release, then regenerates the Homebrew formula in `~/repos/homebrew-cited` with the new tarball URL, SHA256, and all Python dependency resource blocks (resolved from PyPI). Finally it commits and pushes the tap update.
131
+
132
+ **Prerequisites:** clean git state on `main`, `gh` CLI authenticated, `.venv` with cited-cli installed, `~/repos/homebrew-cited` cloned.
133
+
134
+ **Homebrew tap repo:** `~/repos/homebrew-cited` (`YouCited/homebrew-cited` on GitHub). Contains a single formula `Formula/cited.rb` that installs cited-cli into a Python virtualenv with pinned dependency hashes. The formula uses `Language::Python::Virtualenv` and depends on `python@3.12` and `rust` (build-time, for `pydantic-core`).
135
+
136
+ ## API Environments
137
+
138
+ | Name | URL | Default |
139
+ |------|-----|---------|
140
+ | prod | `https://api.youcited.com` | yes |
141
+ | dev | `https://dev.youcited.com` | |
142
+ | local | `http://localhost:8000` | |
143
+
144
+ ## Real API Response Field Names (Critical)
145
+
146
+ These field names were confirmed against the live dev API on 2026-03-18. Using the wrong names produces silently empty output (no error, just blank table cells). Always check the live response shape before adding new field access.
147
+
148
+ ### `GET /recommendations/{job_id}/result` (used by `recommend insights` + `recommend result`)
149
+
150
+ ```python
151
+ {
152
+ "question_insights": [
153
+ {
154
+ "question_id": "uuid", # ← source_id for solution start; NOT "id"
155
+ "question_text": "...", # ← description field; NOT "question"
156
+ "risk_level": "high",
157
+ "coverage_score": 0.33,
158
+ }
159
+ ],
160
+ "head_to_head_comparisons": [ # ← key name; NOT "head_to_head"
161
+ {
162
+ "competitor_domain": "competitor.com", # ← source_id; no uuid id field
163
+ "competitor_url": "https://...",
164
+ "business_domain": "...",
165
+ "overall_winner": "business",
166
+ }
167
+ ],
168
+ "strengthening_tips": [
169
+ {
170
+ "category": "llms_txt", # ← source_id for solution start; no uuid id field
171
+ "title": "Create llms.txt for AI Discovery",
172
+ "priority": "high",
173
+ }
174
+ ],
175
+ "priority_actions": [],
176
+ }
177
+ ```
178
+
179
+ ### `POST /solutions/request` (used by `solution start`)
180
+
181
+ ```json
182
+ {
183
+ "recommendation_job_id": "<uuid>",
184
+ "source_type": "question_insight",
185
+ "source_id": "<question_id from question_insights>"
186
+ }
187
+ ```
188
+
189
+ **`source_type` enum:** `question_insight` · `head_to_head` · `strengthening_tip` · `priority_action`
190
+
191
+ This endpoint replaced the deprecated `POST /solutions/create` which took `{"recommendation_id": ...}`.
192
+
193
+ ### `POST /businesses/{id}/crawl` (used by `business crawl`)
194
+
195
+ Returns a `job_id` in the response body — you can `job watch` the crawl:
196
+ ```json
197
+ {
198
+ "message": "Crawl job enqueued",
199
+ "business_id": "...",
200
+ "job_id": "...",
201
+ "status": "crawling"
202
+ }
203
+ ```
204
+
205
+ ### `POST /businesses` (used by `business create`)
206
+
207
+ - `industry` must be one of: `automotive` `beauty` `consulting` `education` `entertainment` `finance` `fitness` `government` `healthcare` `home_services` `hospitality` `legal` `manufacturing` `non_profit` `real_estate` `restaurant` `retail` `technology` `other`
208
+ - `website` must be a publicly DNS-resolvable domain — fabricated domains (e.g. `example.com`, `acme-test.example.com`) will return 422
209
+ - `description` minimum ~50 characters
210
+
211
+ ### `POST /audit/start` (used by `audit start`)
212
+
213
+ ```json
214
+ {
215
+ "named_audit_id": "<template uuid>",
216
+ "business_id": "<optional override>"
217
+ }
218
+ ```
219
+
220
+ The old shape `{"business_id": ...}` returns 422 — `named_audit_id` is required.
221
+
222
+ ## Testing Patterns
223
+
224
+ Tests use `respx` for HTTP mocking. Key patterns from `tests/test_pipeline.py`:
225
+
226
+ **Pre-seeding a login token** (avoids needing to mock the auth flow):
227
+ ```python
228
+ def _setup_logged_in(tmp_path, monkeypatch):
229
+ # redirect config to tmp_path, then write credentials
230
+ creds_file.write_text(json.dumps({"dev": "test-jwt-dev-token"}))
231
+ ```
232
+
233
+ **Mocking `watch_job`** to avoid `time.sleep` and Rich `Live` renderer in tests:
234
+ ```python
235
+ monkeypatch.setattr(
236
+ "cited_cli.commands.job.watch_job",
237
+ lambda client, path, console, poll_interval=2.0: {"status": "completed", "job_id": "..."},
238
+ )
239
+ ```
240
+
241
+ **Threading IDs between commands** (the `--json` pattern):
242
+ ```python
243
+ result = runner.invoke(app, ["--env", "dev", "--json", "business", "create", ...])
244
+ business_id = json.loads(result.output)["id"] # thread into next command
245
+ ```
246
+
247
+ **Verifying request payload** (not just response):
248
+ ```python
249
+ captured_body = {}
250
+ def _capture(request):
251
+ captured_body.update(json.loads(request.content))
252
+ return httpx.Response(202, json=MOCK_RESPONSE)
253
+ respx.post(f"{DEV_API}/audit/start").mock(side_effect=_capture)
254
+ # ... invoke command ...
255
+ assert captured_body["named_audit_id"] == TEMPLATE_ID
256
+ assert "recommendation_id" not in captured_body # old field must not appear
257
+ ```
@@ -0,0 +1,132 @@
1
+ # Cited CLI — Demo Walkthrough
2
+
3
+ Copy-paste each block into a terminal, one at a time. Each step captures IDs into shell variables that feed into the next step.
4
+
5
+ **Prerequisites:** `cited` CLI installed, `jq` installed, logged out (or ready to re-login).
6
+
7
+ ---
8
+
9
+ ## Setup
10
+
11
+ ```bash
12
+ cited config set environment dev
13
+ cited --env dev status
14
+ cited login
15
+ cited auth status
16
+ ```
17
+
18
+ ## Step 1: Create business
19
+
20
+ ```bash
21
+ BUSINESS_ID=$(cited --env dev --json business create \
22
+ --name "CLI Demo $(date +%H%M%S)" \
23
+ --website "https://anthropic.com" \
24
+ --description "Demo business for validating the full Cited GEO pipeline via CLI." \
25
+ --industry "technology" \
26
+ | jq -r '.id')
27
+ echo "BUSINESS_ID=$BUSINESS_ID"
28
+
29
+ cited --env dev business get $BUSINESS_ID
30
+ ```
31
+
32
+ ## Step 2: Crawl the website
33
+
34
+ ```bash
35
+ CRAWL_JOB=$(cited --env dev --json business crawl $BUSINESS_ID | jq -r '.job_id')
36
+ echo "CRAWL_JOB=$CRAWL_JOB"
37
+
38
+ cited --env dev job watch $CRAWL_JOB
39
+
40
+ cited --env dev business health $BUSINESS_ID
41
+ ```
42
+
43
+ ## Step 3: Create audit template
44
+
45
+ ```bash
46
+ TEMPLATE_ID=$(cited --env dev --json audit template create \
47
+ --name "Demo GEO Audit" \
48
+ --business $BUSINESS_ID \
49
+ --description "Checks citation presence across key AI and technology queries" \
50
+ --question "Are we cited when people ask about AI safety?" \
51
+ --question "Does our product appear in AI assistant recommendations?" \
52
+ --question "Are we mentioned when users research responsible AI?" \
53
+ | jq -r '.id')
54
+ echo "TEMPLATE_ID=$TEMPLATE_ID"
55
+
56
+ cited --env dev audit template get $TEMPLATE_ID
57
+ ```
58
+
59
+ ## Step 4: Refine template questions
60
+
61
+ ```bash
62
+ cited --env dev audit template update $TEMPLATE_ID \
63
+ --question "Are we cited when enterprise buyers ask about AI safety?" \
64
+ --question "Does Anthropic appear in responsible AI tool recommendations?" \
65
+ --question "Are we mentioned when developers compare AI platforms?"
66
+
67
+ cited --env dev audit template get $TEMPLATE_ID
68
+ ```
69
+
70
+ ## Step 5: Run the audit
71
+
72
+ ```bash
73
+ AUDIT_JOB=$(cited --env dev --json audit start $TEMPLATE_ID --business $BUSINESS_ID \
74
+ | jq -r '.job_id')
75
+ echo "AUDIT_JOB=$AUDIT_JOB"
76
+
77
+ cited --env dev job watch $AUDIT_JOB
78
+
79
+ cited --env dev audit result $AUDIT_JOB
80
+ ```
81
+
82
+ ## Step 6: Generate recommendations
83
+
84
+ ```bash
85
+ RECO_JOB=$(cited --env dev --json recommend start $AUDIT_JOB | jq -r '.job_id')
86
+ echo "RECO_JOB=$RECO_JOB"
87
+
88
+ cited --env dev job watch $RECO_JOB
89
+
90
+ cited --env dev recommend result $RECO_JOB
91
+ ```
92
+
93
+ ## Step 7: View insights
94
+
95
+ ```bash
96
+ cited --env dev recommend insights $RECO_JOB
97
+
98
+ SOURCE_ID=$(cited --env dev --json recommend insights $RECO_JOB \
99
+ | jq -r '.question_insights[0].question_id')
100
+ echo "SOURCE_ID=$SOURCE_ID"
101
+ ```
102
+
103
+ ## Step 8: Start a solution
104
+
105
+ ```bash
106
+ cited --env dev solution start $RECO_JOB \
107
+ --type question_insight \
108
+ --source $SOURCE_ID
109
+ ```
110
+
111
+ ## Step 9: HQ dashboard
112
+
113
+ ```bash
114
+ cited --env dev hq $BUSINESS_ID
115
+ cited --env dev hq $BUSINESS_ID --full
116
+ ```
117
+
118
+ ## Step 10: Analytics
119
+
120
+ ```bash
121
+ cited --env dev analytics trends $BUSINESS_ID
122
+ cited --env dev analytics summary $BUSINESS_ID
123
+ ```
124
+
125
+ ## Cleanup
126
+
127
+ ```bash
128
+ cited --env dev audit template delete $TEMPLATE_ID --yes
129
+ cited --env dev business delete $BUSINESS_ID --yes
130
+ cited logout
131
+ cited auth status # should fail with exit code 2
132
+ ```