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 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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ graphe = graphe_cli.main:app
@@ -0,0 +1 @@
1
+ graphe_cli