codegpt-ai 2.7.0 → 2.9.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/chat.py +86 -60
  2. package/package.json +2 -2
package/chat.py CHANGED
@@ -723,6 +723,36 @@ code_exec_count = 0
723
723
  AUTO_LOCK_MINUTES = 10
724
724
  last_activity = [time.time()]
725
725
 
726
+ # Rate limiting — prevent rapid-fire commands
727
+ RATE_LIMIT_WINDOW = 2 # seconds
728
+ RATE_LIMIT_MAX = 5 # max commands in window
729
+ _cmd_timestamps = []
730
+
731
+
732
+ def check_rate_limit():
733
+ """Block rapid-fire command spam. Returns True if OK."""
734
+ now = time.time()
735
+ # Remove old timestamps
736
+ while _cmd_timestamps and now - _cmd_timestamps[0] > RATE_LIMIT_WINDOW:
737
+ _cmd_timestamps.pop(0)
738
+ _cmd_timestamps.append(now)
739
+ if len(_cmd_timestamps) > RATE_LIMIT_MAX:
740
+ return False
741
+ return True
742
+
743
+
744
+ def sanitize_input(text):
745
+ """Strip control characters and null bytes from user input."""
746
+ # Remove null bytes, control chars (except newline/tab)
747
+ cleaned = ""
748
+ for c in text:
749
+ if c == '\x00':
750
+ continue
751
+ if ord(c) < 32 and c not in ('\n', '\t', '\r'):
752
+ continue
753
+ cleaned += c
754
+ return cleaned.strip()
755
+
726
756
 
727
757
  def hash_pin(pin, salt=None):
728
758
  """Hash a PIN with a random salt. Returns 'salt:hash'."""
@@ -1237,72 +1267,58 @@ def print_header(model):
1237
1267
 
1238
1268
  console.print()
1239
1269
 
1240
- # Banner parts
1241
1270
  R = "bold red"
1242
1271
  B = "bold bright_blue"
1243
1272
  D = "dim"
1244
1273
 
1245
- def build_banner(pos):
1246
- """Build banner with spider at given position (0-7 around the border)."""
1247
- sp_faces = [" /╲(o.o)╱\\", " /╲(o.o)╱\\", "~~~(o.o)>", "~~~(o.o)>",
1248
- " \\╱(o.o)╲/", " \\╱(o.o)╲/", "<(o.o)~~~", "<(o.o)~~~"]
1249
- spider = sp_faces[pos % 8]
1250
-
1251
- top_b = f"[{R}] ╔════════════════════════════════════════════════════╗[/]"
1252
- bot_b = f"[{R}] ╚════════════════════════════════════════════════════╝[/]"
1253
- empty = f"[{R}] ║[/] [{R}]║[/]"
1254
- title = f"[{R}] ║[/] [{R}]C[/][{B}]o[/][{R}]d[/][{B}]e[/] [{R}]G[/][{B}]P[/][{R}]T[/] [{D}]v2.0[/] [{R}]║[/]"
1255
- sub = f"[{R}] ║[/] [{D}]local ai · powered by ollama[/] [{R}]║[/]"
1256
- stats = f"[{R}] ║[/] [{B}]123[/] [{D}]commands ·[/] [{B}]26[/] [{D}]tools ·[/] [{B}]8[/] [{D}]agents[/] [{R}]║[/]"
1257
-
1258
- lines = []
1259
- p = pos % 8
1260
-
1261
- if p in (0, 1): # top
1262
- lines.append(f"[{D}] {spider}[/]")
1263
- lines.append(f"[{D}] ||[/]")
1264
- lines.extend([top_b, empty, title, sub, stats, empty, bot_b])
1265
- elif p in (2, 3): # right
1266
- lines.append(top_b)
1267
- lines.append(empty)
1268
- lines.append(title)
1269
- lines.append(f"[{R}] ║[/] [{D}]local ai · powered by ollama[/] [{R}]║[/][{D}]~~{spider}[/]")
1270
- lines.extend([stats, empty, bot_b])
1271
- elif p in (4, 5): # bottom
1272
- lines.extend([top_b, empty, title, sub, stats, empty, bot_b])
1273
- lines.append(f"[{D}] ||[/]")
1274
- lines.append(f"[{D}] {spider}[/]")
1275
- else: # left (6, 7)
1276
- lines.append(top_b)
1277
- lines.append(empty)
1278
- lines.append(f"[{D}]{spider}~~[/][{R}]║[/] [{R}]C[/][{B}]o[/][{R}]d[/][{B}]e[/] [{R}]G[/][{B}]P[/][{R}]T[/] [{D}]v2.0[/] [{R}]║[/]")
1279
- lines.extend([sub, stats, empty, bot_b])
1280
-
1281
- return "\n".join(lines)
1282
-
1283
- # Animate spider crawling around the banner
1284
- try:
1285
- with Live(
1286
- Text.from_markup(build_banner(0)),
1287
- console=console, refresh_per_second=6, transient=True,
1288
- ) as live:
1289
- for frame in range(16): # 2 full laps
1290
- live.update(Text.from_markup(build_banner(frame)))
1291
- time.sleep(0.2)
1292
-
1293
- # Final position — static
1294
- import random
1295
- final = random.randint(0, 7)
1296
- console.print(Text.from_markup(build_banner(final)))
1297
- except Exception:
1298
- # Fallback if Live doesn't work
1299
- console.print(Text.from_markup(build_banner(0)))
1274
+ # Banner
1275
+ console.print(Text.from_markup(
1276
+ f"[{R}] ╔══════════════════════════════════════════════════╗[/]\n"
1277
+ f"[{R}] ║[/] [{R}]║[/]\n"
1278
+ f"[{R}] ║[/] [{R}]C[/][{B}]o[/][{R}]d[/][{B}]e[/] [{R}]G[/][{B}]P[/][{R}]T[/] [{D}]v2.0[/] [{R}]║[/]\n"
1279
+ f"[{R}] ║[/] [{D}]local ai · powered by ollama[/] [{R}]║[/]\n"
1280
+ f"[{R}] [/] [{R}]║[/]\n"
1281
+ f"[{R}] ╚══════════════════════════════════════════════════╝[/]"
1282
+ ))
1283
+ console.print()
1284
+
1285
+ # Info block model, server, tokens, security
1286
+ total_tok = profile.get("total_tokens", 0)
1287
+ total_msgs = profile.get("total_messages", 0)
1288
+ sessions = profile.get("total_sessions", 0)
1289
+ pin_on = has_pin()
1290
+ perms = len(PERMISSION_ALWAYS_ALLOW)
1291
+ audit_count = 0
1292
+ if AUDIT_FILE.exists():
1293
+ try:
1294
+ audit_count = len(AUDIT_FILE.read_text().strip().splitlines())
1295
+ except Exception:
1296
+ pass
1297
+
1298
+ console.print(Text.from_markup(
1299
+ f" [{D}]model[/] [{B}]{model}[/]\n"
1300
+ f" [{D}]server[/] [green]{server}[/]\n"
1301
+ f" [{D}]user[/] {name}\n"
1302
+ f" [{D}]memory[/] {mem_count} items"
1303
+ ))
1304
+ console.print()
1305
+
1306
+ # Token counter
1307
+ console.print(Text.from_markup(
1308
+ f" [{D}]tokens[/] [{B}]{total_tok:,}[/] [{D}]lifetime[/]\n"
1309
+ f" [{D}]messages[/] [{B}]{total_msgs:,}[/] [{D}]across {sessions} sessions[/]"
1310
+ ))
1300
1311
  console.print()
1312
+
1313
+ # Security status
1314
+ pin_status = f"[green]on[/]" if pin_on else f"[yellow]off[/] [{D}]— /pin-set to enable[/]"
1315
+ sandbox_status = f"[green]active[/]"
1301
1316
  console.print(Text.from_markup(
1302
- f" [dim]model[/] [bright_blue]{model}[/]\n"
1303
- f" [dim]server[/] [green]{server}[/]\n"
1304
- f" [dim]user[/] {name}\n"
1305
- f" [dim]memory[/] {mem_count} items"
1317
+ f" [{D}]security[/]\n"
1318
+ f" [{D}]pin lock[/] {pin_status}\n"
1319
+ f" [{D}]sandbox[/] {sandbox_status}\n"
1320
+ f" [{D}]permissions[/] [{B}]{perms}[/] [{D}]always-allowed[/]\n"
1321
+ f" [{D}]audit log[/] [{B}]{audit_count}[/] [{D}]events[/]"
1306
1322
  ))
1307
1323
  console.print(Rule(style="dim", characters="─"))
1308
1324
  console.print()
@@ -5025,6 +5041,16 @@ def main():
5025
5041
  if not user_input:
5026
5042
  continue
5027
5043
 
5044
+ # Sanitize input
5045
+ user_input = sanitize_input(user_input)
5046
+ if not user_input:
5047
+ continue
5048
+
5049
+ # Rate limit
5050
+ if user_input.startswith("/") and not check_rate_limit():
5051
+ print_err("Slow down — too many commands. Wait a moment.")
5052
+ continue
5053
+
5028
5054
  last_activity[0] = time.time()
5029
5055
 
5030
5056
  # Suggestion number shortcut
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codegpt-ai",
3
- "version": "2.7.0",
3
+ "version": "2.9.0",
4
4
  "description": "Local AI Assistant Hub — 80+ commands, 29 tools, 8 agents, training, security",
5
5
  "author": "ArukuX",
6
6
  "license": "MIT",
@@ -36,6 +36,6 @@
36
36
  "README.md"
37
37
  ],
38
38
  "dependencies": {
39
- "codegpt-ai": "^1.18.0"
39
+ "codegpt-ai": "^1.28.2"
40
40
  }
41
41
  }