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.
- honcho_cli/__init__.py +3 -0
- honcho_cli/_help.py +130 -0
- honcho_cli/branding.py +17 -0
- honcho_cli/commands/__init__.py +0 -0
- honcho_cli/commands/conclusion.py +219 -0
- honcho_cli/commands/config_cmd.py +31 -0
- honcho_cli/commands/message.py +161 -0
- honcho_cli/commands/peer.py +308 -0
- honcho_cli/commands/session.py +404 -0
- honcho_cli/commands/setup.py +287 -0
- honcho_cli/commands/workspace.py +322 -0
- honcho_cli/common.py +112 -0
- honcho_cli/config.py +150 -0
- honcho_cli/main.py +96 -0
- honcho_cli/output.py +104 -0
- honcho_cli/skills/CONTEXT.md +50 -0
- honcho_cli/skills/honcho-debug.md +54 -0
- honcho_cli/skills/honcho-inspect.md +53 -0
- honcho_cli/validation.py +44 -0
- honcho_cli-0.1.0.dist-info/METADATA +235 -0
- honcho_cli-0.1.0.dist-info/RECORD +23 -0
- honcho_cli-0.1.0.dist-info/WHEEL +4 -0
- honcho_cli-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -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)
|