codegpt-ai 2.8.0 → 2.10.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 +83 -96
  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,111 +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
- # Fit to terminal width — no clipping
1246
- w = min(console.width - 2, 56)
1247
-
1248
- # Detailed spider models for each direction
1249
- spiders = {
1250
- "top": [
1251
- f"[{D}] ╱╲[/]",
1252
- f"[{D}] ╱╲(o.o)╱╲[/]",
1253
- f"[{D}] ╱╱ ╲╱╱ ╲╲[/]",
1254
- f"[{D}] ||[/]",
1255
- ],
1256
- "right": [
1257
- f"[{D}] ╱╲[/]",
1258
- f"[{D}] (o.o)╲~~~[/]",
1259
- f"[{D}] ╲╱[/]",
1260
- ],
1261
- "bottom": [
1262
- f"[{D}] ||[/]",
1263
- f"[{D}] ╲╲ ╱╲╲ ╱╱[/]",
1264
- f"[{D}] ╲╱(o.o)╲╱[/]",
1265
- f"[{D}] ╲╱[/]",
1266
- ],
1267
- "left": [
1268
- f"[{D}] ╱╲[/]",
1269
- f"[{D}]~~~╱(o.o)[/]",
1270
- f"[{D}] ╲╱[/]",
1271
- ],
1272
- }
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()
1273
1284
 
1274
- def build_banner(pos_name):
1275
- """Build banner with detailed spider at given position."""
1276
- # Adaptive width border
1277
- inner = w - 4 # inside the ║ ║
1278
- pad = inner - 36 # 36 = content width
1279
- lpad = pad // 2
1280
- rpad = pad - lpad
1281
-
1282
- top_b = f"[{R}] ╔{'═' * inner}╗[/]"
1283
- bot_b = f"[{R}] ╚{'═' * inner}╝[/]"
1284
- empty = f"[{R}] ║[/]{' ' * inner}[{R}]║[/]"
1285
- title = f"[{R}] ║[/]{' ' * lpad}[{R}]C[/][{B}]o[/][{R}]d[/][{B}]e[/] [{R}]G[/][{B}]P[/][{R}]T[/] [{D}]v2.0[/]{' ' * (rpad + 16)}[{R}]║[/]"
1286
- sub = f"[{R}] ║[/]{' ' * lpad}[{D}]local ai · powered by ollama[/]{' ' * (rpad + 7)}[{R}]║[/]"
1287
- stats = f"[{R}] ║[/]{' ' * lpad}[{B}]123[/] [{D}]cmds ·[/] [{B}]26[/] [{D}]tools ·[/] [{B}]8[/] [{D}]agents[/]{' ' * (rpad + 8)}[{R}]║[/]"
1288
-
1289
- lines = []
1290
-
1291
- if pos_name == "top":
1292
- for sl in spiders["top"]:
1293
- lines.append(sl)
1294
- lines.extend([top_b, empty, title, sub, stats, empty, bot_b])
1295
- elif pos_name == "right":
1296
- lines.extend([top_b, empty, title])
1297
- # Spider on right side
1298
- for i, sl in enumerate(spiders["right"]):
1299
- if i == 0:
1300
- lines.append(f"{sub} {sl}")
1301
- elif i == 1:
1302
- lines.append(f"{stats} {sl}")
1303
- else:
1304
- lines.append(f"{empty} {sl}")
1305
- lines.append(bot_b)
1306
- elif pos_name == "bottom":
1307
- lines.extend([top_b, empty, title, sub, stats, empty, bot_b])
1308
- for sl in spiders["bottom"]:
1309
- lines.append(sl)
1310
- else: # left
1311
- lines.append(top_b)
1312
- for i, sl in enumerate(spiders["left"]):
1313
- if i == 1:
1314
- lines.append(f"{sl} [{R}]║[/]{' ' * lpad}[{R}]C[/][{B}]o[/][{R}]d[/][{B}]e[/] [{R}]G[/][{B}]P[/][{R}]T[/] [{D}]v2.0[/]{' ' * (rpad + 16)}[{R}]║[/]")
1315
- else:
1316
- lines.append(f"{' ' * 5} {empty}")
1317
- lines.extend([sub, stats, empty, bot_b])
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
1318
1297
 
1319
- return "\n".join(lines)
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()
1320
1305
 
1321
- # Animate spider crawling — 1 full lap, ~2 seconds
1322
- positions = ["top", "right", "bottom", "left"]
1323
- try:
1324
- with Live(
1325
- Text.from_markup(build_banner("top")),
1326
- console=console, refresh_per_second=4, transient=True,
1327
- ) as live:
1328
- for lap in range(2):
1329
- for pos in positions:
1330
- live.update(Text.from_markup(build_banner(pos)))
1331
- time.sleep(0.4)
1332
-
1333
- # Final position
1334
- import random
1335
- final = random.choice(positions)
1336
- console.print(Text.from_markup(build_banner(final)))
1337
- except Exception:
1338
- console.print(Text.from_markup(build_banner("top")))
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
+ ))
1339
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[/]"
1340
1316
  console.print(Text.from_markup(
1341
- f" [dim]model[/] [bright_blue]{model}[/]\n"
1342
- f" [dim]server[/] [green]{server}[/]\n"
1343
- f" [dim]user[/] {name}\n"
1344
- 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[/]"
1345
1322
  ))
1346
1323
  console.print(Rule(style="dim", characters="─"))
1347
1324
  console.print()
@@ -5064,6 +5041,16 @@ def main():
5064
5041
  if not user_input:
5065
5042
  continue
5066
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
+
5067
5054
  last_activity[0] = time.time()
5068
5055
 
5069
5056
  # Suggestion number shortcut
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codegpt-ai",
3
- "version": "2.8.0",
3
+ "version": "2.10.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
  }