agentram 0.1.20 → 0.1.21

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
@@ -5,6 +5,7 @@ import asyncio
5
5
  from concurrent.futures import ThreadPoolExecutor
6
6
  import json
7
7
  import os
8
+ import re
8
9
  import shlex
9
10
  import shutil
10
11
  import textwrap
@@ -16,6 +17,51 @@ from .config import default_ram_root
16
17
  from .mcp_server import McpContext, call_tool
17
18
  from .orchestration import AgentModelEndpoint, MultiModelCodingOrchestrator
18
19
 
20
+
21
+ ENV_NAME_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
22
+ RAW_KEY_PREFIXES = ("sk-", "sk_", "sk-proj-", "gsk_", "xai-", "AIza")
23
+
24
+
25
+ def looks_like_raw_api_key(value: str) -> bool:
26
+ stripped = value.strip()
27
+ if not stripped:
28
+ return False
29
+ if stripped.startswith(RAW_KEY_PREFIXES):
30
+ return True
31
+ return not bool(ENV_NAME_PATTERN.fullmatch(stripped))
32
+
33
+
34
+ def update_env_file_value(path: Path, key: str, value: str) -> None:
35
+ path.parent.mkdir(parents=True, exist_ok=True)
36
+ lines = path.read_text(encoding="utf-8").splitlines() if path.exists() else []
37
+ assignment = f"{key}={value}"
38
+ updated = False
39
+ output: list[str] = []
40
+ for line in lines:
41
+ if line.strip().startswith(f"{key}="):
42
+ output.append(assignment)
43
+ updated = True
44
+ else:
45
+ output.append(line)
46
+ if not updated:
47
+ if output and output[-1].strip():
48
+ output.append("")
49
+ output.append("# Added by AgentRAM TUI setup")
50
+ output.append(assignment)
51
+ path.write_text("\n".join(output) + "\n", encoding="utf-8")
52
+
53
+
54
+ def resolve_api_key_env(slot: str, value: str) -> tuple[str, str | None]:
55
+ stripped = value.strip()
56
+ if not stripped:
57
+ return "", None
58
+ if looks_like_raw_api_key(stripped):
59
+ env_name = f"AGENTRAM_{slot.upper()}_API_KEY"
60
+ update_env_file_value(Path(".env"), env_name, stripped)
61
+ os.environ[env_name] = stripped
62
+ return env_name, env_name
63
+ return stripped, None
64
+
19
65
  SUBAGENTS: dict[str, str] = {
20
66
  "memory": "Summarize important facts, decisions, files, risks, and next tasks into AgentRAM events. Do not edit code.",
21
67
  "planner": "Break the coding request into small ordered steps. Check AgentRAM goal drift before scope changes.",
@@ -335,7 +381,7 @@ def run_textual_tui(context: McpContext) -> bool:
335
381
  yield Input(placeholder="Custom provider if preset=Custom", id=f"setup-{slot}-custom-provider", classes="setup-input")
336
382
  yield Input(value=default_model, placeholder="Model name", id=f"setup-{slot}-model", classes="setup-input")
337
383
  yield Input(placeholder="Base URL, e.g. http://localhost:20128/v1", id=f"setup-{slot}-base-url", classes="setup-input")
338
- yield Input(value="OPENAI_API_KEY", placeholder="API key env name, not raw key", id=f"setup-{slot}-api-key-env", classes="setup-input")
384
+ yield Input(value="OPENAI_API_KEY", placeholder="API key or env name", id=f"setup-{slot}-api-key-env", classes="setup-input")
339
385
  with Horizontal(id="setup-actions"):
340
386
  yield Button("Save setup", id="setup-save", variant="primary")
341
387
  yield Button("Skip", id="setup-skip")
@@ -396,10 +442,8 @@ def run_textual_tui(context: McpContext) -> bool:
396
442
  provider = self.setup_provider(slot)
397
443
  model = self.setup_value(f"setup-{slot}-model")
398
444
  base_url = self.setup_value(f"setup-{slot}-base-url")
399
- api_key_env = self.setup_value(f"setup-{slot}-api-key-env")
400
- if api_key_env.startswith(("sk-", "gsk_", "sk_")):
401
- self.chat.write(f"agentram > setup error: {slot} api key env must be variable name, not raw key")
402
- return
445
+ api_key_input = self.setup_value(f"setup-{slot}-api-key-env")
446
+ api_key_env, saved_env_name = resolve_api_key_env(slot, api_key_input)
403
447
  if not model:
404
448
  continue
405
449
  endpoint = AgentModelEndpoint(
@@ -411,7 +455,10 @@ def run_textual_tui(context: McpContext) -> bool:
411
455
  modalities=["text"],
412
456
  )
413
457
  self.context.profile_store.set_agent(slot, endpoint)
414
- bound.append(f"{slot}={provider}:{model}")
458
+ if saved_env_name:
459
+ bound.append(f"{slot}={provider}:{model} key=.env:{saved_env_name}")
460
+ else:
461
+ bound.append(f"{slot}={provider}:{model}")
415
462
  if not bound:
416
463
  self.chat.write("agentram > setup skipped: no model entered")
417
464
  else:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentram",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
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.20"
7
+ version = "0.1.21"
8
8
  description = "Async, model-agnostic RAM layer for agentic coding tools."
9
9
  readme = "README.md"
10
10
  license = "MIT"