agentram 0.1.14 → 0.1.16
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.
- package/agentram/cli.py +82 -4
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/agentram/cli.py
CHANGED
|
@@ -273,7 +273,7 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
273
273
|
try:
|
|
274
274
|
from textual.app import App, ComposeResult
|
|
275
275
|
from textual.containers import Horizontal, Vertical
|
|
276
|
-
from textual.widgets import Footer, Header, Input, RichLog, Static
|
|
276
|
+
from textual.widgets import Button, Footer, Header, Input, RichLog, Static
|
|
277
277
|
except ImportError:
|
|
278
278
|
return False
|
|
279
279
|
|
|
@@ -283,12 +283,16 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
283
283
|
#layout { height: 1fr; }
|
|
284
284
|
#sidebar { width: 32; border: solid #263241; padding: 1; }
|
|
285
285
|
#main { width: 1fr; }
|
|
286
|
+
#setup { height: auto; border: solid #3b82f6; padding: 1; background: #101826; }
|
|
287
|
+
#setup-title { height: auto; color: #dbeafe; }
|
|
288
|
+
#setup-actions { height: auto; }
|
|
289
|
+
.setup-input { height: 3; }
|
|
286
290
|
#chat { height: 1fr; border: solid #263241; padding: 1; overflow-y: scroll; }
|
|
287
291
|
#palette { height: auto; max-height: 12; border: solid #3b82f6; padding: 1; background: #101826; display: none; }
|
|
288
292
|
#input { dock: bottom; border: solid #3b82f6; }
|
|
289
293
|
.muted { color: #8b9bb0; }
|
|
290
294
|
"""
|
|
291
|
-
BINDINGS = [("ctrl+c", "quit", "Quit"), ("tab", "accept_suggestion", "Complete slash")]
|
|
295
|
+
BINDINGS = [("ctrl+c", "quit", "Quit"), ("tab", "accept_suggestion", "Complete slash"), ("ctrl+s", "save_setup", "Save setup")]
|
|
292
296
|
|
|
293
297
|
def __init__(self, mcp_context: McpContext) -> None:
|
|
294
298
|
super().__init__()
|
|
@@ -296,6 +300,7 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
296
300
|
self.messages: list[str] = []
|
|
297
301
|
self.busy = False
|
|
298
302
|
self.suggestions: list[str] = []
|
|
303
|
+
self.setup_done = False
|
|
299
304
|
|
|
300
305
|
def compose(self) -> ComposeResult:
|
|
301
306
|
yield Header(show_clock=True)
|
|
@@ -310,8 +315,10 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
310
315
|
self.context.init_storage()
|
|
311
316
|
self.chat.write("system > AgentRAM Textual TUI ready")
|
|
312
317
|
self.chat.write(f"system > RAM root: {self.context.ram_root}")
|
|
318
|
+
self.prefill_setup_from_profile()
|
|
313
319
|
self.refresh_sidebar()
|
|
314
320
|
self.set_interval(1.0, self.refresh_sidebar)
|
|
321
|
+
self.query_one("#setup-provider", Input).focus()
|
|
315
322
|
|
|
316
323
|
@property
|
|
317
324
|
def chat(self) -> RichLog:
|
|
@@ -320,13 +327,79 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
320
327
|
def refresh_sidebar(self) -> None:
|
|
321
328
|
self.query_one("#sidebar", Static).update("\n".join(sidebar_lines(self.context)))
|
|
322
329
|
|
|
330
|
+
def prefill_setup_from_profile(self) -> None:
|
|
331
|
+
agents = self.context.profile_store.list_agents()
|
|
332
|
+
if not agents:
|
|
333
|
+
return
|
|
334
|
+
first = next(iter(agents.values()))
|
|
335
|
+
self.query_one("#setup-provider", Input).value = first.provider
|
|
336
|
+
self.query_one("#setup-base-url", Input).value = first.base_url
|
|
337
|
+
self.query_one("#setup-api-key-env", Input).value = first.api_key_env
|
|
338
|
+
for slot in ("supervisor", "main", "small"):
|
|
339
|
+
endpoint = agents.get(slot)
|
|
340
|
+
if endpoint:
|
|
341
|
+
self.query_one(f"#setup-{slot}", Input).value = endpoint.model
|
|
342
|
+
self.chat.write("system > Existing workflow models loaded. Save to update, or Skip to keep.")
|
|
343
|
+
|
|
344
|
+
def setup_value(self, widget_id: str) -> str:
|
|
345
|
+
return self.query_one(f"#{widget_id}", Input).value.strip()
|
|
346
|
+
|
|
347
|
+
def action_save_setup(self) -> None:
|
|
348
|
+
provider = self.setup_value("setup-provider") or "openai-compatible"
|
|
349
|
+
base_url = self.setup_value("setup-base-url")
|
|
350
|
+
api_key_env = self.setup_value("setup-api-key-env")
|
|
351
|
+
if api_key_env.startswith(("sk-", "gsk_", "sk_")):
|
|
352
|
+
self.chat.write("agentram > setup error: api key env must be variable name, not raw key")
|
|
353
|
+
return
|
|
354
|
+
role_by_slot = {"supervisor": "supervisor", "main": "coder", "small": "memory"}
|
|
355
|
+
bound: list[str] = []
|
|
356
|
+
for slot, role in role_by_slot.items():
|
|
357
|
+
model = self.setup_value(f"setup-{slot}")
|
|
358
|
+
if not model:
|
|
359
|
+
continue
|
|
360
|
+
endpoint = AgentModelEndpoint(
|
|
361
|
+
provider=provider,
|
|
362
|
+
model=model,
|
|
363
|
+
role=role,
|
|
364
|
+
base_url=base_url,
|
|
365
|
+
api_key_env=api_key_env,
|
|
366
|
+
modalities=["text"],
|
|
367
|
+
)
|
|
368
|
+
self.context.profile_store.set_agent(slot, endpoint)
|
|
369
|
+
bound.append(f"{slot}={model}")
|
|
370
|
+
if not bound:
|
|
371
|
+
self.chat.write("agentram > setup skipped: no model entered")
|
|
372
|
+
else:
|
|
373
|
+
self.chat.write("agentram > setup saved: " + ", ".join(bound))
|
|
374
|
+
self.hide_setup()
|
|
375
|
+
self.refresh_sidebar()
|
|
376
|
+
self.query_one("#input", Input).focus()
|
|
377
|
+
|
|
378
|
+
def hide_setup(self) -> None:
|
|
379
|
+
self.setup_done = True
|
|
380
|
+
self.query_one("#setup", Vertical).styles.display = "none"
|
|
381
|
+
|
|
382
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
383
|
+
if event.button.id == "setup-save":
|
|
384
|
+
self.action_save_setup()
|
|
385
|
+
elif event.button.id == "setup-skip":
|
|
386
|
+
self.chat.write("system > Model setup skipped. Use /bind-agent later.")
|
|
387
|
+
self.hide_setup()
|
|
388
|
+
self.query_one("#input", Input).focus()
|
|
389
|
+
|
|
323
390
|
def hide_palette(self) -> None:
|
|
324
|
-
|
|
391
|
+
try:
|
|
392
|
+
palette = self.query_one("#palette", Static)
|
|
393
|
+
except Exception: # noqa: BLE001 - widget may not be mounted yet
|
|
394
|
+
return
|
|
325
395
|
palette.update("")
|
|
326
396
|
palette.styles.display = "none"
|
|
327
397
|
|
|
328
398
|
def show_palette(self, value: str) -> None:
|
|
329
|
-
|
|
399
|
+
try:
|
|
400
|
+
palette = self.query_one("#palette", Static)
|
|
401
|
+
except Exception: # noqa: BLE001 - widget may not be mounted yet
|
|
402
|
+
return
|
|
330
403
|
query = value.strip().lower().lstrip("/")
|
|
331
404
|
matches = []
|
|
332
405
|
for command, description in SLASH_COMMANDS:
|
|
@@ -347,6 +420,8 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
347
420
|
palette.styles.display = "block"
|
|
348
421
|
|
|
349
422
|
def on_input_changed(self, event: Input.Changed) -> None:
|
|
423
|
+
if event.input.id != "input":
|
|
424
|
+
return
|
|
350
425
|
value = event.value.strip()
|
|
351
426
|
if value.startswith("/"):
|
|
352
427
|
self.show_palette(value)
|
|
@@ -362,6 +437,9 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
362
437
|
self.hide_palette()
|
|
363
438
|
|
|
364
439
|
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
440
|
+
if event.input.id and str(event.input.id).startswith("setup-"):
|
|
441
|
+
self.action_save_setup()
|
|
442
|
+
return
|
|
365
443
|
text = event.value.strip()
|
|
366
444
|
event.input.value = ""
|
|
367
445
|
self.hide_palette()
|
package/package.json
CHANGED