cited-cli 0.3.2__py3-none-any.whl

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 (48) hide show
  1. cited_cli/__init__.py +1 -0
  2. cited_cli/__main__.py +3 -0
  3. cited_cli/api/__init__.py +0 -0
  4. cited_cli/api/client.py +111 -0
  5. cited_cli/api/endpoints.py +88 -0
  6. cited_cli/app.py +199 -0
  7. cited_cli/auth/__init__.py +0 -0
  8. cited_cli/auth/oauth_server.py +123 -0
  9. cited_cli/auth/store.py +83 -0
  10. cited_cli/commands/__init__.py +0 -0
  11. cited_cli/commands/agent.py +129 -0
  12. cited_cli/commands/analytics.py +83 -0
  13. cited_cli/commands/audit.py +177 -0
  14. cited_cli/commands/auth.py +353 -0
  15. cited_cli/commands/business.py +217 -0
  16. cited_cli/commands/config_cmd.py +111 -0
  17. cited_cli/commands/hq.py +125 -0
  18. cited_cli/commands/job.py +106 -0
  19. cited_cli/commands/mcp_cmd.py +30 -0
  20. cited_cli/commands/named_audit.py +244 -0
  21. cited_cli/commands/recommend.py +211 -0
  22. cited_cli/commands/solution.py +154 -0
  23. cited_cli/config/__init__.py +0 -0
  24. cited_cli/config/constants.py +48 -0
  25. cited_cli/config/manager.py +69 -0
  26. cited_cli/mcp/__init__.py +1 -0
  27. cited_cli/mcp/context.py +12 -0
  28. cited_cli/mcp/server.py +54 -0
  29. cited_cli/mcp/tools/__init__.py +1 -0
  30. cited_cli/mcp/tools/_helpers.py +33 -0
  31. cited_cli/mcp/tools/audit.py +129 -0
  32. cited_cli/mcp/tools/auth.py +23 -0
  33. cited_cli/mcp/tools/business.py +96 -0
  34. cited_cli/mcp/tools/job.py +57 -0
  35. cited_cli/mcp/tools/recommend.py +96 -0
  36. cited_cli/mcp/tools/solution.py +66 -0
  37. cited_cli/output/__init__.py +0 -0
  38. cited_cli/output/formatter.py +61 -0
  39. cited_cli/output/progress.py +56 -0
  40. cited_cli/output/tables.py +49 -0
  41. cited_cli/utils/__init__.py +0 -0
  42. cited_cli/utils/errors.py +31 -0
  43. cited_cli/utils/interactive.py +83 -0
  44. cited_cli-0.3.2.dist-info/METADATA +515 -0
  45. cited_cli-0.3.2.dist-info/RECORD +48 -0
  46. cited_cli-0.3.2.dist-info/WHEEL +4 -0
  47. cited_cli-0.3.2.dist-info/entry_points.txt +2 -0
  48. cited_cli-0.3.2.dist-info/licenses/LICENSE +4 -0
@@ -0,0 +1,129 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Annotated
4
+
5
+ import typer
6
+
7
+ from cited_cli.output.formatter import OutputContext, print_error, print_result
8
+ from cited_cli.utils.errors import CitedAPIError, ExitCode, handle_api_error
9
+ from cited_cli.utils.interactive import prompt_if_missing
10
+ from cited_core.api import endpoints
11
+ from cited_core.api.client import CitedClient
12
+ from cited_core.config.manager import ConfigManager
13
+
14
+ agent_app = typer.Typer(
15
+ name="agent",
16
+ help="Agent API (v1) — structured business data for AI agents.",
17
+ )
18
+
19
+
20
+ def _get_agent_client(ctx: typer.Context) -> tuple[OutputContext, CitedClient]:
21
+ obj = ctx.obj or {}
22
+ out: OutputContext = obj.get("output", OutputContext())
23
+ cfg: ConfigManager = obj.get("config", ConfigManager())
24
+ profile = obj.get("profile", "default")
25
+ api_url = cfg.get_api_url(profile, obj.get("env_override"))
26
+
27
+ import os
28
+
29
+ api_key = cfg.get("agent_api_key", profile) or os.environ.get("CITED_AGENT_API_KEY")
30
+ if not api_key:
31
+ print_error(
32
+ "Agent API key required. Set via: cited config set agent_api_key <key>\n"
33
+ "Or set CITED_AGENT_API_KEY environment variable.",
34
+ out,
35
+ )
36
+ raise typer.Exit(ExitCode.AUTH_ERROR)
37
+
38
+ return out, CitedClient(base_url=api_url, agent_api_key=api_key)
39
+
40
+
41
+ @agent_app.command("facts")
42
+ def agent_facts(
43
+ ctx: typer.Context,
44
+ business_id: Annotated[str, typer.Argument(help="Business ID")],
45
+ ) -> None:
46
+ """Get structured business facts."""
47
+ out, client = _get_agent_client(ctx)
48
+ try:
49
+ path = endpoints.AGENT_FACTS.format(business_id=business_id)
50
+ data = client.get(path)
51
+ print_result(data, out)
52
+ except CitedAPIError as e:
53
+ handle_api_error(e, out.json_mode)
54
+ finally:
55
+ client.close()
56
+
57
+
58
+ @agent_app.command("claims")
59
+ def agent_claims(
60
+ ctx: typer.Context,
61
+ business_id: Annotated[str, typer.Argument(help="Business ID")],
62
+ ) -> None:
63
+ """Get verifiable claims about a business."""
64
+ out, client = _get_agent_client(ctx)
65
+ try:
66
+ path = endpoints.AGENT_CLAIMS.format(business_id=business_id)
67
+ data = client.get(path)
68
+ print_result(data, out)
69
+ except CitedAPIError as e:
70
+ handle_api_error(e, out.json_mode)
71
+ finally:
72
+ client.close()
73
+
74
+
75
+ @agent_app.command("comparison")
76
+ def agent_comparison(
77
+ ctx: typer.Context,
78
+ business_id: Annotated[str, typer.Argument(help="Business ID")],
79
+ ) -> None:
80
+ """Get competitive comparison data."""
81
+ out, client = _get_agent_client(ctx)
82
+ try:
83
+ path = endpoints.AGENT_COMPARISON.format(business_id=business_id)
84
+ data = client.get(path)
85
+ print_result(data, out)
86
+ except CitedAPIError as e:
87
+ handle_api_error(e, out.json_mode)
88
+ finally:
89
+ client.close()
90
+
91
+
92
+ @agent_app.command("semantic-health")
93
+ def agent_semantic_health(
94
+ ctx: typer.Context,
95
+ business_id: Annotated[str, typer.Argument(help="Business ID")],
96
+ ) -> None:
97
+ """Get semantic readiness signals."""
98
+ out, client = _get_agent_client(ctx)
99
+ try:
100
+ path = endpoints.AGENT_SEMANTIC_HEALTH.format(business_id=business_id)
101
+ data = client.get(path)
102
+ print_result(data, out)
103
+ except CitedAPIError as e:
104
+ handle_api_error(e, out.json_mode)
105
+ finally:
106
+ client.close()
107
+
108
+
109
+ @agent_app.command("buyer-fit")
110
+ def agent_buyer_fit(
111
+ ctx: typer.Context,
112
+ query: Annotated[
113
+ str | None, typer.Option("--query", "-q", help="Buyer query to simulate")
114
+ ] = None,
115
+ business_id: Annotated[str | None, typer.Option("--business", "-b", help="Business ID")] = None,
116
+ ) -> None:
117
+ """Run a buyer-fit simulation query."""
118
+ out, client = _get_agent_client(ctx)
119
+ query = prompt_if_missing(query, "--query", "Buyer query", out)
120
+ try:
121
+ payload: dict[str, str] = {"query": query}
122
+ if business_id:
123
+ payload["business_id"] = business_id
124
+ data = client.post(endpoints.AGENT_BUYER_FIT, json=payload)
125
+ print_result(data, out)
126
+ except CitedAPIError as e:
127
+ handle_api_error(e, out.json_mode)
128
+ finally:
129
+ client.close()
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Annotated
4
+
5
+ import typer
6
+
7
+ from cited_cli.output.formatter import OutputContext, print_error, print_result
8
+ from cited_cli.output.tables import render_kv
9
+ from cited_cli.utils.errors import CitedAPIError, ExitCode, handle_api_error
10
+ from cited_core.api import endpoints
11
+ from cited_core.api.client import CitedClient
12
+ from cited_core.auth.store import TokenStore
13
+ from cited_core.config.manager import ConfigManager
14
+
15
+ analytics_app = typer.Typer(name="analytics", help="Analytics and trend data.")
16
+
17
+
18
+ def _get_client(ctx: typer.Context) -> tuple[OutputContext, CitedClient]:
19
+ obj = ctx.obj or {}
20
+ out: OutputContext = obj.get("output", OutputContext())
21
+ cfg: ConfigManager = obj.get("config", ConfigManager())
22
+ profile = obj.get("profile", "default")
23
+ env = cfg.get_environment(profile, obj.get("env_override"))
24
+ api_url = cfg.get_api_url(profile, obj.get("env_override"))
25
+
26
+ store = TokenStore()
27
+ token = store.get_token(env)
28
+ if not token:
29
+ print_error(f"Not logged in to {env}. Run: cited login", out)
30
+ raise typer.Exit(ExitCode.AUTH_ERROR)
31
+
32
+ return out, CitedClient(base_url=api_url, token=token)
33
+
34
+
35
+ @analytics_app.command("compare")
36
+ def analytics_compare(
37
+ ctx: typer.Context,
38
+ audit_id: Annotated[str, typer.Argument(help="Audit ID to compare against baseline")],
39
+ ) -> None:
40
+ """Compare an audit against its baseline."""
41
+ out, client = _get_client(ctx)
42
+ try:
43
+ path = endpoints.ANALYTICS_COMPARISON.format(audit_id=audit_id)
44
+ data = client.get(path)
45
+ print_result(data, out)
46
+ except CitedAPIError as e:
47
+ handle_api_error(e, out.json_mode)
48
+ finally:
49
+ client.close()
50
+
51
+
52
+ @analytics_app.command("trends")
53
+ def analytics_trends(
54
+ ctx: typer.Context,
55
+ business_id: Annotated[str, typer.Argument(help="Business ID")],
56
+ ) -> None:
57
+ """Show KPI trends over time."""
58
+ out, client = _get_client(ctx)
59
+ try:
60
+ path = endpoints.ANALYTICS_TRENDS.format(business_id=business_id)
61
+ data = client.get(path)
62
+ print_result(data, out)
63
+ except CitedAPIError as e:
64
+ handle_api_error(e, out.json_mode)
65
+ finally:
66
+ client.close()
67
+
68
+
69
+ @analytics_app.command("summary")
70
+ def analytics_summary(
71
+ ctx: typer.Context,
72
+ business_id: Annotated[str, typer.Argument(help="Business ID")],
73
+ ) -> None:
74
+ """Get analytics summary for a business."""
75
+ out, client = _get_client(ctx)
76
+ try:
77
+ path = endpoints.ANALYTICS_SUMMARY.format(business_id=business_id)
78
+ data = client.get(path)
79
+ print_result(data, out, human_formatter=lambda d, c: render_kv("Analytics Summary", d, c))
80
+ except CitedAPIError as e:
81
+ handle_api_error(e, out.json_mode)
82
+ finally:
83
+ client.close()
@@ -0,0 +1,177 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Annotated
4
+
5
+ import typer
6
+ from rich.console import Console
7
+
8
+ from cited_cli.output.formatter import OutputContext, print_error, print_result, print_success
9
+ from cited_cli.output.tables import render_kv, render_table
10
+ from cited_cli.utils.errors import CitedAPIError, ExitCode, handle_api_error
11
+ from cited_cli.utils.interactive import prompt_if_missing
12
+ from cited_core.api import endpoints
13
+ from cited_core.api.client import LONG_TIMEOUT, CitedClient
14
+ from cited_core.auth.store import TokenStore
15
+ from cited_core.config.manager import ConfigManager
16
+
17
+ audit_app = typer.Typer(name="audit", help="Run and manage audits.")
18
+
19
+
20
+ def _get_client(ctx: typer.Context) -> tuple[OutputContext, CitedClient, str]:
21
+ obj = ctx.obj or {}
22
+ out: OutputContext = obj.get("output", OutputContext())
23
+ cfg: ConfigManager = obj.get("config", ConfigManager())
24
+ profile = obj.get("profile", "default")
25
+ env = cfg.get_environment(profile, obj.get("env_override"))
26
+ api_url = cfg.get_api_url(profile, obj.get("env_override"))
27
+
28
+ store = TokenStore()
29
+ token = store.get_token(env)
30
+ if not token:
31
+ print_error(f"Not logged in to {env}. Run: cited login", out)
32
+ raise typer.Exit(ExitCode.AUTH_ERROR)
33
+
34
+ return out, CitedClient(base_url=api_url, token=token), env
35
+
36
+
37
+ @audit_app.command("start")
38
+ def audit_start(
39
+ ctx: typer.Context,
40
+ named_audit_id: Annotated[
41
+ str | None, typer.Argument(help="Audit template (named audit) ID")
42
+ ] = None,
43
+ business_id: Annotated[
44
+ str | None,
45
+ typer.Option("--business", "-b", help="Business ID override"),
46
+ ] = None,
47
+ providers: Annotated[
48
+ list[str] | None,
49
+ typer.Option("--provider", help="Citation provider to use (repeatable)"),
50
+ ] = None,
51
+ ) -> None:
52
+ """Start a new audit job from a template."""
53
+ out, client, _ = _get_client(ctx)
54
+ named_audit_id = prompt_if_missing(
55
+ named_audit_id, "NAMED_AUDIT_ID", "Audit template ID", out
56
+ )
57
+ try:
58
+ payload: dict[str, object] = {"named_audit_id": named_audit_id}
59
+ if business_id:
60
+ payload["business_id"] = business_id
61
+ if providers:
62
+ payload["providers"] = providers
63
+ data = client.post(
64
+ endpoints.AUDIT_START,
65
+ json=payload,
66
+ timeout=LONG_TIMEOUT,
67
+ )
68
+ job_id = data.get("job_id", data.get("id", ""))
69
+ print_result(data, out, human_formatter=lambda d, c: render_kv("Audit Started", d, c))
70
+ if not out.json_mode and job_id:
71
+ out.console.print(f"\nTrack progress: [bold]cited job watch {job_id}[/bold]")
72
+ except CitedAPIError as e:
73
+ handle_api_error(e, out.json_mode)
74
+ finally:
75
+ client.close()
76
+
77
+
78
+ @audit_app.command("status")
79
+ def audit_status(
80
+ ctx: typer.Context,
81
+ job_id: Annotated[str, typer.Argument(help="Audit job ID")],
82
+ ) -> None:
83
+ """Check audit job status."""
84
+ out, client, _ = _get_client(ctx)
85
+ try:
86
+ path = endpoints.AUDIT_STATUS.format(job_id=job_id)
87
+ data = client.get(path)
88
+ print_result(data, out, human_formatter=lambda d, c: render_kv("Audit Status", d, c))
89
+ except CitedAPIError as e:
90
+ handle_api_error(e, out.json_mode)
91
+ finally:
92
+ client.close()
93
+
94
+
95
+ @audit_app.command("result")
96
+ def audit_result(
97
+ ctx: typer.Context,
98
+ job_id: Annotated[str, typer.Argument(help="Audit job ID")],
99
+ ) -> None:
100
+ """Get full audit results."""
101
+ out, client, _ = _get_client(ctx)
102
+ try:
103
+ path = endpoints.AUDIT_RESULT.format(job_id=job_id)
104
+ data = client.get(path)
105
+ print_result(data, out)
106
+ except CitedAPIError as e:
107
+ handle_api_error(e, out.json_mode)
108
+ finally:
109
+ client.close()
110
+
111
+
112
+ @audit_app.command("list")
113
+ def audit_list(
114
+ ctx: typer.Context,
115
+ business_id: Annotated[
116
+ str | None,
117
+ typer.Option("--business", "-b", help="Filter by business ID"),
118
+ ] = None,
119
+ ) -> None:
120
+ """List audit history."""
121
+ out, client, _ = _get_client(ctx)
122
+ try:
123
+ params = {}
124
+ if business_id:
125
+ params["business_id"] = business_id
126
+ data = client.get(endpoints.AUDIT_HISTORY, params=params or None)
127
+ audits = data if isinstance(data, list) else data.get("audits", data.get("items", []))
128
+
129
+ def _human(d: object, console: Console) -> None:
130
+ items = d if isinstance(d, list) else []
131
+ rows = []
132
+ for a in items:
133
+ rows.append([
134
+ a.get("job_id", a.get("id", ""))[:8],
135
+ a.get("business_name", ""),
136
+ a.get("status", ""),
137
+ a.get("created_at", "")[:19] if a.get("created_at") else "",
138
+ ])
139
+ render_table(
140
+ "Audit History",
141
+ ["Job ID", "Business", "Status", "Created"],
142
+ rows,
143
+ console,
144
+ )
145
+
146
+ print_result(audits, out, human_formatter=_human)
147
+ except CitedAPIError as e:
148
+ handle_api_error(e, out.json_mode)
149
+ finally:
150
+ client.close()
151
+
152
+
153
+ @audit_app.command("export")
154
+ def audit_export(
155
+ ctx: typer.Context,
156
+ job_id: Annotated[str, typer.Argument(help="Audit job ID")],
157
+ output_path: Annotated[
158
+ str | None,
159
+ typer.Option("--output", "-o", help="Output file path"),
160
+ ] = None,
161
+ ) -> None:
162
+ """Export audit as PDF."""
163
+ out, client, _ = _get_client(ctx)
164
+ try:
165
+ path = endpoints.AUDIT_EXPORT_PDF.format(job_id=job_id)
166
+ response = client.post_raw(path, timeout=LONG_TIMEOUT)
167
+ if not response.is_success:
168
+ raise CitedAPIError(response.status_code, f"Export failed ({response.status_code})")
169
+
170
+ filename = output_path or f"audit-{job_id[:8]}.pdf"
171
+ with open(filename, "wb") as f:
172
+ f.write(response.content)
173
+ print_success(f"Exported to {filename}", out)
174
+ except CitedAPIError as e:
175
+ handle_api_error(e, out.json_mode)
176
+ finally:
177
+ client.close()