codegpt-ai 1.27.0 → 1.28.2

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.
Files changed (2) hide show
  1. package/chat.py +319 -65
  2. package/package.json +1 -1
package/chat.py CHANGED
@@ -422,6 +422,8 @@ COMMANDS = {
422
422
  "/vote": "All agents vote on a question",
423
423
  "/swarm": "Agents collaborate on a task step by step",
424
424
  "/team": "Start a team chat with 2 AIs (/team coder reviewer)",
425
+ "/room": "AI chat room — multiple AIs talk (/room coder reviewer architect)",
426
+ "/spectate": "Watch AIs chat without you (/spectate claude codex topic)",
425
427
  "/github": "GitHub tools (/github repos, issues, prs)",
426
428
  "/weather": "Get weather (/weather London)",
427
429
  "/open": "Open URL in browser (/open google.com)",
@@ -607,32 +609,27 @@ def ask_permission(action, detail=""):
607
609
 
608
610
  risk_color = RISK_COLORS.get(risk, "yellow")
609
611
  risk_icon = RISK_ICONS.get(risk, "?")
610
- compact = is_compact()
611
612
 
612
- if compact:
613
- console.print(Panel(
614
- Text.from_markup(
615
- f"[{risk_color}]{risk_icon} {risk}[/]\n"
616
- f" {action_desc}\n"
617
- + (f" [dim]{detail[:35]}[/]\n" if detail else "")
618
- ),
619
- border_style=risk_color.replace("bold ", ""), padding=(0, 1), width=tw(),
620
- ))
621
- else:
622
- console.print(Panel(
623
- Text.from_markup(
624
- f"[{risk_color}]{risk_icon} Risk: {risk}[/]\n\n"
625
- f" Action: [bright_cyan]{action_desc}[/]\n"
626
- + (f" Detail: [dim]{detail[:60]}[/]\n" if detail else "")
627
- + f"\n [dim](y)es (n)o (a)lways allow this[/]"
628
- ),
629
- title=f"[{risk_color}]Permission[/]",
630
- border_style=risk_color.replace("bold ", ""), padding=(0, 2), width=tw(),
631
- ))
613
+ # Risk warnings — explain what could happen
614
+ risk_warnings = {
615
+ "CRITICAL": "This can execute code, modify your system, or expose data.",
616
+ "HIGH": "This accesses external services or modifies important data.",
617
+ "MEDIUM": "This uses resources or changes session settings.",
618
+ "LOW": "This is a safe operation with minimal impact.",
619
+ }
620
+ warning = risk_warnings.get(risk, "")
621
+
622
+ # Clean minimal prompt — like Claude Code
623
+ console.print()
624
+ console.print(Text.from_markup(f" [{risk_color}]{risk_icon} {action_desc}[/]"))
625
+ if detail:
626
+ console.print(Text(f" {detail[:70]}", style="dim"))
627
+ console.print(Text.from_markup(f" [{risk_color}]{risk} — {warning}[/]"))
628
+ console.print()
632
629
 
633
630
  try:
634
631
  answer = prompt(
635
- [("class:prompt", " Allow? (y/n/a) > ")],
632
+ [("class:prompt", " Allow? (y)es / (n)o / (a)lways > ")],
636
633
  style=input_style,
637
634
  ).strip().lower()
638
635
  except (KeyboardInterrupt, EOFError):
@@ -641,9 +638,9 @@ def ask_permission(action, detail=""):
641
638
  if answer in ("a", "always"):
642
639
  PERMISSION_ALWAYS_ALLOW.add(action)
643
640
  save_permissions()
644
- print_sys(f"Always allowed: {action_desc}")
641
+ console.print(Text(f"Always allowed", style="green"))
645
642
  return True
646
- elif answer in ("y", "yes"):
643
+ elif answer in ("y", "yes", ""):
647
644
  return True
648
645
  else:
649
646
  print_sys("Denied.")
@@ -1277,13 +1274,38 @@ def _print_err_panel(text):
1277
1274
 
1278
1275
 
1279
1276
  def print_help():
1280
- table = Table(border_style="bright_black", show_header=True,
1281
- header_style="bold bright_cyan", padding=(0, 2))
1282
- table.add_column("Command", style="bright_cyan", min_width=12)
1283
- table.add_column("Description", style="dim")
1284
- for cmd, desc in COMMANDS.items():
1285
- table.add_row(cmd, desc)
1286
- console.print(table)
1277
+ # Group commands by category — clean minimal list
1278
+ categories = {
1279
+ "Chat": ["/new", "/save", "/load", "/delete", "/copy", "/regen", "/edit", "/history", "/clear", "/quit"],
1280
+ "Model": ["/model", "/modelinfo", "/params", "/temp", "/think", "/tokens", "/compact"],
1281
+ "AI": ["/agent", "/agents", "/all", "/vote", "/swarm", "/team", "/room", "/spectate", "/dm", "/chat-link"],
1282
+ "Lab": ["/lab", "/chain", "/race", "/prompts"],
1283
+ "Tools": ["/tools", "/bg", "/split", "/grid", "/running", "/killall"],
1284
+ "Connect": ["/connect", "/disconnect", "/server", "/qr", "/scan"],
1285
+ "Files": ["/file", "/run", "/code", "/shell", "/browse", "/open", "/export"],
1286
+ "Memory": ["/mem", "/train", "/pin", "/pins", "/search", "/fork", "/rate", "/tag"],
1287
+ "Profile": ["/profile", "/setname", "/setbio", "/persona", "/personas", "/usage"],
1288
+ "Skills": ["/skill", "/skills", "/auto", "/cron", "/crons"],
1289
+ "Comms": ["/broadcast", "/inbox", "/feed", "/monitor", "/hub"],
1290
+ "System": ["/github", "/weather", "/spotify", "/volume", "/bright", "/sysinfo"],
1291
+ "Security": ["/pin-set", "/pin-remove", "/lock", "/audit", "/security", "/permissions"],
1292
+ }
1293
+
1294
+ on_termux = os.path.exists("/data/data/com.termux")
1295
+
1296
+ for cat, cmds in categories.items():
1297
+ console.print(Text(f"\n {cat}", style="bold bright_cyan"))
1298
+ for cmd in cmds:
1299
+ desc = COMMANDS.get(cmd, "")
1300
+ if not desc:
1301
+ continue
1302
+ # Hide unsupported tool commands on Termux
1303
+ tool_name = cmd[1:]
1304
+ if on_termux and tool_name in AI_TOOLS and not AI_TOOLS[tool_name].get("termux", True):
1305
+ continue
1306
+ console.print(Text.from_markup(f" [bright_cyan]{cmd:<16}[/] [dim]{desc}[/]"))
1307
+
1308
+ console.print(Text("\n Type / to autocomplete. Aliases: /q /n /s /m /h /a /t /f", style="dim"))
1287
1309
  console.print()
1288
1310
 
1289
1311
 
@@ -2412,12 +2434,53 @@ def get_weather(city):
2412
2434
 
2413
2435
 
2414
2436
  def open_url(url):
2415
- """Open a URL in the default browser."""
2437
+ """Open a URL or search query in the default browser."""
2416
2438
  import webbrowser
2417
- if not url.startswith("http"):
2439
+
2440
+ # Shortcuts
2441
+ shortcuts = {
2442
+ "google": "https://google.com",
2443
+ "youtube": "https://youtube.com",
2444
+ "github": "https://github.com",
2445
+ "reddit": "https://reddit.com",
2446
+ "twitter": "https://x.com",
2447
+ "x": "https://x.com",
2448
+ "stackoverflow": "https://stackoverflow.com",
2449
+ "npm": "https://npmjs.com",
2450
+ "pypi": "https://pypi.org",
2451
+ "ollama": "https://ollama.com",
2452
+ "claude": "https://claude.ai",
2453
+ "chatgpt": "https://chat.openai.com",
2454
+ "gemini": "https://gemini.google.com",
2455
+ }
2456
+
2457
+ if url.lower() in shortcuts:
2458
+ url = shortcuts[url.lower()]
2459
+ elif "." not in url and ":" not in url:
2460
+ # No dots = search query, not a URL
2461
+ query = url.replace(" ", "+")
2462
+ url = f"https://google.com/search?q={query}"
2463
+ elif not url.startswith("http"):
2418
2464
  url = "https://" + url
2419
- webbrowser.open(url)
2420
- print_sys(f"Opened: {url}")
2465
+
2466
+ # Platform-specific browser open
2467
+ try:
2468
+ if os.path.exists("/data/data/com.termux"):
2469
+ # Termux — use termux-open or am start
2470
+ try:
2471
+ subprocess.run(["termux-open-url", url], timeout=5)
2472
+ except FileNotFoundError:
2473
+ subprocess.run(["am", "start", "-a", "android.intent.action.VIEW", "-d", url], timeout=5)
2474
+ elif os.name == "nt":
2475
+ os.startfile(url)
2476
+ elif sys.platform == "darwin":
2477
+ subprocess.run(["open", url], timeout=5)
2478
+ else:
2479
+ webbrowser.open(url)
2480
+ print_sys(f"Opened: {url}")
2481
+ except Exception as e:
2482
+ print_err(f"Cannot open browser: {e}")
2483
+ print_sys(f"URL: {url}")
2421
2484
  audit_log("OPEN_URL", url)
2422
2485
 
2423
2486
 
@@ -3884,6 +3947,150 @@ def team_chat(name1, name2, default_model, system):
3884
3947
  return history
3885
3948
 
3886
3949
 
3950
+ # --- Chat Room ---
3951
+
3952
+ def chat_room(member_names, default_model, system, user_joins=True, topic=""):
3953
+ """Multi-AI chat room. User can join or spectate."""
3954
+ members = [resolve_team_member(n) for n in member_names]
3955
+
3956
+ names_display = ", ".join(f"[{m['color']}]{m['name']}[/]" for m in members)
3957
+ mode = "Join" if user_joins else "Spectate"
3958
+
3959
+ console.print(Rule(style="bright_green", characters="─"))
3960
+ console.print(Text.from_markup(
3961
+ f" [bold]Chat Room[/] — {mode} mode\n"
3962
+ f" Members: {names_display}\n"
3963
+ ))
3964
+ if user_joins:
3965
+ console.print(Text(" Type to talk. @name to address one AI. 'exit' to leave.", style="dim"))
3966
+ else:
3967
+ console.print(Text(" Watching AIs chat. Ctrl+C to stop.", style="dim"))
3968
+ console.print(Rule(style="bright_green", characters="─"))
3969
+ console.print()
3970
+
3971
+ history = []
3972
+
3973
+ if user_joins:
3974
+ # Interactive room — user + multiple AIs
3975
+ while True:
3976
+ try:
3977
+ user_input = prompt(
3978
+ [("class:prompt", " You ❯ ")],
3979
+ style=input_style,
3980
+ history=input_history,
3981
+ ).strip()
3982
+ except (KeyboardInterrupt, EOFError):
3983
+ break
3984
+
3985
+ if not user_input or user_input.lower() in ("exit", "/exit", "quit"):
3986
+ break
3987
+
3988
+ console.print(Text(f" {user_input}", style="bold white"))
3989
+ console.print()
3990
+ history.append({"speaker": "user", "content": user_input})
3991
+
3992
+ # Check for @mentions
3993
+ mentioned = []
3994
+ for m in members:
3995
+ if f"@{m['name']}" in user_input.lower():
3996
+ mentioned.append(m)
3997
+
3998
+ # If no mentions, all respond
3999
+ responders = mentioned if mentioned else members
4000
+
4001
+ for member in responders:
4002
+ others = [m['name'] for m in members if m != member] + ["user"]
4003
+ conv = "\n".join(
4004
+ f"{h['speaker']}: {h['content'][:200]}"
4005
+ for h in history[-10:]
4006
+ )
4007
+
4008
+ room_prompt = (
4009
+ f"You are {member['name']} in a group chat with {', '.join(others)}.\n"
4010
+ f"Chat so far:\n{conv}\n\n"
4011
+ f"Respond as {member['name']}. Keep it short (2-4 sentences). "
4012
+ f"React to what was said. Agree, disagree, or add something new. "
4013
+ f"Don't repeat what others said."
4014
+ )
4015
+
4016
+ try:
4017
+ resp = requests.post(OLLAMA_URL, json={
4018
+ "model": member["model"] or default_model,
4019
+ "messages": [
4020
+ {"role": "system", "content": member["system"]},
4021
+ {"role": "user", "content": room_prompt},
4022
+ ],
4023
+ "stream": False,
4024
+ }, timeout=60)
4025
+ response = resp.json().get("message", {}).get("content", "")
4026
+ except Exception as e:
4027
+ response = f"(offline)"
4028
+
4029
+ console.print(Text.from_markup(f" [{member['color']}]{member['name']}[/] {response}"))
4030
+ console.print()
4031
+ history.append({"speaker": member["name"], "content": response})
4032
+ bus_send(member["name"], "codegpt", response[:200], "response")
4033
+
4034
+ else:
4035
+ # Spectate mode — AIs chat with each other, user watches
4036
+ try:
4037
+ # Get initial topic from last arg or default
4038
+ if not topic:
4039
+ topic = "Introduce yourselves and start a technical discussion."
4040
+ if history:
4041
+ topic = history[-1]["content"]
4042
+
4043
+ current_input = topic
4044
+ rounds = 0
4045
+ max_rounds = 12
4046
+
4047
+ while rounds < max_rounds:
4048
+ for member in members:
4049
+ rounds += 1
4050
+ if rounds > max_rounds:
4051
+ break
4052
+
4053
+ others = [m['name'] for m in members if m != member]
4054
+ conv = "\n".join(
4055
+ f"{h['speaker']}: {h['content'][:200]}"
4056
+ for h in history[-8:]
4057
+ )
4058
+
4059
+ room_prompt = (
4060
+ f"You are {member['name']} in a group chat with {', '.join(others)}.\n"
4061
+ f"{'Topic: ' + current_input if not history else 'Chat so far:'}\n"
4062
+ f"{conv}\n\n"
4063
+ f"Respond as {member['name']}. Keep it short (2-3 sentences). "
4064
+ f"Build on the conversation. Be opinionated."
4065
+ )
4066
+
4067
+ try:
4068
+ resp = requests.post(OLLAMA_URL, json={
4069
+ "model": member["model"] or default_model,
4070
+ "messages": [
4071
+ {"role": "system", "content": member["system"]},
4072
+ {"role": "user", "content": room_prompt},
4073
+ ],
4074
+ "stream": False,
4075
+ }, timeout=60)
4076
+ response = resp.json().get("message", {}).get("content", "")
4077
+ except Exception as e:
4078
+ response = "(offline)"
4079
+
4080
+ console.print(Text.from_markup(f" [{member['color']}]{member['name']}[/] {response}"))
4081
+ console.print()
4082
+ history.append({"speaker": member["name"], "content": response})
4083
+ time.sleep(0.5)
4084
+
4085
+ except KeyboardInterrupt:
4086
+ pass
4087
+
4088
+ console.print(Rule(style="dim", characters="─"))
4089
+ console.print(Text(f" Room closed. {len(history)} messages.", style="dim"))
4090
+ console.print()
4091
+ return history
4092
+
4093
+
3887
4094
  # --- Split Screen ---
3888
4095
 
3889
4096
  def get_tool_cmd(name):
@@ -4083,7 +4290,7 @@ def delete_skill(name):
4083
4290
 
4084
4291
  # --- Browser ---
4085
4292
 
4086
- def browse_url(url):
4293
+ def browse_url(url, model=None):
4087
4294
  """Fetch a URL, extract text, and summarize it."""
4088
4295
  if not url.startswith("http"):
4089
4296
  url = "https://" + url
@@ -4112,7 +4319,7 @@ def browse_url(url):
4112
4319
  # Ask AI to summarize
4113
4320
  try:
4114
4321
  ai_resp = requests.post(OLLAMA_URL, json={
4115
- "model": MODEL,
4322
+ "model": model or MODEL,
4116
4323
  "messages": [
4117
4324
  {"role": "system", "content": "Summarize this web page content in 3-5 bullet points. Be concise."},
4118
4325
  {"role": "user", "content": f"URL: {url}\n\nContent:\n{text}"},
@@ -4136,6 +4343,7 @@ def browse_url(url):
4136
4343
  # --- Cron / Scheduled Tasks ---
4137
4344
 
4138
4345
  active_crons = []
4346
+ cron_command_queue = [] # Thread-safe command queue for cron execution
4139
4347
 
4140
4348
 
4141
4349
  def add_cron(interval_str, command):
@@ -4161,10 +4369,10 @@ def add_cron(interval_str, command):
4161
4369
  # Check if still active
4162
4370
  if cron_entry not in active_crons:
4163
4371
  break
4164
- print_sys(f"[cron] Running: {command}")
4165
- # Execute as if user typed it
4166
4372
  cron_entry["last_run"] = datetime.now().isoformat()
4167
4373
  cron_entry["runs"] += 1
4374
+ # Queue the command for main loop to execute
4375
+ cron_command_queue.append(command)
4168
4376
 
4169
4377
  cron_entry = {
4170
4378
  "command": command,
@@ -4444,7 +4652,7 @@ def get_input():
4444
4652
  # --- Main ---
4445
4653
 
4446
4654
  def main():
4447
- global last_ai_response, code_exec_count, OLLAMA_URL, sidebar_enabled
4655
+ global last_ai_response, code_exec_count, OLLAMA_URL, sidebar_enabled, think_mode, temperature
4448
4656
 
4449
4657
  # CLI args mode: python chat.py --ask "question" or python chat.py --cmd "/tools"
4450
4658
  if len(sys.argv) > 1:
@@ -4716,6 +4924,11 @@ def main():
4716
4924
  audit_log("LOCKED_OUT")
4717
4925
  break
4718
4926
 
4927
+ # Drain cron command queue
4928
+ while cron_command_queue:
4929
+ cron_cmd = cron_command_queue.pop(0)
4930
+ print_sys(f"[cron] {cron_cmd}")
4931
+
4719
4932
  user_input = get_input()
4720
4933
  if user_input is None:
4721
4934
  cancel_all_reminders()
@@ -4782,10 +4995,10 @@ def main():
4782
4995
  continue
4783
4996
 
4784
4997
  elif cmd == "/save":
4785
- if messages and ask_permission("save_chat", "Save conversation"):
4786
- save_conversation(messages, model)
4787
- else:
4998
+ if not messages:
4788
4999
  print_sys("Nothing to save.")
5000
+ elif ask_permission("save_chat", "Save conversation"):
5001
+ save_conversation(messages, model)
4789
5002
  continue
4790
5003
 
4791
5004
  elif cmd == "/load":
@@ -5271,10 +5484,10 @@ def main():
5271
5484
  elif sub == "clear":
5272
5485
  mem_clear()
5273
5486
  elif sub == "inject":
5274
- # Inject memories into current conversation as context
5487
+ # Inject memories as a user-role context message (not system — avoids corrupting conversation)
5275
5488
  mem_context = get_memory_context()
5276
5489
  if mem_context:
5277
- messages.append({"role": "system", "content": mem_context})
5490
+ messages.append({"role": "user", "content": f"[Memory context for reference]:\n{mem_context}"})
5278
5491
  print_sys("Memories injected into context.")
5279
5492
  else:
5280
5493
  print_sys("No memories to inject.")
@@ -5656,6 +5869,45 @@ def main():
5656
5869
  print_sys("Terminal too narrow for sidebar. Widen to 80+ chars.")
5657
5870
  continue
5658
5871
 
5872
+ elif cmd == "/room":
5873
+ parts = user_input[len("/room "):].strip().split()
5874
+ if len(parts) >= 2:
5875
+ history = chat_room(parts, model, system, user_joins=True)
5876
+ for h in history:
5877
+ if h["speaker"] == "user":
5878
+ messages.append({"role": "user", "content": h["content"]})
5879
+ else:
5880
+ messages.append({"role": "assistant", "content": f"[{h['speaker']}] {h['content']}"})
5881
+ session_stats["messages"] += len(history)
5882
+ else:
5883
+ print_sys("Usage: /room coder reviewer architect")
5884
+ print_sys(" /room claude codex gemini deepseek")
5885
+ print_sys(f"\nAvailable: {', '.join(list(AI_AGENTS.keys()) + list(TOOL_PERSONAS.keys()))}")
5886
+ continue
5887
+
5888
+ elif cmd == "/spectate":
5889
+ args = user_input[len("/spectate "):].strip().split()
5890
+ if len(args) >= 2:
5891
+ # Last arg could be a topic
5892
+ names = args
5893
+ topic = ""
5894
+ # Check if last args aren't AI names — treat as topic
5895
+ all_names = set(AI_AGENTS.keys()) | set(TOOL_PERSONAS.keys())
5896
+ topic_words = []
5897
+ while names and names[-1] not in all_names:
5898
+ topic_words.insert(0, names.pop())
5899
+ topic = " ".join(topic_words) if topic_words else "Discuss the best programming practices"
5900
+
5901
+ if len(names) >= 2:
5902
+ # Inject topic
5903
+ h = chat_room(names, model, system, user_joins=False, topic=topic)
5904
+ else:
5905
+ print_sys("Need at least 2 AIs. Example: /spectate coder reviewer discuss Python")
5906
+ else:
5907
+ print_sys("Usage: /spectate coder reviewer discuss error handling")
5908
+ print_sys(" /spectate claude codex gemini debate which is best")
5909
+ continue
5910
+
5659
5911
  elif cmd == "/monitor":
5660
5912
  # Live updating dashboard — press Ctrl+C to exit
5661
5913
  console.print(Text(" Live monitor — Ctrl+C to stop\n", style="dim"))
@@ -5979,7 +6231,7 @@ def main():
5979
6231
  launch_cmd = [tool_bin] + tool.get("default_args", [])
5980
6232
  if tool_args:
5981
6233
  launch_cmd.append(tool_args)
5982
- subprocess.run(launch_cmd, shell=True, cwd=project_dir, env=tool_env)
6234
+ subprocess.run(" ".join(launch_cmd), shell=True, cwd=project_dir, env=tool_env)
5983
6235
  else:
5984
6236
  tool_sandbox = Path.home() / ".codegpt" / "sandbox" / tool_key
5985
6237
  tool_sandbox.mkdir(parents=True, exist_ok=True)
@@ -6016,7 +6268,7 @@ def main():
6016
6268
  launch_cmd = [tool_bin] + tool.get("default_args", [])
6017
6269
  if tool_args:
6018
6270
  launch_cmd.append(tool_args)
6019
- subprocess.run(launch_cmd, shell=True, cwd=str(tool_sandbox), env=tool_env)
6271
+ subprocess.run(" ".join(launch_cmd), shell=True, cwd=str(tool_sandbox), env=tool_env)
6020
6272
 
6021
6273
  print_sys("Back to CodeGPT.")
6022
6274
  audit_log(f"TOOL_EXIT", tool_key)
@@ -6133,7 +6385,7 @@ def main():
6133
6385
  print_sys(f"Installed in {elapsed:.1f}s. Launching...")
6134
6386
  audit_log(f"TOOL_INSTALL", tool_key)
6135
6387
  launch_cmd = [found_bin] + tool.get("default_args", [])
6136
- subprocess.run(launch_cmd, shell=True)
6388
+ subprocess.run(" ".join(launch_cmd), shell=True)
6137
6389
  print_sys("Back to CodeGPT.")
6138
6390
  elif tool_bin in pip_module_map:
6139
6391
  # Try python -m fallback
@@ -6696,7 +6948,7 @@ def main():
6696
6948
  elif cmd == "/browse":
6697
6949
  url = user_input[len("/browse "):].strip()
6698
6950
  if url and ask_permission("open_url", f"Fetch {url}"):
6699
- content = browse_url(url)
6951
+ content = browse_url(url, model=model)
6700
6952
  if content:
6701
6953
  messages.append({"role": "user", "content": f"[browsed: {url}]"})
6702
6954
  messages.append({"role": "assistant", "content": content[:500]})
@@ -6761,21 +7013,23 @@ def main():
6761
7013
  save_permissions()
6762
7014
  print_sys("All permissions reset. You'll be asked again.")
6763
7015
  else:
6764
- table = Table(title="Permissions", border_style="yellow",
6765
- title_style="bold yellow", show_header=True, header_style="bold")
6766
- table.add_column("Action", style="bright_cyan", width=16)
6767
- table.add_column("Description", style="dim")
6768
- table.add_column("Risk", width=10)
6769
- table.add_column("Status", width=10)
6770
- for action, info in RISKY_ACTIONS.items():
6771
- if isinstance(info, tuple):
6772
- desc, risk = info
6773
- else:
6774
- desc, risk = info, "MEDIUM"
6775
- rc = RISK_COLORS.get(risk, "yellow")
6776
- ri = RISK_ICONS.get(risk, "?")
6777
- status = "[green]allowed[/]" if action in PERMISSION_ALWAYS_ALLOW else "[yellow]ask[/]"
6778
- table.add_row(action, desc, f"[{rc}]{ri} {risk}[/]", status)
7016
+ console.print(Text("\n Permissions", style="bold"))
7017
+ console.print(Rule(style="dim", characters=""))
7018
+
7019
+ # Group by risk level
7020
+ for risk_level in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]:
7021
+ rc = RISK_COLORS.get(risk_level, "yellow")
7022
+ ri = RISK_ICONS.get(risk_level, "?")
7023
+ console.print(Text.from_markup(f"\n [{rc}]{ri} {risk_level}[/]"))
7024
+ for action, info in RISKY_ACTIONS.items():
7025
+ if isinstance(info, tuple):
7026
+ desc, risk = info
7027
+ else:
7028
+ desc, risk = info, "MEDIUM"
7029
+ if risk != risk_level:
7030
+ continue
7031
+ status = "[green]✓ allowed[/]" if action in PERMISSION_ALWAYS_ALLOW else "[dim]ask[/]"
7032
+ console.print(Text.from_markup(f" {action:<16} {status} [dim]{desc}[/]"))
6779
7033
  console.print(table)
6780
7034
  console.print(Text(" /permissions reset — revoke all", style="dim"))
6781
7035
  console.print()
@@ -6809,7 +7063,7 @@ def main():
6809
7063
  f" Log entries [bright_cyan]{audit_count}[/]\n"
6810
7064
  f" Log file [dim]{AUDIT_FILE}[/]\n\n"
6811
7065
  f"[bold]Storage[/]\n"
6812
- f" Encrypted [yellow]local XOR[/]\n"
7066
+ f" PIN hash [green]SHA-256[/]\n"
6813
7067
  f" Location [dim]{SECURITY_DIR}[/]\n"
6814
7068
  ),
6815
7069
  border_style="bright_cyan",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codegpt-ai",
3
- "version": "1.27.0",
3
+ "version": "1.28.2",
4
4
  "description": "Local AI Assistant Hub — 80+ commands, 29 tools, 8 agents, training, security",
5
5
  "author": "ArukuX",
6
6
  "license": "MIT",