honcho-cli 0.1.0__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.
@@ -0,0 +1,308 @@
1
+ """Peer commands: list, inspect, card, chat, search, create, metadata, representation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Optional
7
+
8
+ import typer
9
+
10
+ from honcho.api_types import PeerConfig
11
+
12
+ from honcho_cli.commands.workspace import _config_to_dict, _handle_error, _raw_list
13
+ from honcho_cli.output import print_error, print_result, use_json
14
+ from honcho_cli.validation import validate_resource_id
15
+
16
+ from honcho_cli._help import HonchoTyperGroup
17
+ from honcho_cli.common import add_common_options, get_client, get_resolved_config, handle_cmd_flags
18
+
19
+ app = typer.Typer(cls=HonchoTyperGroup, help="List, create, chat with, search, and manage peers and their representations.")
20
+ add_common_options(app)
21
+
22
+
23
+ def _get_peer_id(peer_id: str | None) -> str:
24
+
25
+ config = get_resolved_config()
26
+ pid = peer_id or config.peer_id
27
+ if not pid:
28
+ if not config.workspace_id:
29
+ print_error(
30
+ "NO_SCOPE",
31
+ "No peer or workspace scoped. Pass --peer/-p and --workspace/-w, or set HONCHO_PEER_ID and HONCHO_WORKSPACE_ID.",
32
+ )
33
+ else:
34
+ print_error("NO_PEER", "No peer ID provided. Pass --peer/-p or set HONCHO_PEER_ID.")
35
+ raise typer.Exit(1)
36
+ return validate_resource_id(pid, "peer")
37
+
38
+
39
+ @app.command("list")
40
+ def list_peers(
41
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
42
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
43
+ ) -> None:
44
+ """List all peers in the workspace."""
45
+ handle_cmd_flags(json_output=json_output, workspace=workspace)
46
+ client, config = get_client()
47
+
48
+ try:
49
+ raw_peers = _raw_list(client.peers())
50
+ items = [
51
+ {
52
+ "id": p.id,
53
+ "metadata": p.metadata,
54
+ "configuration": _config_to_dict(p.configuration) if p.configuration else None,
55
+ "created_at": str(p.created_at),
56
+ }
57
+ for p in raw_peers
58
+ ]
59
+ print_result(items, columns=["id", "metadata", "created_at"], title="Peers")
60
+ except Exception as e:
61
+ _handle_error(e, "peer", "list")
62
+
63
+
64
+ @app.command()
65
+ def inspect(
66
+ peer_id: Optional[str] = typer.Argument(None, help="Peer ID (uses default if omitted)"),
67
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
68
+ peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
69
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
70
+ ) -> None:
71
+ """Inspect a peer: card, session count, recent conclusions."""
72
+
73
+ handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer)
74
+ pid = _get_peer_id(peer_id)
75
+ client, config = get_client()
76
+ p = client.peer(pid)
77
+
78
+ try:
79
+ card = p.get_card()
80
+ peer_config = p.get_configuration()
81
+ # First page only; SyncPage.total (when the server supplies it) is
82
+ # authoritative for counts without walking every page.
83
+ session_page = p.sessions()
84
+ conclusion_page = p.conclusions.list(size=10)
85
+
86
+ session_items = session_page.items
87
+ conclusion_items = conclusion_page.items
88
+
89
+ result = {
90
+ "id": pid,
91
+ "card": card,
92
+ "configuration": _config_to_dict(peer_config) if peer_config else None,
93
+ "session_count": session_page.total,
94
+ "conclusion_count": conclusion_page.total,
95
+ "recent_conclusions": [
96
+ {"id": c.id, "content": c.content if use_json() else c.content[:200], "created_at": str(c.created_at)}
97
+ for c in conclusion_items
98
+ ],
99
+ "sessions": [{"id": s.id} for s in session_items[:10]],
100
+ }
101
+ print_result(result)
102
+ except Exception as e:
103
+ _handle_error(e, "peer", pid)
104
+
105
+
106
+ @app.command()
107
+ def card(
108
+ peer_id: Optional[str] = typer.Argument(None, help="Peer ID (uses default if omitted)"),
109
+ target: Optional[str] = typer.Option(None, help="Target peer for relationship card"),
110
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
111
+ peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
112
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
113
+ ) -> None:
114
+ """Get raw peer card content."""
115
+
116
+ handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer)
117
+ pid = _get_peer_id(peer_id)
118
+ client, config = get_client()
119
+ p = client.peer(pid)
120
+
121
+ try:
122
+ result = p.get_card(target=target)
123
+ print_result({"peer_id": pid, "target": target, "card": result})
124
+ except Exception as e:
125
+ _handle_error(e, "peer", pid)
126
+
127
+
128
+ @app.command()
129
+ def chat(
130
+ query: str = typer.Argument(help="Question to ask about the peer"),
131
+ target: Optional[str] = typer.Option(None, help="Target peer for perspective"),
132
+ reasoning: Optional[str] = typer.Option(None, "--reasoning", "-r", help="Reasoning level: minimal, low, medium, high, max"),
133
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
134
+ peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
135
+ session: Optional[str] = typer.Option(None, "--session", "-s", help="Override session ID"),
136
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
137
+ ) -> None:
138
+ """Query the dialectic about a peer."""
139
+
140
+ _REASONING_LEVELS = ("minimal", "low", "medium", "high", "max")
141
+ if reasoning and reasoning not in _REASONING_LEVELS:
142
+ from honcho_cli.output import print_error
143
+ print_error("INVALID_REASONING", f"--reasoning must be one of: {', '.join(_REASONING_LEVELS)}")
144
+ raise typer.Exit(1)
145
+
146
+ handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer, session=session)
147
+ pid = _get_peer_id(None)
148
+ client, config = get_client()
149
+ p = client.peer(pid)
150
+
151
+ try:
152
+ response = p.chat(
153
+ query,
154
+ target=target,
155
+ session=config.session_id or None,
156
+ reasoning_level=reasoning or None,
157
+ )
158
+ print_result({"peer_id": pid, "query": query, "response": response})
159
+ except Exception as e:
160
+ _handle_error(e, "peer", pid)
161
+
162
+
163
+ @app.command()
164
+ def search(
165
+ query: str = typer.Argument(help="Search query"),
166
+ limit: int = typer.Option(10, help="Max results"),
167
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
168
+ peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
169
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
170
+ ) -> None:
171
+ """Search a peer's messages."""
172
+
173
+ handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer)
174
+ pid = _get_peer_id(None)
175
+ client, config = get_client()
176
+ p = client.peer(pid)
177
+
178
+ try:
179
+ results = p.search(query, limit=limit)
180
+ items = [
181
+ {
182
+ "id": m.id,
183
+ "content": m.content if use_json() else m.content[:200],
184
+ "session_id": m.session_id,
185
+ "created_at": str(m.created_at),
186
+ }
187
+ for m in results
188
+ ]
189
+ print_result(items, columns=["id", "session_id", "content", "created_at"], title=f"Peer search: {query}")
190
+ except Exception as e:
191
+ _handle_error(e, "peer", pid)
192
+
193
+
194
+ @app.command("create")
195
+ def create_peer(
196
+ peer_id: str = typer.Argument(help="Peer ID to create or get"),
197
+ observe_me: Optional[bool] = typer.Option(None, "--observe-me/--no-observe-me", help="Whether Honcho will form a representation of this peer"),
198
+ metadata: Optional[str] = typer.Option(None, "--metadata", help="JSON metadata to associate with the peer"),
199
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
200
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
201
+ ) -> None:
202
+ """Create or get a peer."""
203
+ handle_cmd_flags(json_output=json_output, workspace=workspace)
204
+ pid = validate_resource_id(peer_id, "peer")
205
+ client, config = get_client()
206
+
207
+ parsed_metadata = None
208
+ if metadata:
209
+ try:
210
+ parsed_metadata = json.loads(metadata)
211
+ except json.JSONDecodeError as e:
212
+ print_error("INVALID_JSON", f"--metadata must be valid JSON: {e}", {})
213
+ raise typer.Exit(1)
214
+
215
+ peer_config = PeerConfig(observe_me=observe_me) if observe_me is not None else None
216
+
217
+ try:
218
+ p = client.peer(pid, configuration=peer_config, metadata=parsed_metadata)
219
+ # Only round-trip to the server when the caller passed config or
220
+ # metadata — in that case get-or-create may have returned a
221
+ # pre-existing peer and the echoed output would lie. When no input
222
+ # was passed, skip the two extra API calls entirely.
223
+ result: dict[str, object] = {"peer_id": p.id}
224
+ if peer_config is not None or parsed_metadata is not None:
225
+ result["metadata"] = p.get_metadata()
226
+ result["configuration"] = _config_to_dict(p.get_configuration())
227
+ print_result(result)
228
+ except Exception as e:
229
+ _handle_error(e, "peer", pid)
230
+
231
+
232
+ @app.command("get-metadata")
233
+ def get_metadata(
234
+ peer_id: Optional[str] = typer.Argument(None, help="Peer ID (uses default if omitted)"),
235
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
236
+ peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
237
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
238
+ ) -> None:
239
+ """Get metadata for a peer."""
240
+
241
+ handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer)
242
+ pid = _get_peer_id(peer_id)
243
+ client, config = get_client()
244
+ p = client.peer(pid)
245
+
246
+ try:
247
+ result = p.get_metadata()
248
+ print_result({"peer_id": pid, "metadata": result})
249
+ except Exception as e:
250
+ _handle_error(e, "peer", pid)
251
+
252
+
253
+ @app.command("set-metadata")
254
+ def set_metadata(
255
+ metadata: str = typer.Argument(help="JSON metadata to set (e.g. '{\"key\": \"value\"}')"),
256
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
257
+ peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Peer ID (uses default if omitted)"),
258
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
259
+ ) -> None:
260
+ """Set metadata for a peer."""
261
+
262
+ handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer)
263
+ pid = _get_peer_id(None)
264
+ client, config = get_client()
265
+
266
+ try:
267
+ parsed = json.loads(metadata)
268
+ except json.JSONDecodeError as e:
269
+ print_error("INVALID_JSON", f"metadata must be valid JSON: {e}", {})
270
+ raise typer.Exit(1)
271
+
272
+ p = client.peer(pid)
273
+
274
+ try:
275
+ p.set_metadata(parsed)
276
+ print_result({"peer_id": pid, "metadata": parsed})
277
+ except Exception as e:
278
+ _handle_error(e, "peer", pid)
279
+
280
+
281
+ @app.command()
282
+ def representation(
283
+ peer_id: Optional[str] = typer.Argument(None, help="Peer ID (uses default if omitted)"),
284
+ target: Optional[str] = typer.Option(None, help="Target peer to get representation about"),
285
+ search_query: Optional[str] = typer.Option(None, help="Semantic search query to filter conclusions"),
286
+ max_conclusions: Optional[int] = typer.Option(None, help="Maximum number of conclusions to include"),
287
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
288
+ peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Override peer ID"),
289
+ session: Optional[str] = typer.Option(None, "--session", "-s", help="Override session ID"),
290
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
291
+ ) -> None:
292
+ """Get the formatted representation for a peer."""
293
+
294
+ handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer, session=session)
295
+ pid = _get_peer_id(peer_id)
296
+ client, config = get_client()
297
+ p = client.peer(pid)
298
+
299
+ try:
300
+ result = p.representation(
301
+ target=target,
302
+ session=config.session_id or None,
303
+ search_query=search_query,
304
+ max_conclusions=max_conclusions,
305
+ )
306
+ print_result({"peer_id": pid, "target": target, "representation": result})
307
+ except Exception as e:
308
+ _handle_error(e, "peer", pid)