graphe-cli 0.1.1__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.
- graphe_cli/__init__.py +1 -0
- graphe_cli/context.py +39 -0
- graphe_cli/display.py +178 -0
- graphe_cli/main.py +432 -0
- graphe_cli-0.1.1.dist-info/METADATA +54 -0
- graphe_cli-0.1.1.dist-info/RECORD +9 -0
- graphe_cli-0.1.1.dist-info/WHEEL +5 -0
- graphe_cli-0.1.1.dist-info/entry_points.txt +2 -0
- graphe_cli-0.1.1.dist-info/top_level.txt +1 -0
graphe_cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Graphe CLI."""
|
graphe_cli/context.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from graphe_sdk import GrapheClient, GrapheConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class CliContext:
|
|
13
|
+
client: GrapheClient
|
|
14
|
+
config: GrapheConfig
|
|
15
|
+
output: str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_context(
|
|
19
|
+
*,
|
|
20
|
+
base_url: str | None,
|
|
21
|
+
api_key: str | None,
|
|
22
|
+
workspace_id: str | None,
|
|
23
|
+
project_id: str | None,
|
|
24
|
+
actor_id: str | None,
|
|
25
|
+
config_path: Path | None,
|
|
26
|
+
output: str,
|
|
27
|
+
) -> CliContext:
|
|
28
|
+
loaded = GrapheConfig.from_env(config_path=config_path)
|
|
29
|
+
config = GrapheConfig(
|
|
30
|
+
base_url=base_url or loaded.base_url,
|
|
31
|
+
api_key=api_key or loaded.api_key,
|
|
32
|
+
workspace_id=workspace_id or loaded.workspace_id,
|
|
33
|
+
project_id=project_id or loaded.project_id,
|
|
34
|
+
actor_id=actor_id or loaded.actor_id,
|
|
35
|
+
)
|
|
36
|
+
if not config.api_key:
|
|
37
|
+
raise typer.BadParameter("API key is required. Set GRAPHE_API_KEY or pass --api-key.")
|
|
38
|
+
client = GrapheClient(config.base_url, config.api_key)
|
|
39
|
+
return CliContext(client=client, config=config, output=output)
|
graphe_cli/display.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any, Mapping
|
|
6
|
+
|
|
7
|
+
from graphe_sdk.output import emit, print_table
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def short_id(value: Any, *, head: int = 8, tail: int = 4) -> str:
|
|
11
|
+
text = str(value)
|
|
12
|
+
if len(text) <= head + tail + 3:
|
|
13
|
+
return text
|
|
14
|
+
return f"{text[:head]}...{text[-tail:]}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def print_kv_table(data: Mapping[str, Any], *, stream: Any = None) -> None:
|
|
18
|
+
target = stream or sys.stdout
|
|
19
|
+
rows = [{"field": key, "value": value} for key, value in data.items()]
|
|
20
|
+
print_table(rows, ["field", "value"], stream=target)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def search_result_row(row: Mapping[str, Any]) -> dict[str, str]:
|
|
24
|
+
source_type = row.get("source_type") or row.get("kind") or ""
|
|
25
|
+
source_id = row.get("source_id") or row.get("id") or ""
|
|
26
|
+
summary = row.get("summary") or row.get("snippet") or ""
|
|
27
|
+
return {
|
|
28
|
+
"type": str(source_type),
|
|
29
|
+
"id": short_id(source_id),
|
|
30
|
+
"score": f"{float(row.get('score', 0)):.3f}",
|
|
31
|
+
"summary": str(summary)[:56],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def render(cli_output: str, data: Any, *, kind: str = "auto") -> None:
|
|
36
|
+
if cli_output == "json":
|
|
37
|
+
emit(data, output="json")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
if kind == "kv" and isinstance(data, Mapping):
|
|
41
|
+
print_kv_table({key: _format_scalar(value) for key, value in data.items()})
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
if kind == "search" and isinstance(data, Mapping):
|
|
45
|
+
rows = [search_result_row(row) for row in data.get("results", [])]
|
|
46
|
+
print_table(rows, ["type", "id", "score", "summary"])
|
|
47
|
+
mode = data.get("mode")
|
|
48
|
+
if mode:
|
|
49
|
+
sys.stdout.write(f"mode: {mode}\n")
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
if kind == "state" and isinstance(data, Mapping):
|
|
53
|
+
_render_state(data)
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
if kind == "runs" and isinstance(data, Mapping):
|
|
57
|
+
print_table(
|
|
58
|
+
[
|
|
59
|
+
{
|
|
60
|
+
"run_id": short_id(r.get("run_id", "")),
|
|
61
|
+
"status": r.get("status", ""),
|
|
62
|
+
"started_at": _format_scalar(r.get("started_at", "")),
|
|
63
|
+
}
|
|
64
|
+
for r in data.get("runs", [])
|
|
65
|
+
],
|
|
66
|
+
["run_id", "status", "started_at"],
|
|
67
|
+
)
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if kind == "claims" and isinstance(data, Mapping):
|
|
71
|
+
print_table(
|
|
72
|
+
[
|
|
73
|
+
{
|
|
74
|
+
"claim_id": short_id(c.get("claim_id", "")),
|
|
75
|
+
"status": c.get("status", ""),
|
|
76
|
+
"predicate": c.get("predicate", ""),
|
|
77
|
+
}
|
|
78
|
+
for c in data.get("claims", [])
|
|
79
|
+
],
|
|
80
|
+
["claim_id", "status", "predicate"],
|
|
81
|
+
)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
if kind == "trace" and isinstance(data, Mapping):
|
|
85
|
+
records = data.get("context_used", [])
|
|
86
|
+
if records:
|
|
87
|
+
sys.stdout.write("context used\n")
|
|
88
|
+
print_table(
|
|
89
|
+
[
|
|
90
|
+
{
|
|
91
|
+
"event": short_id(r.get("event_id", "")),
|
|
92
|
+
"type": r.get("event_type", ""),
|
|
93
|
+
"query": (r.get("query") or "")[:24],
|
|
94
|
+
"items": str(len(r.get("items", []))),
|
|
95
|
+
}
|
|
96
|
+
for r in records
|
|
97
|
+
],
|
|
98
|
+
["event", "type", "query", "items"],
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
sys.stdout.write("(no context_used records)\n")
|
|
102
|
+
sys.stdout.write(
|
|
103
|
+
f"events={len(data.get('events', []))} "
|
|
104
|
+
f"observations={len(data.get('observations', []))} "
|
|
105
|
+
f"claims={len(data.get('claims', []))}\n"
|
|
106
|
+
)
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
if kind == "entities" and isinstance(data, Mapping):
|
|
110
|
+
print_table(
|
|
111
|
+
[
|
|
112
|
+
{
|
|
113
|
+
"entity_type": e.get("entity_type", ""),
|
|
114
|
+
"entity_id": short_id(e.get("entity_id", "")),
|
|
115
|
+
"label": (e.get("label") or "")[:40],
|
|
116
|
+
}
|
|
117
|
+
for e in data.get("entities", [])
|
|
118
|
+
],
|
|
119
|
+
["entity_type", "entity_id", "label"],
|
|
120
|
+
)
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
if isinstance(data, Mapping):
|
|
124
|
+
print_kv_table({key: _format_scalar(value) for key, value in data.items()})
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
emit(data, output="table")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _render_state(data: Mapping[str, Any]) -> None:
|
|
131
|
+
header = {
|
|
132
|
+
"workspace_id": short_id(data.get("workspace_id", "")),
|
|
133
|
+
"project_id": short_id(data.get("project_id", "")),
|
|
134
|
+
}
|
|
135
|
+
if "as_of" in data:
|
|
136
|
+
header["as_of"] = _format_scalar(data["as_of"])
|
|
137
|
+
print_kv_table(header)
|
|
138
|
+
|
|
139
|
+
claims = data.get("claims", [])
|
|
140
|
+
if claims:
|
|
141
|
+
sys.stdout.write("\nclaims\n")
|
|
142
|
+
print_table(
|
|
143
|
+
[
|
|
144
|
+
{
|
|
145
|
+
"claim_id": short_id(c.get("claim_id", "")),
|
|
146
|
+
"status": c.get("status", ""),
|
|
147
|
+
"predicate": c.get("predicate", ""),
|
|
148
|
+
}
|
|
149
|
+
for c in claims
|
|
150
|
+
],
|
|
151
|
+
["claim_id", "status", "predicate"],
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
observations = data.get("observations", [])
|
|
155
|
+
if observations:
|
|
156
|
+
sys.stdout.write("\nobservations\n")
|
|
157
|
+
print_table(
|
|
158
|
+
[
|
|
159
|
+
{
|
|
160
|
+
"observation_id": short_id(o.get("observation_id", "")),
|
|
161
|
+
"type": o.get("observation_type", ""),
|
|
162
|
+
"summary": (o.get("summary") or "")[:48],
|
|
163
|
+
}
|
|
164
|
+
for o in observations
|
|
165
|
+
],
|
|
166
|
+
["observation_id", "type", "summary"],
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
if not claims and not observations:
|
|
170
|
+
sys.stdout.write("(no claims or observations)\n")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _format_scalar(value: Any) -> str:
|
|
174
|
+
if value is None:
|
|
175
|
+
return ""
|
|
176
|
+
if isinstance(value, (dict, list)):
|
|
177
|
+
return json.dumps(value, default=str)[:80]
|
|
178
|
+
return str(value)
|
graphe_cli/main.py
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from typer import Context
|
|
12
|
+
|
|
13
|
+
from graphe_sdk import AuthError, ConflictError, GrapheError, NotFoundError
|
|
14
|
+
from graphe_cli.context import CliContext, build_context
|
|
15
|
+
from graphe_cli.display import render
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(
|
|
18
|
+
name="graphe",
|
|
19
|
+
help="Graphe CLI - query and update shared agent state.",
|
|
20
|
+
no_args_is_help=True,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
runs_app = typer.Typer(help="Agent runs.")
|
|
24
|
+
claims_app = typer.Typer(help="Claims lifecycle.")
|
|
25
|
+
entities_app = typer.Typer(help="Entities.")
|
|
26
|
+
events_app = typer.Typer(help="Event ingestion.")
|
|
27
|
+
annotations_app = typer.Typer(help="Human annotations.")
|
|
28
|
+
|
|
29
|
+
app.add_typer(runs_app, name="runs")
|
|
30
|
+
app.add_typer(claims_app, name="claims")
|
|
31
|
+
app.add_typer(entities_app, name="entities")
|
|
32
|
+
app.add_typer(events_app, name="events")
|
|
33
|
+
app.add_typer(annotations_app, name="annotations")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _ctx(ctx: Context) -> CliContext:
|
|
37
|
+
if ctx.obj is None:
|
|
38
|
+
raise typer.Exit(code=2)
|
|
39
|
+
return ctx.obj
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _handle_api_error(exc: GrapheError) -> None:
|
|
43
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
44
|
+
raise typer.Exit(code=1) from exc
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _scope_params(ctx: CliContext, workspace: str | None, project: str | None) -> dict[str, str]:
|
|
48
|
+
ws = workspace or ctx.config.workspace_id
|
|
49
|
+
proj = project or ctx.config.project_id
|
|
50
|
+
if not ws or not proj:
|
|
51
|
+
raise typer.BadParameter("workspace and project are required (flags or GRAPHE_* env).")
|
|
52
|
+
return {"workspace_id": ws, "project_id": proj}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _read_json(value: str | None, path: Path | None) -> dict[str, Any]:
|
|
56
|
+
if path:
|
|
57
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
58
|
+
if value:
|
|
59
|
+
return json.loads(value)
|
|
60
|
+
raise typer.BadParameter("Provide --json or --file.")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.callback()
|
|
64
|
+
def main(
|
|
65
|
+
ctx: Context,
|
|
66
|
+
base_url: Optional[str] = typer.Option(None, "--base-url", help="Graphe API base URL."),
|
|
67
|
+
api_key: Optional[str] = typer.Option(None, "--api-key", "-k", help="API key."),
|
|
68
|
+
workspace: Optional[str] = typer.Option(None, "--workspace", help="Default workspace ID."),
|
|
69
|
+
project: Optional[str] = typer.Option(None, "--project", help="Default project ID."),
|
|
70
|
+
actor: Optional[str] = typer.Option(None, "--actor", help="Default actor ID."),
|
|
71
|
+
config: Optional[Path] = typer.Option(None, "--config", help="Path to graphe.toml."),
|
|
72
|
+
output: str = typer.Option("table", "--output", help="Output format: table or json."),
|
|
73
|
+
) -> None:
|
|
74
|
+
if output not in {"table", "json"}:
|
|
75
|
+
raise typer.BadParameter("--output must be 'table' or 'json'.")
|
|
76
|
+
ctx.obj = build_context(
|
|
77
|
+
base_url=base_url,
|
|
78
|
+
api_key=api_key,
|
|
79
|
+
workspace_id=workspace,
|
|
80
|
+
project_id=project,
|
|
81
|
+
actor_id=actor,
|
|
82
|
+
config_path=config,
|
|
83
|
+
output=output,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.command("health")
|
|
88
|
+
def health(ctx: Context) -> None:
|
|
89
|
+
"""Check API health."""
|
|
90
|
+
cli = _ctx(ctx)
|
|
91
|
+
try:
|
|
92
|
+
result = cli.client.get_health()
|
|
93
|
+
except GrapheError as exc:
|
|
94
|
+
_handle_api_error(exc)
|
|
95
|
+
render(cli.output, result, kind="kv")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@app.command("state")
|
|
99
|
+
def state_current(
|
|
100
|
+
ctx: Context,
|
|
101
|
+
workspace: Optional[str] = typer.Option(None, "--workspace"),
|
|
102
|
+
project: Optional[str] = typer.Option(None, "--project"),
|
|
103
|
+
entity_type: Optional[str] = typer.Option(None, "--entity-type"),
|
|
104
|
+
entity_id: Optional[str] = typer.Option(None, "--entity-id"),
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Get current derived state."""
|
|
107
|
+
cli = _ctx(ctx)
|
|
108
|
+
params = _scope_params(cli, workspace, project)
|
|
109
|
+
if entity_type:
|
|
110
|
+
params["entity_type"] = entity_type
|
|
111
|
+
if entity_id:
|
|
112
|
+
params["entity_id"] = entity_id
|
|
113
|
+
try:
|
|
114
|
+
result = cli.client.get_current_state(**params)
|
|
115
|
+
except GrapheError as exc:
|
|
116
|
+
_handle_api_error(exc)
|
|
117
|
+
render(cli.output, result, kind="state")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@app.command("history")
|
|
121
|
+
def state_history(
|
|
122
|
+
ctx: Context,
|
|
123
|
+
as_of: str = typer.Option(..., "--as-of", help="ISO-8601 timestamp."),
|
|
124
|
+
workspace: Optional[str] = typer.Option(None, "--workspace"),
|
|
125
|
+
project: Optional[str] = typer.Option(None, "--project"),
|
|
126
|
+
entity_type: Optional[str] = typer.Option(None, "--entity-type"),
|
|
127
|
+
entity_id: Optional[str] = typer.Option(None, "--entity-id"),
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Get historical state at a point in time."""
|
|
130
|
+
cli = _ctx(ctx)
|
|
131
|
+
params = {**_scope_params(cli, workspace, project), "as_of": as_of}
|
|
132
|
+
if entity_type:
|
|
133
|
+
params["entity_type"] = entity_type
|
|
134
|
+
if entity_id:
|
|
135
|
+
params["entity_id"] = entity_id
|
|
136
|
+
try:
|
|
137
|
+
result = cli.client.get_history(**params)
|
|
138
|
+
except GrapheError as exc:
|
|
139
|
+
_handle_api_error(exc)
|
|
140
|
+
render(cli.output, result, kind="state")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@app.command("search")
|
|
144
|
+
def search(
|
|
145
|
+
ctx: Context,
|
|
146
|
+
query: str = typer.Option(..., "--query", "-q"),
|
|
147
|
+
mode: Optional[str] = typer.Option(None, "--mode", help="graph_first, vector_first, or balanced."),
|
|
148
|
+
limit: int = typer.Option(10, "--limit", help="Max results (maps to API top_k)."),
|
|
149
|
+
workspace: Optional[str] = typer.Option(None, "--workspace"),
|
|
150
|
+
project: Optional[str] = typer.Option(None, "--project"),
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Hybrid graph + vector search."""
|
|
153
|
+
cli = _ctx(ctx)
|
|
154
|
+
scope = _scope_params(cli, workspace, project)
|
|
155
|
+
payload = {"query": query, "top_k": limit, **scope}
|
|
156
|
+
try:
|
|
157
|
+
result = cli.client.hybrid_search(payload, mode=mode)
|
|
158
|
+
except GrapheError as exc:
|
|
159
|
+
_handle_api_error(exc)
|
|
160
|
+
render(cli.output, result, kind="search")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@runs_app.command("list")
|
|
164
|
+
def runs_list(
|
|
165
|
+
ctx: Context,
|
|
166
|
+
limit: int = typer.Option(20, "--limit"),
|
|
167
|
+
cursor: Optional[str] = typer.Option(None, "--cursor"),
|
|
168
|
+
workspace: Optional[str] = typer.Option(None, "--workspace"),
|
|
169
|
+
project: Optional[str] = typer.Option(None, "--project"),
|
|
170
|
+
) -> None:
|
|
171
|
+
cli = _ctx(ctx)
|
|
172
|
+
params = {**_scope_params(cli, workspace, project), "limit": limit}
|
|
173
|
+
if cursor:
|
|
174
|
+
params["cursor"] = cursor
|
|
175
|
+
try:
|
|
176
|
+
result = cli.client.list_runs(**params)
|
|
177
|
+
except GrapheError as exc:
|
|
178
|
+
_handle_api_error(exc)
|
|
179
|
+
render(cli.output, result, kind="runs")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@runs_app.command("get")
|
|
183
|
+
def runs_get(ctx: Context, run_id: str) -> None:
|
|
184
|
+
cli = _ctx(ctx)
|
|
185
|
+
try:
|
|
186
|
+
result = cli.client.get_run(run_id)
|
|
187
|
+
except GrapheError as exc:
|
|
188
|
+
_handle_api_error(exc)
|
|
189
|
+
render(cli.output, result)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@runs_app.command("trace")
|
|
193
|
+
def runs_trace(ctx: Context, run_id: str) -> None:
|
|
194
|
+
cli = _ctx(ctx)
|
|
195
|
+
try:
|
|
196
|
+
result = cli.client.get_trace(run_id)
|
|
197
|
+
except GrapheError as exc:
|
|
198
|
+
_handle_api_error(exc)
|
|
199
|
+
render(cli.output, result, kind="trace")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@runs_app.command("create")
|
|
203
|
+
def runs_create(
|
|
204
|
+
ctx: Context,
|
|
205
|
+
json_body: Optional[str] = typer.Option(None, "--json"),
|
|
206
|
+
file: Optional[Path] = typer.Option(None, "--file"),
|
|
207
|
+
workspace: Optional[str] = typer.Option(None, "--workspace"),
|
|
208
|
+
project: Optional[str] = typer.Option(None, "--project"),
|
|
209
|
+
actor: Optional[str] = typer.Option(None, "--actor"),
|
|
210
|
+
goal: Optional[str] = typer.Option(None, "--goal"),
|
|
211
|
+
) -> None:
|
|
212
|
+
cli = _ctx(ctx)
|
|
213
|
+
if json_body or file:
|
|
214
|
+
payload = _read_json(json_body, file)
|
|
215
|
+
else:
|
|
216
|
+
scope = _scope_params(cli, workspace, project)
|
|
217
|
+
actor_id = actor or cli.config.actor_id or str(uuid.uuid4())
|
|
218
|
+
if not goal:
|
|
219
|
+
raise typer.BadParameter("Provide --goal or --json/--file.")
|
|
220
|
+
payload = {
|
|
221
|
+
**scope,
|
|
222
|
+
"actor_id": actor_id,
|
|
223
|
+
"goal": goal,
|
|
224
|
+
"metadata": {},
|
|
225
|
+
}
|
|
226
|
+
try:
|
|
227
|
+
result = cli.client.create_run(payload)
|
|
228
|
+
except GrapheError as exc:
|
|
229
|
+
_handle_api_error(exc)
|
|
230
|
+
render(cli.output, result)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@claims_app.command("list")
|
|
234
|
+
def claims_list(
|
|
235
|
+
ctx: Context,
|
|
236
|
+
limit: int = typer.Option(20, "--limit"),
|
|
237
|
+
status: Optional[str] = typer.Option(None, "--status"),
|
|
238
|
+
workspace: Optional[str] = typer.Option(None, "--workspace"),
|
|
239
|
+
project: Optional[str] = typer.Option(None, "--project"),
|
|
240
|
+
) -> None:
|
|
241
|
+
cli = _ctx(ctx)
|
|
242
|
+
params: dict[str, Any] = {**_scope_params(cli, workspace, project), "limit": limit}
|
|
243
|
+
if status:
|
|
244
|
+
params["status"] = status
|
|
245
|
+
try:
|
|
246
|
+
result = cli.client.list_claims(**params)
|
|
247
|
+
except GrapheError as exc:
|
|
248
|
+
_handle_api_error(exc)
|
|
249
|
+
render(cli.output, result, kind="claims")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@claims_app.command("get")
|
|
253
|
+
def claims_get(ctx: Context, claim_id: str) -> None:
|
|
254
|
+
cli = _ctx(ctx)
|
|
255
|
+
try:
|
|
256
|
+
result = cli.client.get_claim(claim_id)
|
|
257
|
+
except GrapheError as exc:
|
|
258
|
+
_handle_api_error(exc)
|
|
259
|
+
render(cli.output, result)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@claims_app.command("propose")
|
|
263
|
+
def claims_propose(
|
|
264
|
+
ctx: Context,
|
|
265
|
+
json_body: Optional[str] = typer.Option(None, "--json"),
|
|
266
|
+
file: Optional[Path] = typer.Option(None, "--file"),
|
|
267
|
+
) -> None:
|
|
268
|
+
cli = _ctx(ctx)
|
|
269
|
+
payload = _read_json(json_body, file)
|
|
270
|
+
try:
|
|
271
|
+
result = cli.client.propose_claim(payload)
|
|
272
|
+
except GrapheError as exc:
|
|
273
|
+
_handle_api_error(exc)
|
|
274
|
+
render(cli.output, result)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@claims_app.command("confirm")
|
|
278
|
+
def claims_confirm(ctx: Context, claim_id: str) -> None:
|
|
279
|
+
cli = _ctx(ctx)
|
|
280
|
+
try:
|
|
281
|
+
result = cli.client.confirm_claim(claim_id)
|
|
282
|
+
except GrapheError as exc:
|
|
283
|
+
_handle_api_error(exc)
|
|
284
|
+
render(cli.output, result)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@claims_app.command("invalidate")
|
|
288
|
+
def claims_invalidate(ctx: Context, claim_id: str) -> None:
|
|
289
|
+
cli = _ctx(ctx)
|
|
290
|
+
try:
|
|
291
|
+
result = cli.client.invalidate_claim(claim_id)
|
|
292
|
+
except GrapheError as exc:
|
|
293
|
+
_handle_api_error(exc)
|
|
294
|
+
render(cli.output, result)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@claims_app.command("supersede")
|
|
298
|
+
def claims_supersede(
|
|
299
|
+
ctx: Context,
|
|
300
|
+
claim_id: str,
|
|
301
|
+
json_body: Optional[str] = typer.Option(None, "--json"),
|
|
302
|
+
file: Optional[Path] = typer.Option(None, "--file"),
|
|
303
|
+
) -> None:
|
|
304
|
+
cli = _ctx(ctx)
|
|
305
|
+
payload = _read_json(json_body, file)
|
|
306
|
+
try:
|
|
307
|
+
result = cli.client.supersede_claim(claim_id, payload)
|
|
308
|
+
except GrapheError as exc:
|
|
309
|
+
_handle_api_error(exc)
|
|
310
|
+
render(cli.output, result)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@entities_app.command("list")
|
|
314
|
+
def entities_list(
|
|
315
|
+
ctx: Context,
|
|
316
|
+
limit: int = typer.Option(20, "--limit"),
|
|
317
|
+
entity_type: Optional[str] = typer.Option(None, "--entity-type"),
|
|
318
|
+
workspace: Optional[str] = typer.Option(None, "--workspace"),
|
|
319
|
+
project: Optional[str] = typer.Option(None, "--project"),
|
|
320
|
+
) -> None:
|
|
321
|
+
cli = _ctx(ctx)
|
|
322
|
+
params: dict[str, Any] = {**_scope_params(cli, workspace, project), "limit": limit}
|
|
323
|
+
if entity_type:
|
|
324
|
+
params["entity_type"] = entity_type
|
|
325
|
+
try:
|
|
326
|
+
result = cli.client.list_entities(**params)
|
|
327
|
+
except GrapheError as exc:
|
|
328
|
+
_handle_api_error(exc)
|
|
329
|
+
render(cli.output, result, kind="entities")
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@entities_app.command("get")
|
|
333
|
+
def entities_get(ctx: Context, entity_type: str, entity_id: str) -> None:
|
|
334
|
+
cli = _ctx(ctx)
|
|
335
|
+
try:
|
|
336
|
+
result = cli.client.get_entity(entity_type, entity_id)
|
|
337
|
+
except GrapheError as exc:
|
|
338
|
+
_handle_api_error(exc)
|
|
339
|
+
emit(result, output=cli.output)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@entities_app.command("subgraph")
|
|
343
|
+
def entities_subgraph(ctx: Context, entity_type: str, entity_id: str) -> None:
|
|
344
|
+
cli = _ctx(ctx)
|
|
345
|
+
try:
|
|
346
|
+
result = cli.client.get_entity_subgraph(entity_type, entity_id)
|
|
347
|
+
except GrapheError as exc:
|
|
348
|
+
_handle_api_error(exc)
|
|
349
|
+
render(cli.output, result)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@events_app.command("log")
|
|
353
|
+
def events_log(
|
|
354
|
+
ctx: Context,
|
|
355
|
+
json_body: Optional[str] = typer.Option(None, "--json"),
|
|
356
|
+
file: Optional[Path] = typer.Option(None, "--file"),
|
|
357
|
+
idempotency_key: Optional[str] = typer.Option(None, "--idempotency-key"),
|
|
358
|
+
event_type: Optional[str] = typer.Option(None, "--event-type"),
|
|
359
|
+
summary: Optional[str] = typer.Option(None, "--summary"),
|
|
360
|
+
workspace: Optional[str] = typer.Option(None, "--workspace"),
|
|
361
|
+
project: Optional[str] = typer.Option(None, "--project"),
|
|
362
|
+
actor: Optional[str] = typer.Option(None, "--actor"),
|
|
363
|
+
) -> None:
|
|
364
|
+
cli = _ctx(ctx)
|
|
365
|
+
if json_body or file:
|
|
366
|
+
payload = _read_json(json_body, file)
|
|
367
|
+
else:
|
|
368
|
+
scope = _scope_params(cli, workspace, project)
|
|
369
|
+
actor_id = actor or cli.config.actor_id
|
|
370
|
+
if not actor_id or not event_type:
|
|
371
|
+
raise typer.BadParameter("Provide --json/--file or --actor, --event-type, and scope.")
|
|
372
|
+
payload = {
|
|
373
|
+
**scope,
|
|
374
|
+
"actor_id": actor_id,
|
|
375
|
+
"event_type": event_type,
|
|
376
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
377
|
+
"payload": {"summary": summary or ""},
|
|
378
|
+
}
|
|
379
|
+
if idempotency_key:
|
|
380
|
+
payload["idempotency_key"] = idempotency_key
|
|
381
|
+
try:
|
|
382
|
+
result = cli.client.log_event(payload)
|
|
383
|
+
except ConflictError as exc:
|
|
384
|
+
typer.secho(f"Conflict (idempotency): {exc}", fg=typer.colors.YELLOW, err=True)
|
|
385
|
+
raise typer.Exit(code=2) from exc
|
|
386
|
+
except GrapheError as exc:
|
|
387
|
+
_handle_api_error(exc)
|
|
388
|
+
render(cli.output, result)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@annotations_app.command("create")
|
|
392
|
+
def annotations_create(
|
|
393
|
+
ctx: Context,
|
|
394
|
+
json_body: Optional[str] = typer.Option(None, "--json"),
|
|
395
|
+
file: Optional[Path] = typer.Option(None, "--file"),
|
|
396
|
+
idempotency_key: Optional[str] = typer.Option(None, "--idempotency-key"),
|
|
397
|
+
) -> None:
|
|
398
|
+
cli = _ctx(ctx)
|
|
399
|
+
payload = _read_json(json_body, file)
|
|
400
|
+
if idempotency_key:
|
|
401
|
+
payload["idempotency_key"] = idempotency_key
|
|
402
|
+
try:
|
|
403
|
+
result = cli.client.create_annotation(payload)
|
|
404
|
+
except ConflictError as exc:
|
|
405
|
+
typer.secho(f"Conflict (idempotency): {exc}", fg=typer.colors.YELLOW, err=True)
|
|
406
|
+
raise typer.Exit(code=2) from exc
|
|
407
|
+
except GrapheError as exc:
|
|
408
|
+
_handle_api_error(exc)
|
|
409
|
+
render(cli.output, result)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@app.command("observation")
|
|
413
|
+
def observation_get(ctx: Context, observation_id: str) -> None:
|
|
414
|
+
cli = _ctx(ctx)
|
|
415
|
+
try:
|
|
416
|
+
result = cli.client.get_observation(observation_id)
|
|
417
|
+
except GrapheError as exc:
|
|
418
|
+
_handle_api_error(exc)
|
|
419
|
+
render(cli.output, result)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def run() -> None:
|
|
423
|
+
try:
|
|
424
|
+
app()
|
|
425
|
+
except typer.Exit:
|
|
426
|
+
raise
|
|
427
|
+
except KeyboardInterrupt:
|
|
428
|
+
sys.exit(130)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
if __name__ == "__main__":
|
|
432
|
+
run()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: graphe-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Command-line interface for the Graphe API
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: graphe-sdk>=0.1.1
|
|
8
|
+
Requires-Dist: typer>=0.12.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
11
|
+
|
|
12
|
+
# graphe-cli
|
|
13
|
+
|
|
14
|
+
Command-line interface for [Graphe](https://graphe.dev) — query shared agent memory, search project context, and ingest events.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install graphe-cli
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
Environment variables:
|
|
25
|
+
|
|
26
|
+
| Variable | Purpose |
|
|
27
|
+
|----------|---------|
|
|
28
|
+
| `GRAPHE_API_BASE` | API URL (default `http://localhost:8000`) |
|
|
29
|
+
| `GRAPHE_API_KEY` | API key from the Graphe console |
|
|
30
|
+
| `GRAPHE_WORKSPACE_ID` | Default workspace |
|
|
31
|
+
| `GRAPHE_PROJECT_ID` | Default project |
|
|
32
|
+
| `GRAPHE_ACTOR_ID` | Default actor for ingestion |
|
|
33
|
+
|
|
34
|
+
Optional config file: `~/.config/graphe/config.toml` or `./graphe.toml`.
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
graphe health
|
|
40
|
+
graphe --output json state
|
|
41
|
+
graphe search -q "authentication middleware"
|
|
42
|
+
graphe runs list --workspace <ws> --project <proj>
|
|
43
|
+
graphe runs trace <run-id>
|
|
44
|
+
graphe events log --event-type agent.action --summary "Started refactor"
|
|
45
|
+
graphe claims propose --file claim.json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Use `--output json` for machine-readable output.
|
|
49
|
+
|
|
50
|
+
Claim lifecycle commands (`propose`, `confirm`, `invalidate`, `supersede`) are available in the CLI. The MCP server focuses on read tools and safe ingestion writes.
|
|
51
|
+
|
|
52
|
+
## Documentation
|
|
53
|
+
|
|
54
|
+
See [connecting clients](https://github.com/ShauryaVM/KG-Idea/blob/main/docs/connecting-clients.md) for cloud setup and API keys.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
graphe_cli/__init__.py,sha256=0Ixnz9x7480EiBcs2aSMYQk3ouciCVVgr7dEXI3Oqoc,18
|
|
2
|
+
graphe_cli/context.py,sha256=EgCWzwAj02TGjaSkdOz9PJDQcyf4QJqgi2TyJPpURXk,1070
|
|
3
|
+
graphe_cli/display.py,sha256=9F2iCEfaIY-DaIH3-hyyZa4Gbu7igoVVVFYSgndo4Pg,5682
|
|
4
|
+
graphe_cli/main.py,sha256=2n3bZuWZSBRUd-ew-faRXad_zywuR1vs9c9TR43CGN0,13972
|
|
5
|
+
graphe_cli-0.1.1.dist-info/METADATA,sha256=Vy_Jv88_LXHCCsV2OHZ4aQC0lKD1rCGOZ-J-00ai940,1581
|
|
6
|
+
graphe_cli-0.1.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
+
graphe_cli-0.1.1.dist-info/entry_points.txt,sha256=jjDg0_u_PmEomvkom_8MSL5qVDf5wtdwjH0wSP0JZcQ,47
|
|
8
|
+
graphe_cli-0.1.1.dist-info/top_level.txt,sha256=SsW8o7DXmYfIAYzzb4b9mEEp-L_Jm1kRIGAfyhW5C6Y,11
|
|
9
|
+
graphe_cli-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
graphe_cli
|