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.
- package/chat.py +319 -65
- 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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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", "
|
|
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
|
-
|
|
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
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2420
|
-
|
|
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
|
|
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
|
|
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": "
|
|
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
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
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"
|
|
7066
|
+
f" PIN hash [green]SHA-256[/]\n"
|
|
6813
7067
|
f" Location [dim]{SECURITY_DIR}[/]\n"
|
|
6814
7068
|
),
|
|
6815
7069
|
border_style="bright_cyan",
|