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.
- cited_cli-0.3.2/.github/workflows/ci.yml +37 -0
- cited_cli-0.3.2/.github/workflows/release.yml +55 -0
- cited_cli-0.3.2/.gitignore +19 -0
- cited_cli-0.3.2/CLAUDE.md +257 -0
- cited_cli-0.3.2/DEMO.md +132 -0
- cited_cli-0.3.2/FUTURE.md +197 -0
- cited_cli-0.3.2/LICENSE +4 -0
- cited_cli-0.3.2/PKG-INFO +515 -0
- cited_cli-0.3.2/README.md +494 -0
- cited_cli-0.3.2/TEST_RUNBOOK.md +736 -0
- cited_cli-0.3.2/cited-plugins/.claude-plugin/marketplace.json +27 -0
- cited_cli-0.3.2/cited-plugins/.claude-plugin/plugin.json +13 -0
- cited_cli-0.3.2/cited-plugins/.mcp.json +11 -0
- cited_cli-0.3.2/cited-plugins/agents/geo-analyst.md +41 -0
- cited_cli-0.3.2/cited-plugins/commands/audit.md +17 -0
- cited_cli-0.3.2/cited-plugins/commands/recommend.md +15 -0
- cited_cli-0.3.2/cited-plugins/commands/status.md +13 -0
- cited_cli-0.3.2/cited-plugins/settings.json +3 -0
- cited_cli-0.3.2/cited-plugins/skills/business-setup/SKILL.md +28 -0
- cited_cli-0.3.2/cited-plugins/skills/geo-audit/SKILL.md +30 -0
- cited_cli-0.3.2/cited-plugins/skills/geo-recommend/SKILL.md +33 -0
- cited_cli-0.3.2/packages/core/pyproject.toml +20 -0
- cited_cli-0.3.2/packages/core/src/cited_core/__init__.py +1 -0
- cited_cli-0.3.2/packages/core/src/cited_core/api/__init__.py +1 -0
- cited_cli-0.3.2/packages/core/src/cited_core/api/client.py +111 -0
- cited_cli-0.3.2/packages/core/src/cited_core/api/endpoints.py +90 -0
- cited_cli-0.3.2/packages/core/src/cited_core/auth/__init__.py +1 -0
- cited_cli-0.3.2/packages/core/src/cited_core/auth/oauth_server.py +122 -0
- cited_cli-0.3.2/packages/core/src/cited_core/auth/store.py +83 -0
- cited_cli-0.3.2/packages/core/src/cited_core/config/__init__.py +1 -0
- cited_cli-0.3.2/packages/core/src/cited_core/config/constants.py +50 -0
- cited_cli-0.3.2/packages/core/src/cited_core/config/manager.py +69 -0
- cited_cli-0.3.2/packages/core/src/cited_core/errors.py +32 -0
- cited_cli-0.3.2/packages/mcp/pyproject.toml +29 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/__init__.py +4 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/__main__.py +5 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/context.py +12 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/server.py +54 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/__init__.py +1 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/_helpers.py +33 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/audit.py +186 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/auth.py +59 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/business.py +153 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/job.py +57 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/recommend.py +117 -0
- cited_cli-0.3.2/packages/mcp/src/cited_mcp/tools/solution.py +78 -0
- cited_cli-0.3.2/pyproject.toml +50 -0
- cited_cli-0.3.2/scripts/cleanup_dev.py +246 -0
- cited_cli-0.3.2/scripts/dev-install.sh +26 -0
- cited_cli-0.3.2/scripts/release.sh +240 -0
- cited_cli-0.3.2/src/cited_cli/__init__.py +1 -0
- cited_cli-0.3.2/src/cited_cli/__main__.py +3 -0
- cited_cli-0.3.2/src/cited_cli/api/__init__.py +0 -0
- cited_cli-0.3.2/src/cited_cli/api/client.py +111 -0
- cited_cli-0.3.2/src/cited_cli/api/endpoints.py +88 -0
- cited_cli-0.3.2/src/cited_cli/app.py +199 -0
- cited_cli-0.3.2/src/cited_cli/auth/__init__.py +0 -0
- cited_cli-0.3.2/src/cited_cli/auth/oauth_server.py +123 -0
- cited_cli-0.3.2/src/cited_cli/auth/store.py +83 -0
- cited_cli-0.3.2/src/cited_cli/commands/__init__.py +0 -0
- cited_cli-0.3.2/src/cited_cli/commands/agent.py +129 -0
- cited_cli-0.3.2/src/cited_cli/commands/analytics.py +83 -0
- cited_cli-0.3.2/src/cited_cli/commands/audit.py +177 -0
- cited_cli-0.3.2/src/cited_cli/commands/auth.py +353 -0
- cited_cli-0.3.2/src/cited_cli/commands/business.py +217 -0
- cited_cli-0.3.2/src/cited_cli/commands/config_cmd.py +111 -0
- cited_cli-0.3.2/src/cited_cli/commands/hq.py +125 -0
- cited_cli-0.3.2/src/cited_cli/commands/job.py +106 -0
- cited_cli-0.3.2/src/cited_cli/commands/mcp_cmd.py +30 -0
- cited_cli-0.3.2/src/cited_cli/commands/named_audit.py +244 -0
- cited_cli-0.3.2/src/cited_cli/commands/recommend.py +211 -0
- cited_cli-0.3.2/src/cited_cli/commands/solution.py +154 -0
- cited_cli-0.3.2/src/cited_cli/config/__init__.py +0 -0
- cited_cli-0.3.2/src/cited_cli/config/constants.py +48 -0
- cited_cli-0.3.2/src/cited_cli/config/manager.py +69 -0
- cited_cli-0.3.2/src/cited_cli/mcp/__init__.py +1 -0
- cited_cli-0.3.2/src/cited_cli/mcp/context.py +12 -0
- cited_cli-0.3.2/src/cited_cli/mcp/server.py +54 -0
- cited_cli-0.3.2/src/cited_cli/mcp/tools/__init__.py +1 -0
- cited_cli-0.3.2/src/cited_cli/mcp/tools/_helpers.py +33 -0
- cited_cli-0.3.2/src/cited_cli/mcp/tools/audit.py +129 -0
- cited_cli-0.3.2/src/cited_cli/mcp/tools/auth.py +23 -0
- cited_cli-0.3.2/src/cited_cli/mcp/tools/business.py +96 -0
- cited_cli-0.3.2/src/cited_cli/mcp/tools/job.py +57 -0
- cited_cli-0.3.2/src/cited_cli/mcp/tools/recommend.py +96 -0
- cited_cli-0.3.2/src/cited_cli/mcp/tools/solution.py +66 -0
- cited_cli-0.3.2/src/cited_cli/output/__init__.py +0 -0
- cited_cli-0.3.2/src/cited_cli/output/formatter.py +61 -0
- cited_cli-0.3.2/src/cited_cli/output/progress.py +56 -0
- cited_cli-0.3.2/src/cited_cli/output/tables.py +49 -0
- cited_cli-0.3.2/src/cited_cli/utils/__init__.py +0 -0
- cited_cli-0.3.2/src/cited_cli/utils/errors.py +31 -0
- cited_cli-0.3.2/src/cited_cli/utils/interactive.py +83 -0
- cited_cli-0.3.2/tests/__init__.py +0 -0
- cited_cli-0.3.2/tests/conftest.py +16 -0
- cited_cli-0.3.2/tests/test_api_client.py +81 -0
- cited_cli-0.3.2/tests/test_auth.py +377 -0
- cited_cli-0.3.2/tests/test_config.py +64 -0
- cited_cli-0.3.2/tests/test_mcp_server.py +61 -0
- cited_cli-0.3.2/tests/test_mcp_tools.py +261 -0
- cited_cli-0.3.2/tests/test_output.py +35 -0
- cited_cli-0.3.2/tests/test_pipeline.py +1381 -0
- 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,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
|
+
```
|
cited_cli-0.3.2/DEMO.md
ADDED
|
@@ -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
|
+
```
|