codegpt-ai 1.28.1 → 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 +111 -67
  2. package/package.json +1 -1
package/chat.py CHANGED
@@ -609,32 +609,27 @@ def ask_permission(action, detail=""):
609
609
 
610
610
  risk_color = RISK_COLORS.get(risk, "yellow")
611
611
  risk_icon = RISK_ICONS.get(risk, "?")
612
- compact = is_compact()
613
612
 
614
- if compact:
615
- console.print(Panel(
616
- Text.from_markup(
617
- f"[{risk_color}]{risk_icon} {risk}[/]\n"
618
- f" {action_desc}\n"
619
- + (f" [dim]{detail[:35]}[/]\n" if detail else "")
620
- ),
621
- border_style=risk_color.replace("bold ", ""), padding=(0, 1), width=tw(),
622
- ))
623
- else:
624
- console.print(Panel(
625
- Text.from_markup(
626
- f"[{risk_color}]{risk_icon} Risk: {risk}[/]\n\n"
627
- f" Action: [bright_cyan]{action_desc}[/]\n"
628
- + (f" Detail: [dim]{detail[:60]}[/]\n" if detail else "")
629
- + f"\n [dim](y)es (n)o (a)lways allow this[/]"
630
- ),
631
- title=f"[{risk_color}]Permission[/]",
632
- border_style=risk_color.replace("bold ", ""), padding=(0, 2), width=tw(),
633
- ))
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()
634
629
 
635
630
  try:
636
631
  answer = prompt(
637
- [("class:prompt", " Allow? (y/n/a) > ")],
632
+ [("class:prompt", " Allow? (y)es / (n)o / (a)lways > ")],
638
633
  style=input_style,
639
634
  ).strip().lower()
640
635
  except (KeyboardInterrupt, EOFError):
@@ -643,9 +638,9 @@ def ask_permission(action, detail=""):
643
638
  if answer in ("a", "always"):
644
639
  PERMISSION_ALWAYS_ALLOW.add(action)
645
640
  save_permissions()
646
- print_sys(f"Always allowed: {action_desc}")
641
+ console.print(Text(f"Always allowed", style="green"))
647
642
  return True
648
- elif answer in ("y", "yes"):
643
+ elif answer in ("y", "yes", ""):
649
644
  return True
650
645
  else:
651
646
  print_sys("Denied.")
@@ -1279,13 +1274,38 @@ def _print_err_panel(text):
1279
1274
 
1280
1275
 
1281
1276
  def print_help():
1282
- table = Table(border_style="bright_black", show_header=True,
1283
- header_style="bold bright_cyan", padding=(0, 2))
1284
- table.add_column("Command", style="bright_cyan", min_width=12)
1285
- table.add_column("Description", style="dim")
1286
- for cmd, desc in COMMANDS.items():
1287
- table.add_row(cmd, desc)
1288
- 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"))
1289
1309
  console.print()
1290
1310
 
1291
1311
 
@@ -2443,8 +2463,24 @@ def open_url(url):
2443
2463
  elif not url.startswith("http"):
2444
2464
  url = "https://" + url
2445
2465
 
2446
- webbrowser.open(url)
2447
- print_sys(f"Opened: {url}")
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}")
2448
2484
  audit_log("OPEN_URL", url)
2449
2485
 
2450
2486
 
@@ -3913,7 +3949,7 @@ def team_chat(name1, name2, default_model, system):
3913
3949
 
3914
3950
  # --- Chat Room ---
3915
3951
 
3916
- def chat_room(member_names, default_model, system, user_joins=True):
3952
+ def chat_room(member_names, default_model, system, user_joins=True, topic=""):
3917
3953
  """Multi-AI chat room. User can join or spectate."""
3918
3954
  members = [resolve_team_member(n) for n in member_names]
3919
3955
 
@@ -3999,7 +4035,8 @@ def chat_room(member_names, default_model, system, user_joins=True):
3999
4035
  # Spectate mode — AIs chat with each other, user watches
4000
4036
  try:
4001
4037
  # Get initial topic from last arg or default
4002
- topic = "Introduce yourselves and start a technical discussion."
4038
+ if not topic:
4039
+ topic = "Introduce yourselves and start a technical discussion."
4003
4040
  if history:
4004
4041
  topic = history[-1]["content"]
4005
4042
 
@@ -4253,7 +4290,7 @@ def delete_skill(name):
4253
4290
 
4254
4291
  # --- Browser ---
4255
4292
 
4256
- def browse_url(url):
4293
+ def browse_url(url, model=None):
4257
4294
  """Fetch a URL, extract text, and summarize it."""
4258
4295
  if not url.startswith("http"):
4259
4296
  url = "https://" + url
@@ -4282,7 +4319,7 @@ def browse_url(url):
4282
4319
  # Ask AI to summarize
4283
4320
  try:
4284
4321
  ai_resp = requests.post(OLLAMA_URL, json={
4285
- "model": MODEL,
4322
+ "model": model or MODEL,
4286
4323
  "messages": [
4287
4324
  {"role": "system", "content": "Summarize this web page content in 3-5 bullet points. Be concise."},
4288
4325
  {"role": "user", "content": f"URL: {url}\n\nContent:\n{text}"},
@@ -4306,6 +4343,7 @@ def browse_url(url):
4306
4343
  # --- Cron / Scheduled Tasks ---
4307
4344
 
4308
4345
  active_crons = []
4346
+ cron_command_queue = [] # Thread-safe command queue for cron execution
4309
4347
 
4310
4348
 
4311
4349
  def add_cron(interval_str, command):
@@ -4331,10 +4369,10 @@ def add_cron(interval_str, command):
4331
4369
  # Check if still active
4332
4370
  if cron_entry not in active_crons:
4333
4371
  break
4334
- print_sys(f"[cron] Running: {command}")
4335
- # Execute as if user typed it
4336
4372
  cron_entry["last_run"] = datetime.now().isoformat()
4337
4373
  cron_entry["runs"] += 1
4374
+ # Queue the command for main loop to execute
4375
+ cron_command_queue.append(command)
4338
4376
 
4339
4377
  cron_entry = {
4340
4378
  "command": command,
@@ -4614,7 +4652,7 @@ def get_input():
4614
4652
  # --- Main ---
4615
4653
 
4616
4654
  def main():
4617
- 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
4618
4656
 
4619
4657
  # CLI args mode: python chat.py --ask "question" or python chat.py --cmd "/tools"
4620
4658
  if len(sys.argv) > 1:
@@ -4886,6 +4924,11 @@ def main():
4886
4924
  audit_log("LOCKED_OUT")
4887
4925
  break
4888
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
+
4889
4932
  user_input = get_input()
4890
4933
  if user_input is None:
4891
4934
  cancel_all_reminders()
@@ -4952,10 +4995,10 @@ def main():
4952
4995
  continue
4953
4996
 
4954
4997
  elif cmd == "/save":
4955
- if messages and ask_permission("save_chat", "Save conversation"):
4956
- save_conversation(messages, model)
4957
- else:
4998
+ if not messages:
4958
4999
  print_sys("Nothing to save.")
5000
+ elif ask_permission("save_chat", "Save conversation"):
5001
+ save_conversation(messages, model)
4959
5002
  continue
4960
5003
 
4961
5004
  elif cmd == "/load":
@@ -5441,10 +5484,10 @@ def main():
5441
5484
  elif sub == "clear":
5442
5485
  mem_clear()
5443
5486
  elif sub == "inject":
5444
- # Inject memories into current conversation as context
5487
+ # Inject memories as a user-role context message (not system — avoids corrupting conversation)
5445
5488
  mem_context = get_memory_context()
5446
5489
  if mem_context:
5447
- messages.append({"role": "system", "content": mem_context})
5490
+ messages.append({"role": "user", "content": f"[Memory context for reference]:\n{mem_context}"})
5448
5491
  print_sys("Memories injected into context.")
5449
5492
  else:
5450
5493
  print_sys("No memories to inject.")
@@ -5857,8 +5900,7 @@ def main():
5857
5900
 
5858
5901
  if len(names) >= 2:
5859
5902
  # Inject topic
5860
- history_init = [{"speaker": "moderator", "content": topic}]
5861
- h = chat_room(names, model, system, user_joins=False)
5903
+ h = chat_room(names, model, system, user_joins=False, topic=topic)
5862
5904
  else:
5863
5905
  print_sys("Need at least 2 AIs. Example: /spectate coder reviewer discuss Python")
5864
5906
  else:
@@ -6189,7 +6231,7 @@ def main():
6189
6231
  launch_cmd = [tool_bin] + tool.get("default_args", [])
6190
6232
  if tool_args:
6191
6233
  launch_cmd.append(tool_args)
6192
- 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)
6193
6235
  else:
6194
6236
  tool_sandbox = Path.home() / ".codegpt" / "sandbox" / tool_key
6195
6237
  tool_sandbox.mkdir(parents=True, exist_ok=True)
@@ -6226,7 +6268,7 @@ def main():
6226
6268
  launch_cmd = [tool_bin] + tool.get("default_args", [])
6227
6269
  if tool_args:
6228
6270
  launch_cmd.append(tool_args)
6229
- 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)
6230
6272
 
6231
6273
  print_sys("Back to CodeGPT.")
6232
6274
  audit_log(f"TOOL_EXIT", tool_key)
@@ -6343,7 +6385,7 @@ def main():
6343
6385
  print_sys(f"Installed in {elapsed:.1f}s. Launching...")
6344
6386
  audit_log(f"TOOL_INSTALL", tool_key)
6345
6387
  launch_cmd = [found_bin] + tool.get("default_args", [])
6346
- subprocess.run(launch_cmd, shell=True)
6388
+ subprocess.run(" ".join(launch_cmd), shell=True)
6347
6389
  print_sys("Back to CodeGPT.")
6348
6390
  elif tool_bin in pip_module_map:
6349
6391
  # Try python -m fallback
@@ -6906,7 +6948,7 @@ def main():
6906
6948
  elif cmd == "/browse":
6907
6949
  url = user_input[len("/browse "):].strip()
6908
6950
  if url and ask_permission("open_url", f"Fetch {url}"):
6909
- content = browse_url(url)
6951
+ content = browse_url(url, model=model)
6910
6952
  if content:
6911
6953
  messages.append({"role": "user", "content": f"[browsed: {url}]"})
6912
6954
  messages.append({"role": "assistant", "content": content[:500]})
@@ -6971,21 +7013,23 @@ def main():
6971
7013
  save_permissions()
6972
7014
  print_sys("All permissions reset. You'll be asked again.")
6973
7015
  else:
6974
- table = Table(title="Permissions", border_style="yellow",
6975
- title_style="bold yellow", show_header=True, header_style="bold")
6976
- table.add_column("Action", style="bright_cyan", width=16)
6977
- table.add_column("Description", style="dim")
6978
- table.add_column("Risk", width=10)
6979
- table.add_column("Status", width=10)
6980
- for action, info in RISKY_ACTIONS.items():
6981
- if isinstance(info, tuple):
6982
- desc, risk = info
6983
- else:
6984
- desc, risk = info, "MEDIUM"
6985
- rc = RISK_COLORS.get(risk, "yellow")
6986
- ri = RISK_ICONS.get(risk, "?")
6987
- status = "[green]allowed[/]" if action in PERMISSION_ALWAYS_ALLOW else "[yellow]ask[/]"
6988
- 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}[/]"))
6989
7033
  console.print(table)
6990
7034
  console.print(Text(" /permissions reset — revoke all", style="dim"))
6991
7035
  console.print()
@@ -7019,7 +7063,7 @@ def main():
7019
7063
  f" Log entries [bright_cyan]{audit_count}[/]\n"
7020
7064
  f" Log file [dim]{AUDIT_FILE}[/]\n\n"
7021
7065
  f"[bold]Storage[/]\n"
7022
- f" Encrypted [yellow]local XOR[/]\n"
7066
+ f" PIN hash [green]SHA-256[/]\n"
7023
7067
  f" Location [dim]{SECURITY_DIR}[/]\n"
7024
7068
  ),
7025
7069
  border_style="bright_cyan",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codegpt-ai",
3
- "version": "1.28.1",
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",