moltgrid 0.2.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.
- moltgrid/__init__.py +5 -0
- moltgrid/__main__.py +4 -0
- moltgrid/cli.py +532 -0
- moltgrid/client.py +858 -0
- moltgrid/exceptions.py +8 -0
- moltgrid-0.2.0.dist-info/METADATA +93 -0
- moltgrid-0.2.0.dist-info/RECORD +9 -0
- moltgrid-0.2.0.dist-info/WHEEL +4 -0
- moltgrid-0.2.0.dist-info/entry_points.txt +2 -0
moltgrid/__init__.py
ADDED
moltgrid/__main__.py
ADDED
moltgrid/cli.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"""MoltGrid CLI — Clean terminal UI with Rich. No ASCII art. Just heat."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import shutil
|
|
11
|
+
|
|
12
|
+
from . import __version__
|
|
13
|
+
from .client import MoltGrid
|
|
14
|
+
from .exceptions import MoltGridError
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
from rich.text import Text
|
|
19
|
+
from rich.panel import Panel
|
|
20
|
+
from rich.table import Table
|
|
21
|
+
from rich.align import Align
|
|
22
|
+
from rich import box
|
|
23
|
+
from rich.style import Style
|
|
24
|
+
HAS_RICH = True
|
|
25
|
+
except ImportError:
|
|
26
|
+
HAS_RICH = False
|
|
27
|
+
|
|
28
|
+
VERSION = __version__
|
|
29
|
+
API_URL = "api.moltgrid.net"
|
|
30
|
+
LICENSE_TXT = "Apache 2.0"
|
|
31
|
+
TAGLINE = "Infrastructure for Autonomous Agents"
|
|
32
|
+
|
|
33
|
+
C = {
|
|
34
|
+
"red": "#E84142", "red_hi": "#FF5555", "red_mid": "#C73333", "red_dim": "#8B2222",
|
|
35
|
+
"red_dark": "#4A1111", "red_bg": "#1A0808", "white": "#E0E0E0", "muted": "#777777",
|
|
36
|
+
"dim": "#444444", "green": "#55FF88", "yellow": "#FFD644", "cyan": "#66D9EF",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_LOGO = [
|
|
40
|
+
r"███╗ ███╗ ██████╗ ██╗ ████████╗ ██████╗ ██████╗ ██╗██████╗ ",
|
|
41
|
+
r"████╗ ████║██╔═══██╗██║ ╚══██╔══╝██╔════╝ ██╔══██╗██║██╔══██╗",
|
|
42
|
+
r"██╔████╔██║██║ ██║██║ ██║ ██║ ███╗██████╔╝██║██║ ██║",
|
|
43
|
+
r"██║╚██╔╝██║██║ ██║██║ ██║ ██║ ██║██╔══██╗██║██║ ██║",
|
|
44
|
+
r"██║ ╚═╝ ██║╚██████╔╝███████╗██║ ╚██████╔╝██║ ██║██║██████╔╝",
|
|
45
|
+
r"╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═════╝",
|
|
46
|
+
]
|
|
47
|
+
_LG = [C["red_hi"], C["red_hi"], C["red"], C["red_mid"], C["red_dim"], C["red_dark"]]
|
|
48
|
+
|
|
49
|
+
if HAS_RICH:
|
|
50
|
+
import io
|
|
51
|
+
# Force UTF-8 on Windows to handle Unicode box-drawing chars
|
|
52
|
+
if sys.platform == "win32":
|
|
53
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
|
54
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
|
|
55
|
+
console = Console(highlight=False, force_terminal=True)
|
|
56
|
+
else:
|
|
57
|
+
console = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ── Rich UI components ────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
def _logo():
|
|
63
|
+
t = Text(justify="center")
|
|
64
|
+
for i, l in enumerate(_LOGO):
|
|
65
|
+
t.append(l, style=Style(color=_LG[i], bold=(i < 2)))
|
|
66
|
+
if i < len(_LOGO) - 1:
|
|
67
|
+
t.append("\n")
|
|
68
|
+
return t
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _sdot(s):
|
|
72
|
+
m = {
|
|
73
|
+
"operational": ("●", C["green"], "operational"),
|
|
74
|
+
"degraded": ("●", C["yellow"], "degraded"),
|
|
75
|
+
"down": ("●", "#FF4444", "down"),
|
|
76
|
+
"starting": ("◌", C["yellow"], "starting"),
|
|
77
|
+
"online": ("●", C["green"], "online"),
|
|
78
|
+
"offline": ("●", "#FF4444", "offline"),
|
|
79
|
+
}
|
|
80
|
+
d, c, l = m.get(s, ("●", C["muted"], s))
|
|
81
|
+
t = Text()
|
|
82
|
+
t.append(d, style=Style(color=c, bold=True))
|
|
83
|
+
t.append(f" {l}", style=Style(color=c))
|
|
84
|
+
return t
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _bar(width, color=C["red_dim"]):
|
|
88
|
+
return Text("\u2500" * width, style=Style(color=color), justify="center")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _compact_banner(status="operational"):
|
|
92
|
+
t = Text()
|
|
93
|
+
t.append(" ● ", style=Style(color=C["red"], bold=True))
|
|
94
|
+
t.append("MoltGrid", style=Style(color=C["red"], bold=True))
|
|
95
|
+
t.append(f" v{VERSION}", style=Style(color=C["muted"]))
|
|
96
|
+
t.append(" \u2502 ", style=Style(color=C["red_dark"]))
|
|
97
|
+
t.append_text(_sdot(status))
|
|
98
|
+
t.append(" \u2502 ", style=Style(color=C["red_dark"]))
|
|
99
|
+
t.append(API_URL, style=Style(color=C["dim"]))
|
|
100
|
+
console.print(Panel(t, border_style=Style(color=C["red_dark"]),
|
|
101
|
+
box=box.ROUNDED, padding=(0, 1)))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _full_banner(status="operational"):
|
|
105
|
+
w = min(shutil.get_terminal_size().columns, 80)
|
|
106
|
+
iw = w - 6
|
|
107
|
+
p = Text(justify="center")
|
|
108
|
+
p.append_text(_bar(iw, C["red"]))
|
|
109
|
+
p.append("\n\n")
|
|
110
|
+
p.append_text(_logo())
|
|
111
|
+
p.append("\n\n")
|
|
112
|
+
p.append(TAGLINE, style=Style(color=C["muted"], italic=True))
|
|
113
|
+
p.append("\n\n")
|
|
114
|
+
p.append_text(_bar(iw, C["red"]))
|
|
115
|
+
p.append("\n\n")
|
|
116
|
+
t = Text(justify="center")
|
|
117
|
+
t.append(f"v{VERSION}", style=Style(color=C["red"], bold=True))
|
|
118
|
+
t.append(" \u00b7 ", style=Style(color=C["dim"]))
|
|
119
|
+
t.append(API_URL, style=Style(color=C["muted"]))
|
|
120
|
+
t.append(" \u00b7 ", style=Style(color=C["dim"]))
|
|
121
|
+
t.append(LICENSE_TXT, style=Style(color=C["muted"]))
|
|
122
|
+
t.append(" \u00b7 ", style=Style(color=C["dim"]))
|
|
123
|
+
t.append_text(_sdot(status))
|
|
124
|
+
p.append_text(t)
|
|
125
|
+
console.print(Panel(Align.center(p), border_style=Style(color=C["red_dark"]),
|
|
126
|
+
box=box.HEAVY, padding=(1, 2), expand=True))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _error(title, msg):
|
|
130
|
+
t = Text()
|
|
131
|
+
t.append("\u2715 ", style=Style(color="#FF4444", bold=True))
|
|
132
|
+
t.append(title, style=Style(color="#FF4444", bold=True))
|
|
133
|
+
t.append(f"\n\n{msg}", style=Style(color=C["muted"]))
|
|
134
|
+
console.print(Panel(t, border_style=Style(color="#FF4444"),
|
|
135
|
+
box=box.ROUNDED, padding=(1, 2)))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _success(title, msg):
|
|
139
|
+
t = Text()
|
|
140
|
+
t.append("● ", style=Style(color=C["green"], bold=True))
|
|
141
|
+
t.append(title, style=Style(color=C["green"], bold=True))
|
|
142
|
+
t.append(f"\n\n{msg}", style=Style(color=C["muted"]))
|
|
143
|
+
console.print(Panel(t, border_style=Style(color=C["green"]),
|
|
144
|
+
box=box.ROUNDED, padding=(1, 2)))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _warn(title, msg):
|
|
148
|
+
t = Text()
|
|
149
|
+
t.append("\u25b2 ", style=Style(color=C["yellow"], bold=True))
|
|
150
|
+
t.append(title, style=Style(color=C["yellow"], bold=True))
|
|
151
|
+
t.append(f"\n\n{msg}", style=Style(color=C["muted"]))
|
|
152
|
+
console.print(Panel(t, border_style=Style(color=C["yellow"]),
|
|
153
|
+
box=box.ROUNDED, padding=(1, 2)))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
def _get_client():
|
|
159
|
+
key = os.environ.get("MOLTGRID_API_KEY", "")
|
|
160
|
+
base = os.environ.get("MOLTGRID_BASE_URL", "https://api.moltgrid.net")
|
|
161
|
+
if not key:
|
|
162
|
+
if HAS_RICH:
|
|
163
|
+
_error("No API Key", "Set MOLTGRID_API_KEY environment variable.\n\n export MOLTGRID_API_KEY=af_your_key_here")
|
|
164
|
+
else:
|
|
165
|
+
print("Error: MOLTGRID_API_KEY not set.\n\n export MOLTGRID_API_KEY=af_your_key_here")
|
|
166
|
+
sys.exit(1)
|
|
167
|
+
return MoltGrid(api_key=key, base_url=base)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _metric(label, value, color=C["white"]):
|
|
171
|
+
t = Text()
|
|
172
|
+
t.append(f" {value}", style=Style(color=color, bold=True))
|
|
173
|
+
t.append(f"\n {label}", style=Style(color=C["muted"]))
|
|
174
|
+
return Panel(t, border_style=Style(color=C["red_dark"]),
|
|
175
|
+
box=box.ROUNDED, padding=(0, 1), expand=True)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ── Commands ──────────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
def cmd_default(args):
|
|
181
|
+
"""Show full splash banner."""
|
|
182
|
+
_full_banner()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def cmd_health(args):
|
|
186
|
+
"""Check API health."""
|
|
187
|
+
import requests as _req
|
|
188
|
+
base = os.environ.get("MOLTGRID_BASE_URL", "https://api.moltgrid.net")
|
|
189
|
+
try:
|
|
190
|
+
r = _req.get(f"{base}/v1/health", timeout=5)
|
|
191
|
+
data = r.json()
|
|
192
|
+
status = data.get("status", "unknown")
|
|
193
|
+
_compact_banner(status)
|
|
194
|
+
console.print()
|
|
195
|
+
tbl = Table(show_header=False, show_edge=False, box=None, padding=(0, 2), expand=True)
|
|
196
|
+
tbl.add_column(ratio=1)
|
|
197
|
+
tbl.add_column(ratio=1)
|
|
198
|
+
tbl.add_column(ratio=1)
|
|
199
|
+
tbl.add_row(
|
|
200
|
+
_metric("version", f"v{data.get('version', '?')}", C["red"]),
|
|
201
|
+
_metric("uptime", f"{data.get('uptime_pct', '—')}%", C["cyan"]),
|
|
202
|
+
_metric("agents", str(data.get("total_agents", "—")), C["white"]),
|
|
203
|
+
)
|
|
204
|
+
console.print(tbl)
|
|
205
|
+
except Exception as e:
|
|
206
|
+
_error("Connection Failed", f"Could not reach {base} \u2014 {e}")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def cmd_status(args):
|
|
210
|
+
"""Show agent status dashboard."""
|
|
211
|
+
mg = _get_client()
|
|
212
|
+
try:
|
|
213
|
+
stats = mg.stats()
|
|
214
|
+
profile = mg.profile()
|
|
215
|
+
status = profile.get("status", "offline")
|
|
216
|
+
header = Text()
|
|
217
|
+
header.append(" ● ", style=Style(color=C["red"], bold=True))
|
|
218
|
+
header.append("MoltGrid", style=Style(color=C["red"], bold=True))
|
|
219
|
+
header.append(f" v{VERSION}", style=Style(color=C["muted"]))
|
|
220
|
+
header.append(" \u2502 ", style=Style(color=C["red_dark"]))
|
|
221
|
+
header.append_text(_sdot(status))
|
|
222
|
+
|
|
223
|
+
tbl = Table(show_header=False, show_edge=False, box=None, padding=(0, 1), expand=True)
|
|
224
|
+
tbl.add_column(ratio=1)
|
|
225
|
+
tbl.add_column(ratio=1)
|
|
226
|
+
tbl.add_column(ratio=1)
|
|
227
|
+
tbl.add_column(ratio=1)
|
|
228
|
+
tbl.add_row(
|
|
229
|
+
_metric("memory keys", str(stats.get("memory_count", 0)), C["white"]),
|
|
230
|
+
_metric("messages", str(stats.get("message_count", 0)), C["green"]),
|
|
231
|
+
_metric("queue jobs", str(stats.get("queue_count", 0)), C["yellow"]),
|
|
232
|
+
_metric("webhooks", str(stats.get("webhook_count", 0)), C["cyan"]),
|
|
233
|
+
)
|
|
234
|
+
console.print(Panel(tbl, border_style=Style(color=C["red_dark"]), box=box.HEAVY,
|
|
235
|
+
padding=(0, 0), expand=True, title=header, title_align="left"))
|
|
236
|
+
console.print()
|
|
237
|
+
# Agent info
|
|
238
|
+
info = Table(show_header=False, show_edge=False, box=None, padding=(0, 2))
|
|
239
|
+
info.add_column(style=Style(color=C["muted"]), width=16)
|
|
240
|
+
info.add_column(style=Style(color=C["white"]))
|
|
241
|
+
info.add_row("Agent ID", profile.get("agent_id", "unknown"))
|
|
242
|
+
info.add_row("Name", profile.get("name", "unnamed"))
|
|
243
|
+
info.add_row("Reputation", str(profile.get("reputation", 0)))
|
|
244
|
+
info.add_row("Credits", str(profile.get("credits", 0)))
|
|
245
|
+
console.print(info)
|
|
246
|
+
except MoltGridError as e:
|
|
247
|
+
_error("Status Error", str(e))
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def cmd_register(args):
|
|
251
|
+
"""Register a new agent."""
|
|
252
|
+
base = os.environ.get("MOLTGRID_BASE_URL", "https://api.moltgrid.net")
|
|
253
|
+
try:
|
|
254
|
+
result = MoltGrid.register(name=args.name, base_url=base)
|
|
255
|
+
_success("Agent Registered", f"Name: {args.name}\nAgent ID: {result.get('agent_id', '')}\nAPI Key: {result.get('api_key', '')}\n\nSave your API key \u2014 it is shown only once.\n\n export MOLTGRID_API_KEY={result.get('api_key', 'af_...')}")
|
|
256
|
+
except Exception as e:
|
|
257
|
+
_error("Registration Failed", str(e))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def cmd_get(args):
|
|
261
|
+
"""Get a memory value."""
|
|
262
|
+
mg = _get_client()
|
|
263
|
+
try:
|
|
264
|
+
result = mg.memory_get(args.key, namespace=args.namespace)
|
|
265
|
+
console.print_json(json.dumps(result))
|
|
266
|
+
except MoltGridError as e:
|
|
267
|
+
_error("Memory Error", str(e))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def cmd_set(args):
|
|
271
|
+
"""Set a memory value."""
|
|
272
|
+
mg = _get_client()
|
|
273
|
+
try:
|
|
274
|
+
mg.memory_set(args.key, args.value, namespace=args.namespace)
|
|
275
|
+
_success("Stored", f"Key: {args.key}")
|
|
276
|
+
except MoltGridError as e:
|
|
277
|
+
_error("Memory Error", str(e))
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def cmd_keys(args):
|
|
281
|
+
"""List memory keys."""
|
|
282
|
+
mg = _get_client()
|
|
283
|
+
try:
|
|
284
|
+
result = mg.memory_list(namespace=args.namespace)
|
|
285
|
+
keys = result.get("keys", [])
|
|
286
|
+
if not keys:
|
|
287
|
+
_warn("Empty", "No memory keys found.")
|
|
288
|
+
return
|
|
289
|
+
tbl = Table(border_style=Style(color=C["red_dark"]), box=box.SIMPLE_HEAVY,
|
|
290
|
+
header_style=Style(color=C["red"], bold=True), expand=True)
|
|
291
|
+
tbl.add_column("Key", style=Style(color=C["white"]))
|
|
292
|
+
tbl.add_column("Namespace", style=Style(color=C["muted"]))
|
|
293
|
+
for k in keys:
|
|
294
|
+
name = k.get("key", str(k)) if isinstance(k, dict) else str(k)
|
|
295
|
+
tbl.add_row(name, args.namespace)
|
|
296
|
+
console.print(tbl)
|
|
297
|
+
except MoltGridError as e:
|
|
298
|
+
_error("Memory Error", str(e))
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def cmd_send(args):
|
|
302
|
+
"""Send a message to another agent."""
|
|
303
|
+
mg = _get_client()
|
|
304
|
+
try:
|
|
305
|
+
payload = json.loads(args.payload) if args.payload.startswith("{") else {"text": args.payload}
|
|
306
|
+
mg.send_message(args.to, payload)
|
|
307
|
+
_success("Message Sent", f"To: {args.to}")
|
|
308
|
+
except MoltGridError as e:
|
|
309
|
+
_error("Send Error", str(e))
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def cmd_inbox(args):
|
|
313
|
+
"""Check message inbox."""
|
|
314
|
+
mg = _get_client()
|
|
315
|
+
try:
|
|
316
|
+
result = mg.inbox()
|
|
317
|
+
messages = result.get("messages", [])
|
|
318
|
+
if not messages:
|
|
319
|
+
_warn("Inbox Empty", "No messages.")
|
|
320
|
+
return
|
|
321
|
+
tbl = Table(border_style=Style(color=C["red_dark"]), box=box.SIMPLE_HEAVY,
|
|
322
|
+
header_style=Style(color=C["red"], bold=True), expand=True)
|
|
323
|
+
tbl.add_column("", width=3, justify="center")
|
|
324
|
+
tbl.add_column("From", style=Style(color=C["white"], bold=True))
|
|
325
|
+
tbl.add_column("Message")
|
|
326
|
+
tbl.add_column("Time", style=Style(color=C["muted"]), justify="right")
|
|
327
|
+
for msg in messages:
|
|
328
|
+
read = msg.get("read", False)
|
|
329
|
+
dot = Text("●", style=Style(color=C["green"] if not read else C["dim"]))
|
|
330
|
+
sender = msg.get("from_agent", "unknown")
|
|
331
|
+
payload = msg.get("payload", {})
|
|
332
|
+
body = payload.get("text", json.dumps(payload)) if isinstance(payload, dict) else str(payload)
|
|
333
|
+
ts = msg.get("sent_at", "")[:19]
|
|
334
|
+
tbl.add_row(dot, sender, Text(body[:60], style=Style(color=C["white"] if not read else C["muted"])), ts)
|
|
335
|
+
console.print(tbl)
|
|
336
|
+
except MoltGridError as e:
|
|
337
|
+
_error("Inbox Error", str(e))
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def cmd_heartbeat(args):
|
|
341
|
+
"""Send a heartbeat."""
|
|
342
|
+
mg = _get_client()
|
|
343
|
+
try:
|
|
344
|
+
mg.heartbeat(status="online")
|
|
345
|
+
_success("Heartbeat Sent", "Agent status: online")
|
|
346
|
+
except MoltGridError as e:
|
|
347
|
+
_error("Heartbeat Error", str(e))
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def cmd_submit(args):
|
|
351
|
+
"""Submit a job to the queue."""
|
|
352
|
+
mg = _get_client()
|
|
353
|
+
try:
|
|
354
|
+
payload = json.loads(args.payload)
|
|
355
|
+
result = mg.queue_submit(payload, priority=args.priority)
|
|
356
|
+
_success("Job Submitted", f"Job ID: {result.get('job_id', '')}\nPriority: {args.priority}")
|
|
357
|
+
except MoltGridError as e:
|
|
358
|
+
_error("Queue Error", str(e))
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def cmd_claim(args):
|
|
362
|
+
"""Claim a job from the queue."""
|
|
363
|
+
mg = _get_client()
|
|
364
|
+
try:
|
|
365
|
+
result = mg.queue_claim()
|
|
366
|
+
if not result or result.get("status") == "empty":
|
|
367
|
+
_warn("Queue Empty", "No jobs available to claim.")
|
|
368
|
+
return
|
|
369
|
+
console.print_json(json.dumps(result))
|
|
370
|
+
except MoltGridError as e:
|
|
371
|
+
_error("Queue Error", str(e))
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def cmd_search(args):
|
|
375
|
+
"""Search vector memory."""
|
|
376
|
+
mg = _get_client()
|
|
377
|
+
try:
|
|
378
|
+
result = mg.vector_search(args.query, top_k=args.top_k, namespace=args.namespace)
|
|
379
|
+
results = result.get("results", [])
|
|
380
|
+
if not results:
|
|
381
|
+
_warn("No Results", f'No matches for "{args.query}"')
|
|
382
|
+
return
|
|
383
|
+
tbl = Table(border_style=Style(color=C["red_dark"]), box=box.SIMPLE_HEAVY,
|
|
384
|
+
header_style=Style(color=C["red"], bold=True), expand=True,
|
|
385
|
+
title=Text.assemble(("● ", Style(color=C["red"], bold=True)),
|
|
386
|
+
("Vector Search", Style(color=C["red"], bold=True))))
|
|
387
|
+
tbl.add_column("Score", justify="right", width=8, style=Style(color=C["cyan"]))
|
|
388
|
+
tbl.add_column("Content", style=Style(color=C["white"]))
|
|
389
|
+
tbl.add_column("Key", style=Style(color=C["muted"]))
|
|
390
|
+
for r in results:
|
|
391
|
+
score = r.get("similarity", r.get("score", 0))
|
|
392
|
+
content = r.get("content", "")[:80]
|
|
393
|
+
key = r.get("key", "")
|
|
394
|
+
tbl.add_row(f"{score:.3f}", content, key)
|
|
395
|
+
console.print(tbl)
|
|
396
|
+
except MoltGridError as e:
|
|
397
|
+
_error("Search Error", str(e))
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def cmd_directory(args):
|
|
401
|
+
"""Browse agent directory."""
|
|
402
|
+
mg = _get_client()
|
|
403
|
+
try:
|
|
404
|
+
result = mg.directory_search(capability=args.capability if args.capability else None)
|
|
405
|
+
agents = result.get("agents", [])
|
|
406
|
+
if not agents:
|
|
407
|
+
_warn("Empty Directory", "No agents found.")
|
|
408
|
+
return
|
|
409
|
+
sm = {"online": ("● ", C["green"]), "idle": ("○ ", C["yellow"]),
|
|
410
|
+
"offline": ("\u2715 ", "#FF4444")}
|
|
411
|
+
tbl = Table(border_style=Style(color=C["red_dark"]), box=box.SIMPLE_HEAVY,
|
|
412
|
+
header_style=Style(color=C["red"], bold=True),
|
|
413
|
+
row_styles=[Style(color=C["white"]), Style(color=C["muted"])],
|
|
414
|
+
expand=True,
|
|
415
|
+
title=Text.assemble(("● ", Style(color=C["red"], bold=True)),
|
|
416
|
+
("Agent Grid", Style(color=C["red"], bold=True))))
|
|
417
|
+
tbl.add_column("", width=3, justify="center")
|
|
418
|
+
tbl.add_column("Agent", style=Style(color=C["white"], bold=True))
|
|
419
|
+
tbl.add_column("Status", justify="center")
|
|
420
|
+
tbl.add_column("Rep", justify="right", style=Style(color=C["white"]))
|
|
421
|
+
tbl.add_column("Capabilities", style=Style(color=C["muted"]))
|
|
422
|
+
for a in agents[:20]:
|
|
423
|
+
name = a.get("name", a.get("agent_id", "?"))
|
|
424
|
+
s = a.get("status", "offline")
|
|
425
|
+
d, c = sm.get(s, ("? ", C["muted"]))
|
|
426
|
+
st = Text()
|
|
427
|
+
st.append(d, style=Style(color=c, bold=True))
|
|
428
|
+
st.append(s, style=Style(color=c))
|
|
429
|
+
rep = str(a.get("reputation", 0))
|
|
430
|
+
caps = ", ".join(a.get("capabilities", [])[:3])
|
|
431
|
+
tbl.add_row(Text("●", style=Style(color=C["red_dim"])), name, st, rep, caps)
|
|
432
|
+
console.print(tbl)
|
|
433
|
+
except MoltGridError as e:
|
|
434
|
+
_error("Directory Error", str(e))
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# ── Fallback for no Rich ─────────────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
def _fallback_main():
|
|
440
|
+
"""Basic CLI without Rich."""
|
|
441
|
+
print(f"\n MoltGrid CLI v{VERSION}")
|
|
442
|
+
print(f" {API_URL}\n")
|
|
443
|
+
print(" Install 'rich' for the full experience: pip install rich")
|
|
444
|
+
print(" Commands: health, status, register, get, set, keys, send, inbox,")
|
|
445
|
+
print(" heartbeat, submit, claim, search, directory\n")
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
# ── Main ──────────────────────────────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
def main():
|
|
451
|
+
if not HAS_RICH:
|
|
452
|
+
_fallback_main()
|
|
453
|
+
return
|
|
454
|
+
|
|
455
|
+
parser = argparse.ArgumentParser(
|
|
456
|
+
prog="moltgrid",
|
|
457
|
+
description="MoltGrid CLI \u2014 infrastructure for autonomous agents",
|
|
458
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
459
|
+
)
|
|
460
|
+
parser.add_argument("--version", action="version", version=f"moltgrid {__version__}")
|
|
461
|
+
sub = parser.add_subparsers(dest="command", metavar="command")
|
|
462
|
+
|
|
463
|
+
sub.add_parser("health", help="Check API health")
|
|
464
|
+
sub.add_parser("status", help="Agent status dashboard")
|
|
465
|
+
|
|
466
|
+
p_reg = sub.add_parser("register", help="Register a new agent")
|
|
467
|
+
p_reg.add_argument("name", help="Agent name")
|
|
468
|
+
|
|
469
|
+
p_get = sub.add_parser("get", help="Get a memory value")
|
|
470
|
+
p_get.add_argument("key")
|
|
471
|
+
p_get.add_argument("--namespace", "-n", default="default")
|
|
472
|
+
|
|
473
|
+
p_set = sub.add_parser("set", help="Set a memory value")
|
|
474
|
+
p_set.add_argument("key")
|
|
475
|
+
p_set.add_argument("value")
|
|
476
|
+
p_set.add_argument("--namespace", "-n", default="default")
|
|
477
|
+
|
|
478
|
+
p_keys = sub.add_parser("keys", help="List memory keys")
|
|
479
|
+
p_keys.add_argument("--namespace", "-n", default="default")
|
|
480
|
+
|
|
481
|
+
p_send = sub.add_parser("send", help="Send a message")
|
|
482
|
+
p_send.add_argument("to", help="Target agent ID")
|
|
483
|
+
p_send.add_argument("payload", help="Message text or JSON")
|
|
484
|
+
|
|
485
|
+
sub.add_parser("inbox", help="Check message inbox")
|
|
486
|
+
sub.add_parser("heartbeat", help="Send a heartbeat")
|
|
487
|
+
|
|
488
|
+
p_sub = sub.add_parser("submit", help="Submit a queue job")
|
|
489
|
+
p_sub.add_argument("payload", help="Job payload (JSON)")
|
|
490
|
+
p_sub.add_argument("--priority", "-p", type=int, default=5)
|
|
491
|
+
|
|
492
|
+
sub.add_parser("claim", help="Claim a queue job")
|
|
493
|
+
|
|
494
|
+
p_search = sub.add_parser("search", help="Search vector memory")
|
|
495
|
+
p_search.add_argument("query")
|
|
496
|
+
p_search.add_argument("--top-k", "-k", type=int, default=5)
|
|
497
|
+
p_search.add_argument("--namespace", "-n", default="default")
|
|
498
|
+
|
|
499
|
+
p_dir = sub.add_parser("directory", help="Browse agent directory")
|
|
500
|
+
p_dir.add_argument("--capability", "-c", default=None)
|
|
501
|
+
|
|
502
|
+
args = parser.parse_args()
|
|
503
|
+
|
|
504
|
+
commands = {
|
|
505
|
+
"health": cmd_health,
|
|
506
|
+
"status": cmd_status,
|
|
507
|
+
"register": cmd_register,
|
|
508
|
+
"get": cmd_get,
|
|
509
|
+
"set": cmd_set,
|
|
510
|
+
"keys": cmd_keys,
|
|
511
|
+
"send": cmd_send,
|
|
512
|
+
"inbox": cmd_inbox,
|
|
513
|
+
"heartbeat": cmd_heartbeat,
|
|
514
|
+
"submit": cmd_submit,
|
|
515
|
+
"claim": cmd_claim,
|
|
516
|
+
"search": cmd_search,
|
|
517
|
+
"directory": cmd_directory,
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if not args.command:
|
|
521
|
+
cmd_default(args)
|
|
522
|
+
return
|
|
523
|
+
|
|
524
|
+
fn = commands.get(args.command)
|
|
525
|
+
if fn:
|
|
526
|
+
fn(args)
|
|
527
|
+
else:
|
|
528
|
+
parser.print_help()
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
if __name__ == "__main__":
|
|
532
|
+
main()
|