agentram 0.1.2 → 0.1.5
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/README.md +41 -3
- package/agentram/cli.py +139 -17
- package/package.json +1 -1
- package/pyproject.toml +4 -1
package/README.md
CHANGED
|
@@ -53,9 +53,10 @@ If using the Claude plugin, `.mcp.json` sets `AGENTRAM_AUTO_INIT=true`, so these
|
|
|
53
53
|
|
|
54
54
|
## Terminal UI CLI
|
|
55
55
|
|
|
56
|
-
AgentRAM
|
|
56
|
+
AgentRAM includes a terminal UI with Claude-like slash autocomplete. It uses `prompt_toolkit` when installed and falls back to plain `input()` otherwise:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
+
pip install -e ".[tui]"
|
|
59
60
|
agentram
|
|
60
61
|
```
|
|
61
62
|
|
|
@@ -170,7 +171,7 @@ Run TUI:
|
|
|
170
171
|
agentram tui
|
|
171
172
|
```
|
|
172
173
|
|
|
173
|
-
Supported TUI slash commands:
|
|
174
|
+
Supported TUI slash commands. Plain text only records/retrieves RAM; use `/ask` to call bound models:
|
|
174
175
|
|
|
175
176
|
```text
|
|
176
177
|
/help
|
|
@@ -385,12 +386,13 @@ agentram_build_coding_orchestration
|
|
|
385
386
|
agentram_run_coding_orchestration
|
|
386
387
|
```
|
|
387
388
|
|
|
388
|
-
TUI slash commands:
|
|
389
|
+
TUI slash commands. Plain text only records/retrieves RAM; use `/ask` to call bound models:
|
|
389
390
|
|
|
390
391
|
```text
|
|
391
392
|
/bind-model claude claude-3-5-sonnet-latest role=reviewer api_key_env=ANTHROPIC_API_KEY
|
|
392
393
|
/models
|
|
393
394
|
/orchestrate review CLI orchestration
|
|
395
|
+
/ask xin chào
|
|
394
396
|
```
|
|
395
397
|
|
|
396
398
|
Storage:
|
|
@@ -844,3 +846,39 @@ Use `.env.example` as the shareable template.
|
|
|
844
846
|
- Model-based ingestion is best run through daemon/wrapper configuration.
|
|
845
847
|
- Wrapper CLI records command-level events, not deep internal agent tool events.
|
|
846
848
|
- Rich file/tool hooks need direct agent hooks or disciplined MCP calls.
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
### Why TUI May Not Respond Like A Model
|
|
852
|
+
|
|
853
|
+
Plain text in `agentram tui` records a user message and retrieves RAM context. It does not call paid/local models by default. To get a model response:
|
|
854
|
+
|
|
855
|
+
1. Put the real API key in an environment variable.
|
|
856
|
+
2. Bind the model using the env var name, not the raw key.
|
|
857
|
+
3. Use `/ask` or `agentram orchestrate --execute`.
|
|
858
|
+
|
|
859
|
+
Example:
|
|
860
|
+
|
|
861
|
+
```bash
|
|
862
|
+
set OPENAI_API_KEY=sk-your-key
|
|
863
|
+
agentram tui
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
Inside TUI:
|
|
867
|
+
|
|
868
|
+
```text
|
|
869
|
+
/bind-model openai-compatible Agentic --role coder --base-url http://localhost:8000/v1 --api-key-env OPENAI_API_KEY
|
|
870
|
+
/ask xin chào
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Do not paste raw API keys into `/bind-model`. `api_key_env` means environment variable name, e.g. `OPENAI_API_KEY`.
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
### Optional Rich TUI
|
|
877
|
+
|
|
878
|
+
For live slash-command dropdown and command metadata, install the optional TUI extra:
|
|
879
|
+
|
|
880
|
+
```bash
|
|
881
|
+
pip install agentram[tui]
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
Without `prompt_toolkit`, AgentRAM still works with basic terminal input; `/` then Enter opens the full slash palette.
|
package/agentram/cli.py
CHANGED
|
@@ -18,6 +18,30 @@ SUBAGENTS: dict[str, str] = {
|
|
|
18
18
|
"documenter": "Update README or usage notes from current implementation. Keep docs concise and runnable.",
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
SLASH_COMMANDS: list[tuple[str, str]] = [
|
|
22
|
+
("/help", "Show commands"),
|
|
23
|
+
("/init", "Create .agentram files"),
|
|
24
|
+
("/status", "Show RAM status"),
|
|
25
|
+
("/retrieve <query>", "Print relevant RAM context"),
|
|
26
|
+
("/goal", "Show Goal Stack"),
|
|
27
|
+
("/set-goal key=value ...", "Update mission/current_goal/current_task/etc"),
|
|
28
|
+
("/drift <task>", "Check goal drift"),
|
|
29
|
+
("/events", "Show latest events"),
|
|
30
|
+
("/replay", "Replay events into memory"),
|
|
31
|
+
("/agents", "List Claude-style subagent presets"),
|
|
32
|
+
("/agent <name> <task>", "Build Claude Code subagent prompt"),
|
|
33
|
+
("/models", "List bound coding models"),
|
|
34
|
+
("/bind-model provider model", "Bind model endpoint"),
|
|
35
|
+
("/orchestrate <task>", "Build multi-model agent plan"),
|
|
36
|
+
("/ask <prompt>", "Execute bound models and return outputs"),
|
|
37
|
+
("/claude-install [path]", "Install .claude commands and agents"),
|
|
38
|
+
("/note <text>", "Save decision note"),
|
|
39
|
+
("/clear", "Clear chat"),
|
|
40
|
+
("/exit", "Quit"),
|
|
41
|
+
("Plain text", "Capture coding chat message and retrieve context"),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
21
45
|
SLASH_HELP = """Slash commands:
|
|
22
46
|
/help Show commands
|
|
23
47
|
/init Create .agentram files
|
|
@@ -33,6 +57,7 @@ SLASH_HELP = """Slash commands:
|
|
|
33
57
|
/models List bound coding models
|
|
34
58
|
/bind-model provider model Bind model endpoint
|
|
35
59
|
/orchestrate <task> Build multi-model agent plan
|
|
60
|
+
/ask <prompt> Execute bound models and return outputs
|
|
36
61
|
/claude-install [path] Install .claude commands and agents
|
|
37
62
|
/note <text> Save decision note
|
|
38
63
|
/clear Clear chat
|
|
@@ -150,10 +175,19 @@ def run_command(args: argparse.Namespace) -> int:
|
|
|
150
175
|
|
|
151
176
|
def run_tui(context: McpContext) -> None:
|
|
152
177
|
context.init_storage()
|
|
153
|
-
|
|
178
|
+
prompt_session = create_prompt_session()
|
|
179
|
+
input_hint = "live slash autocomplete enabled" if prompt_session else "basic input fallback; install prompt_toolkit for live slash dropdown"
|
|
180
|
+
messages = [
|
|
181
|
+
"system > AgentRAM coding-agent CLI ready",
|
|
182
|
+
f"system > RAM root: {context.ram_root}",
|
|
183
|
+
f"system > {input_hint}",
|
|
184
|
+
]
|
|
154
185
|
while True:
|
|
155
186
|
draw_screen(context, messages)
|
|
156
|
-
|
|
187
|
+
try:
|
|
188
|
+
user_input = read_tui_input(prompt_session).strip()
|
|
189
|
+
except (EOFError, KeyboardInterrupt):
|
|
190
|
+
return
|
|
157
191
|
if user_input in {"0", "q", "quit", "exit", "/exit", "/quit"}:
|
|
158
192
|
return
|
|
159
193
|
if user_input in BUTTONS:
|
|
@@ -169,9 +203,46 @@ def run_tui(context: McpContext) -> None:
|
|
|
169
203
|
del messages[:-10]
|
|
170
204
|
|
|
171
205
|
|
|
206
|
+
|
|
207
|
+
def slash_command_names() -> list[str]:
|
|
208
|
+
return [command.split()[0] for command, _ in SLASH_COMMANDS if command.startswith("/")]
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def slash_command_meta() -> dict[str, str]:
|
|
212
|
+
return {command.split()[0]: description for command, description in SLASH_COMMANDS if command.startswith("/")}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def create_prompt_session() -> Any | None:
|
|
216
|
+
try:
|
|
217
|
+
from prompt_toolkit import PromptSession
|
|
218
|
+
from prompt_toolkit.completion import WordCompleter
|
|
219
|
+
except ImportError:
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
completer = WordCompleter(
|
|
223
|
+
slash_command_names(),
|
|
224
|
+
meta_dict=slash_command_meta(),
|
|
225
|
+
ignore_case=True,
|
|
226
|
+
sentence=True,
|
|
227
|
+
)
|
|
228
|
+
return PromptSession(
|
|
229
|
+
completer=completer,
|
|
230
|
+
complete_while_typing=True,
|
|
231
|
+
reserve_space_for_menu=12,
|
|
232
|
+
bottom_toolbar="Type / for slash commands · /agents for subagents · /ask to call bound models",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def read_tui_input(prompt_session: Any | None) -> str:
|
|
237
|
+
if prompt_session is None:
|
|
238
|
+
return input("You > ")
|
|
239
|
+
return prompt_session.prompt("You > ")
|
|
240
|
+
|
|
172
241
|
def handle_chat_input(context: McpContext, text: str, messages: list[str] | None = None) -> str:
|
|
173
242
|
if not text:
|
|
174
243
|
return SLASH_HELP
|
|
244
|
+
if text == "/":
|
|
245
|
+
return slash_palette_text()
|
|
175
246
|
if text.startswith("/"):
|
|
176
247
|
return handle_slash_command(context, text, messages)
|
|
177
248
|
call_tool(
|
|
@@ -213,15 +284,14 @@ def handle_slash_command(context: McpContext, text: str, messages: list[str] | N
|
|
|
213
284
|
return models_text(context)
|
|
214
285
|
if command == "/bind-model":
|
|
215
286
|
if len(rest) < 2:
|
|
216
|
-
return "Usage: /bind-model <provider> <model> [role
|
|
217
|
-
|
|
218
|
-
for item in rest[2:]:
|
|
219
|
-
if "=" in item:
|
|
220
|
-
key, value = item.split("=", 1)
|
|
221
|
-
options[key.replace("-", "_")] = value
|
|
222
|
-
return bind_model_text(context, options)
|
|
287
|
+
return "Usage: /bind-model <provider> <model> [--role coder] [--base-url URL] [--api-key-env ENV_NAME]"
|
|
288
|
+
return bind_model_text(context, parse_bind_model_args(rest))
|
|
223
289
|
if command == "/orchestrate":
|
|
224
|
-
|
|
290
|
+
execute = "--execute" in rest
|
|
291
|
+
task = raw_rest.replace("--execute", "").strip()
|
|
292
|
+
return orchestrate_text(context, task, execute=execute)
|
|
293
|
+
if command == "/ask":
|
|
294
|
+
return orchestrate_text(context, raw_rest, execute=True)
|
|
225
295
|
if command == "/agent":
|
|
226
296
|
if not rest:
|
|
227
297
|
return "Usage: /agent <memory|planner|reviewer|documenter> <task>"
|
|
@@ -313,8 +383,34 @@ def replay_text(context: McpContext, arguments: dict[str, object]) -> str:
|
|
|
313
383
|
|
|
314
384
|
|
|
315
385
|
def agents_text() -> str:
|
|
316
|
-
|
|
317
|
-
|
|
386
|
+
rows = [
|
|
387
|
+
("agentram-memory", "Memory", "Records durable notes. Never edits code."),
|
|
388
|
+
("agentram-planner", "Planner", "Checks Goal Stack and decomposes work."),
|
|
389
|
+
("agentram-reviewer", "Reviewer", "Reviews changes, tests, and drift risk."),
|
|
390
|
+
("agentram-docs", "Docs", "Updates README and usage docs."),
|
|
391
|
+
]
|
|
392
|
+
lines = ["Claude Code subagents", ""]
|
|
393
|
+
for name, label, description in rows:
|
|
394
|
+
lines.append(f" {name.ljust(22)} {label}")
|
|
395
|
+
lines.append(f" {description}")
|
|
396
|
+
lines.append("")
|
|
397
|
+
lines.append("Use: /agent reviewer <task>")
|
|
398
|
+
lines.append("Install: /claude-install .")
|
|
399
|
+
return "\n".join(lines)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def slash_palette_text(filter_text: str = "") -> str:
|
|
403
|
+
normalized = filter_text.strip().lower().lstrip("/")
|
|
404
|
+
rows = []
|
|
405
|
+
for command, description in SLASH_COMMANDS:
|
|
406
|
+
command_key = command.lower().lstrip("/")
|
|
407
|
+
if normalized and normalized not in command_key:
|
|
408
|
+
continue
|
|
409
|
+
rows.append((command, description))
|
|
410
|
+
command_width = max([len(command) for command, _ in rows] + [12])
|
|
411
|
+
lines = ["Slash commands", ""]
|
|
412
|
+
for command, description in rows:
|
|
413
|
+
lines.append(f" {command.ljust(command_width)} {description}")
|
|
318
414
|
return "\n".join(lines)
|
|
319
415
|
|
|
320
416
|
|
|
@@ -350,7 +446,32 @@ def models_text(context: McpContext) -> str:
|
|
|
350
446
|
return "\n".join(lines)
|
|
351
447
|
|
|
352
448
|
|
|
449
|
+
def parse_bind_model_args(parts: list[str]) -> dict[str, object]:
|
|
450
|
+
options: dict[str, object] = {"provider": parts[0], "model": parts[1]}
|
|
451
|
+
index = 2
|
|
452
|
+
while index < len(parts):
|
|
453
|
+
item = parts[index]
|
|
454
|
+
if item.startswith("--"):
|
|
455
|
+
key = item[2:].replace("-", "_")
|
|
456
|
+
if index + 1 < len(parts) and not parts[index + 1].startswith("--"):
|
|
457
|
+
options[key] = parts[index + 1]
|
|
458
|
+
index += 2
|
|
459
|
+
else:
|
|
460
|
+
options[key] = True
|
|
461
|
+
index += 1
|
|
462
|
+
elif "=" in item:
|
|
463
|
+
key, value = item.split("=", 1)
|
|
464
|
+
options[key.replace("-", "_")] = value
|
|
465
|
+
index += 1
|
|
466
|
+
else:
|
|
467
|
+
index += 1
|
|
468
|
+
return options
|
|
469
|
+
|
|
470
|
+
|
|
353
471
|
def bind_model_text(context: McpContext, options: dict[str, object]) -> str:
|
|
472
|
+
api_key_env = str(options.get("api_key_env", ""))
|
|
473
|
+
if api_key_env.startswith(("sk-", "gsk_", "sk_")):
|
|
474
|
+
return "Do not paste raw API key into api_key_env. Set env var first, e.g. OPENAI_API_KEY=sk-..., then use api_key_env=OPENAI_API_KEY."
|
|
354
475
|
modalities_value = str(options.get("modalities", "text"))
|
|
355
476
|
arguments = {
|
|
356
477
|
"id": str(options.get("id", "")) or None,
|
|
@@ -459,7 +580,7 @@ def draw_screen(context: McpContext, messages: list[str]) -> None:
|
|
|
459
580
|
left_width = 30
|
|
460
581
|
right_width = width - left_width - 3
|
|
461
582
|
print("+" + "-" * (width - 2) + "+")
|
|
462
|
-
print(box_text("AgentRAM Code
|
|
583
|
+
print(box_text("◆ AgentRAM Code | RAM + Claude subagents + slash commands", width))
|
|
463
584
|
print("+" + "-" * (width - 2) + "+")
|
|
464
585
|
|
|
465
586
|
sidebar = sidebar_lines(context)
|
|
@@ -470,7 +591,7 @@ def draw_screen(context: McpContext, messages: list[str]) -> None:
|
|
|
470
591
|
right = chat[index] if index < len(chat) else ""
|
|
471
592
|
print("| " + left[: left_width - 2].ljust(left_width - 2) + "| " + right[: right_width - 2].ljust(right_width - 2) + "|")
|
|
472
593
|
print("+" + "-" * (left_width - 1) + "+" + "-" * (right_width) + "+")
|
|
473
|
-
print(box_text("Input:
|
|
594
|
+
print(box_text("Input: type / for live dropdown. Enter '/' for full palette. /ask calls models.", width))
|
|
474
595
|
print("+" + "-" * (width - 2) + "+")
|
|
475
596
|
|
|
476
597
|
|
|
@@ -501,10 +622,11 @@ def sidebar_lines(context: McpContext) -> list[str]:
|
|
|
501
622
|
" 9 help 0 exit",
|
|
502
623
|
"",
|
|
503
624
|
"CLAUDE",
|
|
625
|
+
" / = command palette",
|
|
626
|
+
" /agents",
|
|
504
627
|
" /claude-install .",
|
|
505
628
|
" /models",
|
|
506
|
-
" /
|
|
507
|
-
" /orchestrate <task>",
|
|
629
|
+
" /ask <prompt>",
|
|
508
630
|
]
|
|
509
631
|
|
|
510
632
|
|
|
@@ -515,7 +637,7 @@ def trim(text: str, limit: int) -> str:
|
|
|
515
637
|
def menu_lines() -> list[str]:
|
|
516
638
|
return [
|
|
517
639
|
"1=/init 2=/status 3=/retrieve 4=/goal 5=/set-goal 6=/drift 7=/events 8=/replay 9=/help",
|
|
518
|
-
"Plain text
|
|
640
|
+
"Plain text saves RAM only. Use /ask <prompt> to call bound models.",
|
|
519
641
|
]
|
|
520
642
|
|
|
521
643
|
|
package/package.json
CHANGED
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.
|
|
7
|
+
version = "0.1.5"
|
|
8
8
|
description = "Async, model-agnostic RAM layer for agentic coding tools."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -21,6 +21,9 @@ classifiers = [
|
|
|
21
21
|
requires-python = ">=3.10"
|
|
22
22
|
dependencies = []
|
|
23
23
|
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
tui = ["prompt_toolkit>=3.0"]
|
|
26
|
+
|
|
24
27
|
[project.urls]
|
|
25
28
|
Homepage = "https://github.com/your-org/agentram"
|
|
26
29
|
Repository = "https://github.com/your-org/agentram"
|