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.
- cited_cli/__init__.py +1 -0
- cited_cli/__main__.py +3 -0
- cited_cli/api/__init__.py +0 -0
- cited_cli/api/client.py +111 -0
- cited_cli/api/endpoints.py +88 -0
- cited_cli/app.py +199 -0
- cited_cli/auth/__init__.py +0 -0
- cited_cli/auth/oauth_server.py +123 -0
- cited_cli/auth/store.py +83 -0
- cited_cli/commands/__init__.py +0 -0
- cited_cli/commands/agent.py +129 -0
- cited_cli/commands/analytics.py +83 -0
- cited_cli/commands/audit.py +177 -0
- cited_cli/commands/auth.py +353 -0
- cited_cli/commands/business.py +217 -0
- cited_cli/commands/config_cmd.py +111 -0
- cited_cli/commands/hq.py +125 -0
- cited_cli/commands/job.py +106 -0
- cited_cli/commands/mcp_cmd.py +30 -0
- cited_cli/commands/named_audit.py +244 -0
- cited_cli/commands/recommend.py +211 -0
- cited_cli/commands/solution.py +154 -0
- cited_cli/config/__init__.py +0 -0
- cited_cli/config/constants.py +48 -0
- cited_cli/config/manager.py +69 -0
- cited_cli/mcp/__init__.py +1 -0
- cited_cli/mcp/context.py +12 -0
- cited_cli/mcp/server.py +54 -0
- cited_cli/mcp/tools/__init__.py +1 -0
- cited_cli/mcp/tools/_helpers.py +33 -0
- cited_cli/mcp/tools/audit.py +129 -0
- cited_cli/mcp/tools/auth.py +23 -0
- cited_cli/mcp/tools/business.py +96 -0
- cited_cli/mcp/tools/job.py +57 -0
- cited_cli/mcp/tools/recommend.py +96 -0
- cited_cli/mcp/tools/solution.py +66 -0
- cited_cli/output/__init__.py +0 -0
- cited_cli/output/formatter.py +61 -0
- cited_cli/output/progress.py +56 -0
- cited_cli/output/tables.py +49 -0
- cited_cli/utils/__init__.py +0 -0
- cited_cli/utils/errors.py +31 -0
- cited_cli/utils/interactive.py +83 -0
- cited_cli-0.3.2.dist-info/METADATA +515 -0
- cited_cli-0.3.2.dist-info/RECORD +48 -0
- cited_cli-0.3.2.dist-info/WHEEL +4 -0
- cited_cli-0.3.2.dist-info/entry_points.txt +2 -0
- 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()
|