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 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
- palette = self.query_one("#palette", Static)
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
- palette = self.query_one("#palette", Static)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentram",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Async, model-agnostic Working RAM for coding agents.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/trancongnghia/AGENT_RAM#readme",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentram"
7
- version = "0.1.14"
7
+ version = "0.1.16"
8
8
  description = "Async, model-agnostic RAM layer for agentic coding tools."
9
9
  readme = "README.md"
10
10
  license = "MIT"