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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/tui.py +304 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codegpt-ai",
3
- "version": "2.26.0",
3
+ "version": "2.28.0",
4
4
  "description": "Local AI Assistant Hub — 123 commands, 26 tools, 8 agents, multi-AI, security. No cloud needed.",
5
5
  "author": "ArukuX",
6
6
  "license": "MIT",
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
- cmds = {
360
- "/new": "New chat", "/model": "Switch model", "/persona": "Switch persona",
361
- "/think": "Toggle reasoning", "/tokens": "Token count", "/clear": "Clear screen",
362
- "/sidebar": "Toggle sidebar", "/history": "Show history", "/connect": "Remote Ollama",
363
- "/server": "Server info", "/weather": "Get weather", "/agent": "Run agent",
364
- "/help": "This list", "/quit": "Exit",
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 c, d in cmds.items():
367
- console.print(Text.from_markup(f" [bright_blue]{c:<12}[/] [dim]{d}[/]"))
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):