codegpt-ai 2.27.0 → 2.28.0
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/package.json +1 -1
- package/tui.py +271 -8
package/package.json
CHANGED
package/tui.py
CHANGED
|
@@ -3,6 +3,8 @@ import json
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
|
+
import subprocess
|
|
7
|
+
import threading
|
|
6
8
|
import requests
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
from datetime import datetime
|
|
@@ -123,6 +125,61 @@ TUI_COMMANDS = {
|
|
|
123
125
|
"/weather": "Get weather",
|
|
124
126
|
"/agent": "Run an AI agent",
|
|
125
127
|
"/quit": "Exit",
|
|
128
|
+
"/temp": "Set temperature (0-2)",
|
|
129
|
+
"/system": "Set system prompt",
|
|
130
|
+
"/export": "Export chat as text",
|
|
131
|
+
"/browse": "Fetch and summarize URL",
|
|
132
|
+
"/open": "Open URL in browser",
|
|
133
|
+
"/save": "Save conversation",
|
|
134
|
+
"/copy": "Copy last response",
|
|
135
|
+
"/regen": "Regenerate last response",
|
|
136
|
+
"/compact": "Summarize old messages",
|
|
137
|
+
"/search": "Search conversation",
|
|
138
|
+
"/diff": "Compare last 2 responses",
|
|
139
|
+
"/pin": "Pin a message",
|
|
140
|
+
"/pins": "Show pinned messages",
|
|
141
|
+
"/fork": "Fork conversation from #",
|
|
142
|
+
"/rate": "Rate last response (good/bad)",
|
|
143
|
+
"/all": "Ask ALL agents at once",
|
|
144
|
+
"/vote": "Agents vote on a question",
|
|
145
|
+
"/swarm": "6-agent pipeline",
|
|
146
|
+
"/team": "Group chat with 2 AIs",
|
|
147
|
+
"/room": "Chat room with 3+ AIs",
|
|
148
|
+
"/spectate": "Watch AIs debate",
|
|
149
|
+
"/dm": "Message a specific agent",
|
|
150
|
+
"/race": "Race all models",
|
|
151
|
+
"/compare": "Compare 2 models",
|
|
152
|
+
"/chain": "Chain prompts (p1 | p2 | p3)",
|
|
153
|
+
"/lab": "AI Lab experiments",
|
|
154
|
+
"/train": "AI Training Lab",
|
|
155
|
+
"/mem": "AI memory (save/recall)",
|
|
156
|
+
"/skill": "Create custom command",
|
|
157
|
+
"/skills": "List custom skills",
|
|
158
|
+
"/auto": "AI creates a skill",
|
|
159
|
+
"/cron": "Schedule recurring task",
|
|
160
|
+
"/tools": "List AI tool integrations",
|
|
161
|
+
"/github": "GitHub tools",
|
|
162
|
+
"/spotify": "Spotify controls",
|
|
163
|
+
"/volume": "System volume",
|
|
164
|
+
"/sysinfo": "System info",
|
|
165
|
+
"/usage": "Usage dashboard",
|
|
166
|
+
"/profile": "View profile",
|
|
167
|
+
"/setname": "Set display name",
|
|
168
|
+
"/setbio": "Set bio",
|
|
169
|
+
"/security": "Security dashboard",
|
|
170
|
+
"/permissions": "View permissions",
|
|
171
|
+
"/audit": "Security audit log",
|
|
172
|
+
"/pin-set": "Set login PIN",
|
|
173
|
+
"/lock": "Lock session",
|
|
174
|
+
"/qr": "QR code to connect",
|
|
175
|
+
"/broadcast": "Message all tools",
|
|
176
|
+
"/inbox": "Check messages",
|
|
177
|
+
"/feed": "Message feed",
|
|
178
|
+
"/monitor": "Live dashboard",
|
|
179
|
+
"/hub": "Command center",
|
|
180
|
+
"/shortcuts": "Keyboard shortcuts",
|
|
181
|
+
"/prompts": "Prompt templates",
|
|
182
|
+
"/desktop": "Desktop app (PC only)",
|
|
126
183
|
}
|
|
127
184
|
|
|
128
185
|
|
|
@@ -386,16 +443,222 @@ def handle_command(text):
|
|
|
386
443
|
console.print(Text(" Usage: /agent coder build a flask API", style="dim"))
|
|
387
444
|
console.print()
|
|
388
445
|
|
|
446
|
+
elif cmd == "/temp":
|
|
447
|
+
if args:
|
|
448
|
+
try:
|
|
449
|
+
t = float(args)
|
|
450
|
+
if 0 <= t <= 2:
|
|
451
|
+
console.print(Text(f" Temperature: {t}", style="green"))
|
|
452
|
+
except:
|
|
453
|
+
console.print(Text(" Usage: /temp 0.7 (0.0-2.0)", style="dim"))
|
|
454
|
+
else:
|
|
455
|
+
console.print(Text(" Usage: /temp 0.7", style="dim"))
|
|
456
|
+
console.print()
|
|
457
|
+
|
|
458
|
+
elif cmd == "/system":
|
|
459
|
+
if args:
|
|
460
|
+
system = args
|
|
461
|
+
console.print(Text(" System prompt updated.", style="green"))
|
|
462
|
+
else:
|
|
463
|
+
console.print(Text(f" Current: {system[:80]}...", style="dim"))
|
|
464
|
+
console.print()
|
|
465
|
+
|
|
466
|
+
elif cmd == "/export":
|
|
467
|
+
if messages:
|
|
468
|
+
lines = []
|
|
469
|
+
for m in messages:
|
|
470
|
+
role = "You" if m["role"] == "user" else "AI"
|
|
471
|
+
lines.append(f"{role}: {m['content']}\n")
|
|
472
|
+
export_path = Path.home() / ".codegpt" / f"export_{datetime.now().strftime('%Y%m%d_%H%M')}.md"
|
|
473
|
+
export_path.parent.mkdir(parents=True, exist_ok=True)
|
|
474
|
+
export_path.write_text("\n".join(lines), encoding="utf-8")
|
|
475
|
+
console.print(Text(f" Exported: {export_path}", style="green"))
|
|
476
|
+
else:
|
|
477
|
+
console.print(Text(" Nothing to export.", style="dim"))
|
|
478
|
+
console.print()
|
|
479
|
+
|
|
480
|
+
elif cmd == "/browse":
|
|
481
|
+
if args:
|
|
482
|
+
url = args if args.startswith("http") else "https://" + args
|
|
483
|
+
console.print(Text(f" Fetching {url}...", style="dim"))
|
|
484
|
+
try:
|
|
485
|
+
import re as _re
|
|
486
|
+
r = requests.get(url, timeout=15, headers={"User-Agent": "CodeGPT/2.0"})
|
|
487
|
+
text = _re.sub(r'<script[^>]*>.*?</script>', '', r.text, flags=_re.DOTALL)
|
|
488
|
+
text = _re.sub(r'<style[^>]*>.*?</style>', '', text, flags=_re.DOTALL)
|
|
489
|
+
text = _re.sub(r'<[^>]+>', ' ', text)
|
|
490
|
+
text = _re.sub(r'\s+', ' ', text).strip()[:3000]
|
|
491
|
+
resp = requests.post(OLLAMA_URL, json={
|
|
492
|
+
"model": MODEL, "messages": [
|
|
493
|
+
{"role": "system", "content": "Summarize in 3-5 bullet points."},
|
|
494
|
+
{"role": "user", "content": text},
|
|
495
|
+
], "stream": False,
|
|
496
|
+
}, timeout=60)
|
|
497
|
+
summary = resp.json().get("message", {}).get("content", text[:500])
|
|
498
|
+
print_msg("ai", summary)
|
|
499
|
+
except Exception as e:
|
|
500
|
+
console.print(Text(f" Error: {e}", style="red"))
|
|
501
|
+
else:
|
|
502
|
+
console.print(Text(" Usage: /browse github.com", style="dim"))
|
|
503
|
+
console.print()
|
|
504
|
+
|
|
505
|
+
elif cmd == "/open":
|
|
506
|
+
if args:
|
|
507
|
+
url = args if args.startswith("http") else "https://" + args
|
|
508
|
+
if os.path.exists("/data/data/com.termux"):
|
|
509
|
+
try:
|
|
510
|
+
subprocess.run(["termux-open-url", url], timeout=5)
|
|
511
|
+
except:
|
|
512
|
+
subprocess.run(["am", "start", "-a", "android.intent.action.VIEW", "-d", url], timeout=5)
|
|
513
|
+
else:
|
|
514
|
+
import webbrowser
|
|
515
|
+
webbrowser.open(url)
|
|
516
|
+
console.print(Text(f" Opened: {url}", style="green"))
|
|
517
|
+
else:
|
|
518
|
+
console.print(Text(" Usage: /open google.com", style="dim"))
|
|
519
|
+
console.print()
|
|
520
|
+
|
|
521
|
+
elif cmd == "/all":
|
|
522
|
+
if args:
|
|
523
|
+
agents_list = {
|
|
524
|
+
"coder": "You are an expert programmer.",
|
|
525
|
+
"debugger": "You are a debugging expert.",
|
|
526
|
+
"reviewer": "You are a code reviewer.",
|
|
527
|
+
"architect": "You are a system architect.",
|
|
528
|
+
"pentester": "You are an ethical pentester.",
|
|
529
|
+
"explainer": "You are a patient teacher.",
|
|
530
|
+
"optimizer": "You are a performance engineer.",
|
|
531
|
+
"researcher": "You are a research analyst.",
|
|
532
|
+
}
|
|
533
|
+
import threading
|
|
534
|
+
results = {}
|
|
535
|
+
def _query(n, s):
|
|
536
|
+
try:
|
|
537
|
+
r = requests.post(OLLAMA_URL, json={"model": MODEL, "messages": [
|
|
538
|
+
{"role": "system", "content": s}, {"role": "user", "content": args}
|
|
539
|
+
], "stream": False}, timeout=90)
|
|
540
|
+
results[n] = r.json().get("message", {}).get("content", "")
|
|
541
|
+
except:
|
|
542
|
+
results[n] = "(error)"
|
|
543
|
+
threads = [threading.Thread(target=_query, args=(n, s), daemon=True) for n, s in agents_list.items()]
|
|
544
|
+
for t in threads: t.start()
|
|
545
|
+
console.print(Text(" Asking all 8 agents...", style="dim"))
|
|
546
|
+
for t in threads: t.join(timeout=90)
|
|
547
|
+
for name, resp in results.items():
|
|
548
|
+
console.print(Text.from_markup(f"\n [bright_blue]{name}[/]"))
|
|
549
|
+
console.print(Text(f" {resp[:200]}", style="white"))
|
|
550
|
+
console.print()
|
|
551
|
+
else:
|
|
552
|
+
console.print(Text(" Usage: /all what database should I use?", style="dim"))
|
|
553
|
+
console.print()
|
|
554
|
+
|
|
555
|
+
elif cmd == "/save":
|
|
556
|
+
if messages:
|
|
557
|
+
save_path = Path.home() / ".codegpt" / "chats" / f"{datetime.now().strftime('%Y%m%d_%H%M')}.json"
|
|
558
|
+
save_path.parent.mkdir(parents=True, exist_ok=True)
|
|
559
|
+
save_path.write_text(json.dumps({"messages": messages, "model": MODEL}))
|
|
560
|
+
console.print(Text(f" Saved: {save_path.name}", style="green"))
|
|
561
|
+
else:
|
|
562
|
+
console.print(Text(" Nothing to save.", style="dim"))
|
|
563
|
+
console.print()
|
|
564
|
+
|
|
565
|
+
elif cmd == "/copy":
|
|
566
|
+
ai_msgs = [m for m in messages if m["role"] == "assistant"]
|
|
567
|
+
if ai_msgs:
|
|
568
|
+
last = ai_msgs[-1]["content"]
|
|
569
|
+
try:
|
|
570
|
+
import subprocess
|
|
571
|
+
if os.path.exists("/data/data/com.termux"):
|
|
572
|
+
subprocess.run(["termux-clipboard-set"], input=last.encode(), timeout=5)
|
|
573
|
+
else:
|
|
574
|
+
subprocess.run("clip" if os.name == "nt" else "pbcopy", input=last.encode(), shell=True, timeout=5)
|
|
575
|
+
console.print(Text(" Copied to clipboard.", style="green"))
|
|
576
|
+
except:
|
|
577
|
+
console.print(Text(" Cannot copy — clipboard not available.", style="red"))
|
|
578
|
+
else:
|
|
579
|
+
console.print(Text(" No response to copy.", style="dim"))
|
|
580
|
+
console.print()
|
|
581
|
+
|
|
582
|
+
elif cmd == "/regen":
|
|
583
|
+
if messages and messages[-1]["role"] == "assistant":
|
|
584
|
+
messages.pop()
|
|
585
|
+
if messages and messages[-1]["role"] == "user":
|
|
586
|
+
last_q = messages[-1]["content"]
|
|
587
|
+
messages.pop()
|
|
588
|
+
print_msg("user", last_q)
|
|
589
|
+
chat(last_q)
|
|
590
|
+
else:
|
|
591
|
+
console.print(Text(" Nothing to regenerate.", style="dim"))
|
|
592
|
+
console.print()
|
|
593
|
+
|
|
594
|
+
elif cmd == "/rate":
|
|
595
|
+
rating = args.lower() if args else ""
|
|
596
|
+
if rating in ("good", "bad", "+", "-"):
|
|
597
|
+
ai_msgs = [m for m in messages if m["role"] == "assistant"]
|
|
598
|
+
if ai_msgs:
|
|
599
|
+
ratings_file = Path.home() / ".codegpt" / "ratings.json"
|
|
600
|
+
ratings = []
|
|
601
|
+
if ratings_file.exists():
|
|
602
|
+
try: ratings = json.loads(ratings_file.read_text())
|
|
603
|
+
except: pass
|
|
604
|
+
ratings.append({"rating": "good" if rating in ("good", "+") else "bad",
|
|
605
|
+
"response": ai_msgs[-1]["content"][:200],
|
|
606
|
+
"timestamp": datetime.now().isoformat()})
|
|
607
|
+
ratings_file.write_text(json.dumps(ratings))
|
|
608
|
+
console.print(Text(f" Rated: {rating}", style="green"))
|
|
609
|
+
else:
|
|
610
|
+
console.print(Text(" Usage: /rate good or /rate bad", style="dim"))
|
|
611
|
+
console.print()
|
|
612
|
+
|
|
613
|
+
elif cmd == "/usage":
|
|
614
|
+
profile = {}
|
|
615
|
+
if profile_file.exists():
|
|
616
|
+
try: profile = json.loads(profile_file.read_text())
|
|
617
|
+
except: pass
|
|
618
|
+
console.print(Text.from_markup(
|
|
619
|
+
f" [bold]Session[/]\n"
|
|
620
|
+
f" Messages [bright_blue]{len(messages)}[/]\n"
|
|
621
|
+
f" Tokens [bright_blue]{total_tokens}[/]\n"
|
|
622
|
+
f" Model [bright_blue]{MODEL}[/]\n"
|
|
623
|
+
f" Persona [bright_blue]{persona}[/]\n\n"
|
|
624
|
+
f" [bold]Lifetime[/]\n"
|
|
625
|
+
f" Messages [bright_blue]{profile.get('total_messages', 0)}[/]\n"
|
|
626
|
+
f" Sessions [bright_blue]{profile.get('total_sessions', 0)}[/]"
|
|
627
|
+
))
|
|
628
|
+
console.print()
|
|
629
|
+
|
|
630
|
+
elif cmd == "/profile":
|
|
631
|
+
profile = {}
|
|
632
|
+
if profile_file.exists():
|
|
633
|
+
try: profile = json.loads(profile_file.read_text())
|
|
634
|
+
except: pass
|
|
635
|
+
console.print(Text.from_markup(
|
|
636
|
+
f" [bold]{profile.get('name', 'User')}[/]\n"
|
|
637
|
+
f" Bio {profile.get('bio', '')}\n"
|
|
638
|
+
f" Model [bright_blue]{profile.get('model', MODEL)}[/]\n"
|
|
639
|
+
f" Persona [green]{profile.get('persona', 'default')}[/]\n"
|
|
640
|
+
f" Sessions {profile.get('total_sessions', 0)}"
|
|
641
|
+
))
|
|
642
|
+
console.print()
|
|
643
|
+
|
|
389
644
|
elif cmd == "/help" or cmd == "/h":
|
|
390
|
-
|
|
391
|
-
"
|
|
392
|
-
"
|
|
393
|
-
"
|
|
394
|
-
"
|
|
395
|
-
"
|
|
645
|
+
groups = {
|
|
646
|
+
"Chat": ["/new", "/save", "/copy", "/regen", "/history", "/clear", "/export", "/quit"],
|
|
647
|
+
"Model": ["/model", "/models", "/persona", "/think", "/temp", "/tokens", "/system"],
|
|
648
|
+
"AI": ["/agent", "/all", "/vote", "/swarm", "/team", "/room", "/spectate", "/race"],
|
|
649
|
+
"Files": ["/browse", "/open"],
|
|
650
|
+
"Memory": ["/mem", "/train", "/rate", "/search", "/fork", "/pin"],
|
|
651
|
+
"Tools": ["/tools", "/skill", "/auto", "/cron"],
|
|
652
|
+
"Connect": ["/connect", "/server", "/qr", "/weather", "/sysinfo", "/github"],
|
|
653
|
+
"Profile": ["/profile", "/usage", "/setname", "/setbio", "/permissions"],
|
|
654
|
+
"Security": ["/pin-set", "/lock", "/audit", "/security"],
|
|
396
655
|
}
|
|
397
|
-
for
|
|
398
|
-
console.print(Text
|
|
656
|
+
for group, cmds_list in groups.items():
|
|
657
|
+
console.print(Text(f"\n {group}", style="bold bright_blue"))
|
|
658
|
+
for c in cmds_list:
|
|
659
|
+
desc = TUI_COMMANDS.get(c, "")
|
|
660
|
+
if desc:
|
|
661
|
+
console.print(Text.from_markup(f" [bright_blue]{c:<14}[/] [dim]{desc}[/]"))
|
|
399
662
|
console.print()
|
|
400
663
|
|
|
401
664
|
else:
|