codegpt-ai 2.26.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 +304 -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
|
|
@@ -17,6 +19,7 @@ from rich.rule import Rule
|
|
|
17
19
|
from rich.align import Align
|
|
18
20
|
from prompt_toolkit import prompt
|
|
19
21
|
from prompt_toolkit.history import InMemoryHistory
|
|
22
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
20
23
|
from prompt_toolkit.styles import Style as PtStyle
|
|
21
24
|
|
|
22
25
|
# Config
|
|
@@ -105,6 +108,91 @@ if profile_file.exists():
|
|
|
105
108
|
OLLAMA_BASE = OLLAMA_URL.replace("/api/chat", "")
|
|
106
109
|
|
|
107
110
|
console = Console()
|
|
111
|
+
|
|
112
|
+
TUI_COMMANDS = {
|
|
113
|
+
"/help": "Show all commands",
|
|
114
|
+
"/new": "New conversation",
|
|
115
|
+
"/model": "Switch model",
|
|
116
|
+
"/models": "List all models",
|
|
117
|
+
"/persona": "Switch persona",
|
|
118
|
+
"/think": "Toggle deep thinking",
|
|
119
|
+
"/tokens": "Token count",
|
|
120
|
+
"/clear": "Clear screen",
|
|
121
|
+
"/sidebar": "Toggle sidebar",
|
|
122
|
+
"/history": "Show history",
|
|
123
|
+
"/connect": "Connect to remote Ollama",
|
|
124
|
+
"/server": "Server info",
|
|
125
|
+
"/weather": "Get weather",
|
|
126
|
+
"/agent": "Run an AI agent",
|
|
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)",
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class TuiCompleter(Completer):
|
|
187
|
+
def get_completions(self, document, complete_event):
|
|
188
|
+
text = document.text_before_cursor.lstrip()
|
|
189
|
+
if text.startswith("/"):
|
|
190
|
+
typed = text.lower()
|
|
191
|
+
for cmd, desc in TUI_COMMANDS.items():
|
|
192
|
+
if cmd.startswith(typed):
|
|
193
|
+
yield Completion(cmd, start_position=-len(text), display=cmd, display_meta=desc)
|
|
194
|
+
|
|
195
|
+
cmd_completer = TuiCompleter()
|
|
108
196
|
history = InMemoryHistory()
|
|
109
197
|
style = PtStyle.from_dict({
|
|
110
198
|
"prompt": "ansicyan bold",
|
|
@@ -355,16 +443,222 @@ def handle_command(text):
|
|
|
355
443
|
console.print(Text(" Usage: /agent coder build a flask API", style="dim"))
|
|
356
444
|
console.print()
|
|
357
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
|
+
|
|
358
644
|
elif cmd == "/help" or cmd == "/h":
|
|
359
|
-
|
|
360
|
-
"
|
|
361
|
-
"
|
|
362
|
-
"
|
|
363
|
-
"
|
|
364
|
-
"
|
|
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"],
|
|
365
655
|
}
|
|
366
|
-
for
|
|
367
|
-
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}[/]"))
|
|
368
662
|
console.print()
|
|
369
663
|
|
|
370
664
|
else:
|
|
@@ -408,6 +702,8 @@ def main():
|
|
|
408
702
|
[("class:prompt", " ❯ ")],
|
|
409
703
|
style=style,
|
|
410
704
|
history=history,
|
|
705
|
+
completer=cmd_completer,
|
|
706
|
+
complete_while_typing=True,
|
|
411
707
|
bottom_toolbar=toolbar,
|
|
412
708
|
).strip()
|
|
413
709
|
except (KeyboardInterrupt, EOFError):
|