tokenjar 1.0.1__py3-none-any.whl

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.
tokenjar/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """TokenJar: MCP server that saves 70-95% tokens for AI coding assistants."""
2
+
3
+ __version__ = "1.0.1"
tokenjar/__main__.py ADDED
@@ -0,0 +1,629 @@
1
+ """TokenJar CLI & MCP Entry Point.
2
+
3
+ Supports running both as an MCP server for AI coding assistants
4
+ and as a standalone CLI tool for developers (run, stats, hook, unhook).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import sys
11
+
12
+ # Configure stdout and stderr to handle UTF-8 cleanly on Windows/legacy terminals
13
+ if hasattr(sys.stdout, "reconfigure"):
14
+ try:
15
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
16
+ except Exception:
17
+ pass
18
+ if hasattr(sys.stderr, "reconfigure"):
19
+ try:
20
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
21
+ except Exception:
22
+ pass
23
+
24
+
25
+ def main() -> None:
26
+ """Main CLI entry point for TokenJar."""
27
+ parser = argparse.ArgumentParser(
28
+ prog="tokenjar",
29
+ description="TokenJar: Zero-cost token optimization engine for AI coding assistants and developers.",
30
+ )
31
+ parser.add_argument(
32
+ "-v",
33
+ "--version",
34
+ action="version",
35
+ version="tokenjar 1.0.1",
36
+ help="Show program's version number and exit",
37
+ )
38
+ subparsers = parser.add_subparsers(dest="subcommand", metavar="<command>", help="Available subcommands")
39
+
40
+ # Subcommand: version
41
+ subparsers.add_parser("version", help="Show program's version number and exit")
42
+
43
+ # Subcommand: server (default if no args)
44
+ subparsers.add_parser("server", help="Start the MCP server (stdio transport)")
45
+
46
+ # Subcommand: stats
47
+ subparsers.add_parser("stats", help="Display cumulative token and financial savings dashboard")
48
+
49
+ # Subcommand: status
50
+ status_parser = subparsers.add_parser(
51
+ "status",
52
+ help="Check comprehensive live operational status of TokenJar across all AI CLIs and project rules",
53
+ )
54
+ status_parser.add_argument(
55
+ "--path",
56
+ default=".",
57
+ help="Target project directory to check rules for (default: current directory)",
58
+ )
59
+
60
+ # Subcommand: reset-stats
61
+ subparsers.add_parser("reset-stats", help="Reset cumulative telemetry metrics")
62
+
63
+ # Subcommand: run
64
+ run_parser = subparsers.add_parser("run", help="Execute a shell command with intelligent output filtering")
65
+ run_parser.add_argument("command", nargs=argparse.REMAINDER, help="The command to execute (e.g. pytest, npm test)")
66
+
67
+ # Subcommand: hook
68
+ hook_parser = subparsers.add_parser("hook", help="Install non-intrusive transparent shell hooks")
69
+ hook_parser.add_argument(
70
+ "--shell",
71
+ choices=["auto", "powershell", "bash"],
72
+ default="auto",
73
+ help="Target shell environment (default: auto)",
74
+ )
75
+
76
+ # Subcommand: unhook
77
+ subparsers.add_parser("unhook", help="Safely remove all installed shell hooks")
78
+
79
+ # Subcommand: on / enable
80
+ on_parser = subparsers.add_parser(
81
+ "on", aliases=["enable"], help="Activate TokenJar for current project (or use --global for all IDEs)"
82
+ )
83
+ on_parser.add_argument(
84
+ "-g",
85
+ "--global",
86
+ dest="global_scope",
87
+ action="store_true",
88
+ help="Configure MCP server globally in all detected IDEs without modifying project files",
89
+ )
90
+
91
+ # Subcommand: off / disable
92
+ off_parser = subparsers.add_parser(
93
+ "off",
94
+ aliases=["disable"],
95
+ help="Deactivate TokenJar for current project (or use --global to uninstall from IDEs)",
96
+ )
97
+ off_parser.add_argument(
98
+ "-g",
99
+ "--global",
100
+ dest="global_scope",
101
+ action="store_true",
102
+ help="Uninstall TokenJar MCP configuration globally from all IDEs",
103
+ )
104
+
105
+ # Subcommand: install-mcp
106
+ install_mcp_parser = subparsers.add_parser(
107
+ "install-mcp",
108
+ help="Configure TokenJar MCP server in Claude Desktop, Cursor, Windsurf, VS Code with safe backup",
109
+ )
110
+ install_mcp_parser.add_argument(
111
+ "--all",
112
+ action="store_true",
113
+ help="Configure for all supported IDEs even if not currently detected on system",
114
+ )
115
+
116
+ # Subcommand: uninstall-mcp
117
+ subparsers.add_parser(
118
+ "uninstall-mcp",
119
+ help="Safely remove TokenJar MCP configuration and restore exact original state from backup",
120
+ )
121
+
122
+ # Subcommand: ui
123
+ ui_parser = subparsers.add_parser(
124
+ "ui",
125
+ help="Launch the lightweight On-Demand Settings & Dashboard UI (Zero Background RAM)",
126
+ )
127
+ ui_parser.add_argument(
128
+ "--port",
129
+ type=int,
130
+ default=4141,
131
+ help="Port to bind the local dashboard server (default: 4141)",
132
+ )
133
+ ui_parser.add_argument(
134
+ "--no-open",
135
+ action="store_true",
136
+ help="Do not automatically open the browser or native app window",
137
+ )
138
+
139
+ # Subcommand: setup-commands
140
+ subparsers.add_parser(
141
+ "setup-commands",
142
+ help="Install /tokenjar slash command definitions across AGY CLI and Claude Code",
143
+ )
144
+
145
+ # Subcommand: output (toggle compact output on/off/status)
146
+ output_parser = subparsers.add_parser(
147
+ "output",
148
+ help="Manage AI output mode: 'tokenjar output on' or 'tokenjar output off'",
149
+ )
150
+ output_parser.add_argument(
151
+ "state",
152
+ nargs="?",
153
+ choices=["on", "off", "status"],
154
+ default="status",
155
+ help="Output mode action: 'on' (compact surgical diffs), 'off' (default output), or 'status'",
156
+ )
157
+ output_parser.add_argument(
158
+ "--path",
159
+ default=".",
160
+ help="Target project directory (default: current directory)",
161
+ )
162
+
163
+ # Subcommand: init / init-rules
164
+ rules_parser = subparsers.add_parser(
165
+ "init",
166
+ aliases=["init-rules"],
167
+ help="Install agent steering rules into AGENTS.md, .cursorrules, .windsurfrules, and CLAUDE.md",
168
+ )
169
+ rules_parser.add_argument(
170
+ "--path",
171
+ default=".",
172
+ help="Target project directory (default: current directory)",
173
+ )
174
+ rules_parser.add_argument(
175
+ "--compact",
176
+ dest="compact_output",
177
+ action="store_true",
178
+ default=None,
179
+ help="Enforce compact surgical output rules",
180
+ )
181
+ rules_parser.add_argument(
182
+ "--no-compact",
183
+ dest="compact_output",
184
+ action="store_false",
185
+ help="Disable compact output restrictions in agent rules",
186
+ )
187
+ rules_parser.add_argument(
188
+ "--all",
189
+ action="store_true",
190
+ help="Generate rule files for all AI coding assistants (default: auto-detect installed assistants)",
191
+ )
192
+ rules_parser.add_argument(
193
+ "--clean",
194
+ action="store_true",
195
+ help="Clean steering rules from target project directory instead of installing",
196
+ )
197
+
198
+ # Subcommand: cache-prune / cache-clear / cache-reset
199
+ prune_parser = subparsers.add_parser(
200
+ "cache-prune",
201
+ aliases=["cache-clear", "cache-reset"],
202
+ help="Prune expired entries or completely reset L2 SQLite cache",
203
+ )
204
+ prune_parser.add_argument(
205
+ "--all",
206
+ action="store_true",
207
+ help="Completely clear all cached files and symbols",
208
+ )
209
+ prune_parser.add_argument(
210
+ "--max-entries",
211
+ type=int,
212
+ default=5000,
213
+ help="Maximum cache entries to retain (default: 5000)",
214
+ )
215
+ prune_parser.add_argument(
216
+ "--ttl-days",
217
+ type=int,
218
+ default=30,
219
+ help="Evict entries older than N days (default: 30)",
220
+ )
221
+
222
+ # Subcommand: update / upgrade
223
+ update_parser = subparsers.add_parser(
224
+ "update",
225
+ aliases=["upgrade"],
226
+ help="Check for updates and automatically upgrade TokenJar to the latest release",
227
+ )
228
+ update_parser.add_argument(
229
+ "--force",
230
+ action="store_true",
231
+ help="Force reinstallation even if already on the latest version",
232
+ )
233
+
234
+ # Subcommand: uninstall / purge
235
+ uninstall_parser = subparsers.add_parser(
236
+ "uninstall",
237
+ aliases=["purge", "self-destruct"],
238
+ help="Completely uninstall TokenJar: revert IDE configs, remove project rules, hooks, cache, and PATH",
239
+ )
240
+ uninstall_parser.add_argument(
241
+ "-y",
242
+ "--yes",
243
+ action="store_true",
244
+ help="Skip confirmation prompt and immediately purge all TokenJar traces",
245
+ )
246
+
247
+ # If called with no arguments:
248
+ # If piped by AI assistant (Cursor / Claude Desktop / Windsurf) -> run Stdio MCP Server!
249
+ # If user runs interactively in terminal -> open Web Dashboard UI in browser (matching Rust binary)!
250
+ if len(sys.argv) == 1:
251
+ if sys.stdin.isatty():
252
+ print("๐Ÿš€ Launching TokenJar Web Dashboard in your browser...")
253
+ from tokenjar.ui.server import start_ui_server
254
+
255
+ start_ui_server(port=4141, open_browser=True)
256
+ return
257
+ else:
258
+ from tokenjar.server import mcp
259
+
260
+ mcp.run(transport="stdio")
261
+ return
262
+
263
+ args = parser.parse_args()
264
+
265
+ if args.subcommand == "version":
266
+ print("tokenjar 1.0.1")
267
+ return
268
+
269
+ if args.subcommand in (None, "server"):
270
+ from tokenjar.server import mcp
271
+
272
+ mcp.run(transport="stdio")
273
+
274
+ elif args.subcommand == "stats":
275
+ from tokenjar.telemetry.stats import tracker
276
+
277
+ print(tracker.render_dashboard())
278
+
279
+ elif args.subcommand == "status":
280
+ import json
281
+ from pathlib import Path
282
+
283
+ from tokenjar.cache.persistent_cache import PersistentCache
284
+ from tokenjar.hooks.manager import HookManager
285
+ from tokenjar.rules.manager import RULES_MARKER_START, RulesManager
286
+
287
+ project_path = Path(args.path).resolve()
288
+ configs = HookManager.get_supported_cli_configs()
289
+
290
+ print("=" * 60)
291
+ print("๐Ÿ”‹ TOKENJAR SYSTEM STATUS REPORT")
292
+ print("=" * 60)
293
+
294
+ # 1. Check CLI MCP Integrations
295
+ active_clis = []
296
+ inactive_clis = []
297
+ for name, cfg_path in configs.items():
298
+ is_inst = HookManager.is_cli_installed(name)
299
+ is_active = False
300
+ if cfg_path.exists():
301
+ try:
302
+ with open(cfg_path, "r", encoding="utf-8") as f:
303
+ data = json.load(f)
304
+ if "tokenjar" in data.get("mcpServers", {}):
305
+ is_active = True
306
+ except Exception:
307
+ pass
308
+ if is_active:
309
+ active_clis.append(f"๐ŸŸข {name} (Active in {cfg_path.name})")
310
+ elif is_inst:
311
+ inactive_clis.append(f"๐Ÿ”ด {name} (Installed, but TokenJar disabled)")
312
+ else:
313
+ inactive_clis.append(f"โšช {name} (Not installed)")
314
+
315
+ overall_active = len(active_clis) > 0
316
+ overall_badge = "๐ŸŸข ACTIVE (Operational)" if overall_active else "๐Ÿ”ด INACTIVE (Turn on with 'tokenjar on')"
317
+ print(f"Overall Engine Status : {overall_badge}\n")
318
+
319
+ print("AI Assistant Integrations:")
320
+ for line in active_clis + inactive_clis:
321
+ print(f" โ€ข {line}")
322
+
323
+ # 2. Check Output Optimization Status
324
+ output_active = RulesManager.get_output_mode(project_path)
325
+ out_badge = (
326
+ "๐ŸŸข ON (Compact surgical diffs & zero-truncation)" if output_active else "โšช OFF (Default full output)"
327
+ )
328
+ print(f"\nOutput Optimization : {out_badge}")
329
+
330
+ # 3. Check Project Steering Rules
331
+ rule_files_found = []
332
+ for r_name in RulesManager.SUPPORTED_RULE_FILES:
333
+ r_path = project_path / r_name
334
+ if r_path.exists():
335
+ try:
336
+ content = r_path.read_text(encoding="utf-8")
337
+ if RULES_MARKER_START in content:
338
+ rule_files_found.append(r_name)
339
+ except Exception:
340
+ pass
341
+ if rule_files_found:
342
+ print(f"Project Steering Rules: ๐ŸŸข INSTALLED ({', '.join(rule_files_found)})")
343
+ else:
344
+ print("Project Steering Rules: ๐Ÿ”ด NOT INSTALLED (Run 'tokenjar init-rules')")
345
+
346
+ # 4. Check L2 Persistent Cache
347
+ try:
348
+ cache = PersistentCache()
349
+ entries = cache.count_entries()
350
+ print(f"L2 Persistent Cache : ๐ŸŸข ONLINE ({entries} cached entries in ~/.tokenjar/cache.db)")
351
+ except Exception as e:
352
+ print(f"L2 Persistent Cache : โš ๏ธ Error accessing DB: {e}")
353
+
354
+ print("=" * 60)
355
+ print("Useful Commands:")
356
+ print(" tokenjar on -> Enable TokenJar for THIS project")
357
+ print(" tokenjar off -> Disable TokenJar for THIS project")
358
+ print(" tokenjar on --global -> Enable MCP across all IDEs globally")
359
+ print(" tokenjar off --global -> Disable MCP across all IDEs globally")
360
+ print(" tokenjar ui -> Open Web Dashboard in browser")
361
+ print(" tokenjar stats -> View live token and financial savings")
362
+ print(" tokenjar status -> Check operational status")
363
+ print("=" * 60)
364
+ if not overall_active:
365
+ sys.exit(1)
366
+
367
+ elif args.subcommand == "reset-stats":
368
+ from tokenjar.telemetry.stats import tracker
369
+
370
+ tracker.reset()
371
+ print("Telemetry metrics have been successfully reset.")
372
+
373
+ elif args.subcommand == "run":
374
+ if not args.command:
375
+ print("Error: No command provided to run. Example: tokenjar run pytest")
376
+ sys.exit(1)
377
+ from tokenjar.hooks.manager import execute_filtered_command
378
+
379
+ exit_code = execute_filtered_command(args.command)
380
+ sys.exit(exit_code)
381
+
382
+ elif args.subcommand == "hook":
383
+ from tokenjar.hooks.manager import HookManager
384
+
385
+ success, msg = HookManager.install_shell_hook(target_shell=args.shell)
386
+ print(msg)
387
+ if not success:
388
+ sys.exit(1)
389
+
390
+ elif args.subcommand == "unhook":
391
+ from tokenjar.hooks.manager import HookManager
392
+
393
+ success, msg = HookManager.uninstall_shell_hook()
394
+ print(msg)
395
+ if not success:
396
+ sys.exit(1)
397
+
398
+ elif args.subcommand in ("on", "enable"):
399
+ from tokenjar.hooks.manager import HookManager
400
+ from tokenjar.rules.manager import RulesManager
401
+
402
+ if getattr(args, "global_scope", False):
403
+ results = HookManager.enable_all()
404
+ for name, ok, msg in results:
405
+ if "skipped" in msg.lower():
406
+ status = "โšช"
407
+ else:
408
+ status = "๐ŸŸข" if ok else "โŒ"
409
+ print(f"{status} {name}: {msg}")
410
+ print("\nโœจ TokenJar is now GLOBALLY ACTIVE across detected IDEs!")
411
+ print("๐Ÿ’ก Projects remain clean by default. To enable for a specific project, run:")
412
+ print(" tokenjar on")
413
+ else:
414
+ # Local on
415
+ HookManager.enable_all()
416
+ rule_results = RulesManager.install_rules(".")
417
+ for name, ok, msg in rule_results:
418
+ print(f"๐ŸŸข Rules: {msg}")
419
+ print("\nโœจ TokenJar is now ACTIVE for this project!")
420
+ print("๐Ÿ’ก Other projects remain unaffected unless explicitly enabled.")
421
+
422
+ elif args.subcommand in ("off", "disable"):
423
+ from tokenjar.hooks.manager import HookManager
424
+ from tokenjar.rules.manager import RulesManager
425
+
426
+ if getattr(args, "global_scope", False):
427
+ results = HookManager.disable_all()
428
+ for name, ok, msg in results:
429
+ if "skipped" in msg.lower():
430
+ status = "โšช"
431
+ else:
432
+ status = "๐Ÿ”ด" if ok else "โŒ"
433
+ print(f"{status} {name}: {msg}")
434
+ print("\nโšช TokenJar has been deactivated globally across all IDEs.")
435
+ else:
436
+ rule_results = RulesManager.remove_rules(".")
437
+ for name, ok, msg in rule_results:
438
+ print(f"๐Ÿ”ด Rules: {msg}")
439
+ print("\nโšช TokenJar has been deactivated for THIS project.")
440
+ print("๐Ÿ’ก Global MCP and other projects remain active and unaffected.")
441
+ print(" (To remove globally from all IDEs, run: tokenjar off --global)")
442
+
443
+ elif args.subcommand in ("install-mcp", "enable-mcp"):
444
+ from tokenjar.hooks.manager import HookManager
445
+
446
+ results = HookManager.enable_all(only_installed=not getattr(args, "all", False))
447
+ for name, ok, msg in results:
448
+ if "skipped" in msg.lower():
449
+ status = "โšช"
450
+ else:
451
+ status = "๐ŸŸข" if ok else "โŒ"
452
+ print(f"{status} {name}: {msg}")
453
+
454
+ elif args.subcommand in ("uninstall-mcp", "disable-mcp"):
455
+ from tokenjar.hooks.manager import HookManager
456
+
457
+ results = HookManager.disable_all()
458
+ for name, ok, msg in results:
459
+ if "skipped" in msg.lower():
460
+ status = "โšช"
461
+ else:
462
+ status = "๐Ÿ”ด" if ok else "โŒ"
463
+ print(f"{status} {name}: {msg}")
464
+
465
+ elif args.subcommand in ("setup-commands", "install-commands"):
466
+ from tokenjar.hooks.manager import HookManager
467
+
468
+ results = HookManager.install_all_slash_commands(only_installed=True)
469
+ all_ok = True
470
+ for name, ok, msg in results:
471
+ if "skipped" in msg.lower():
472
+ status = "โšช"
473
+ else:
474
+ status = "โœ…" if ok else "โŒ"
475
+ if not ok:
476
+ all_ok = False
477
+ print(f"{status} {name}: {msg}")
478
+ if not all_ok:
479
+ sys.exit(1)
480
+
481
+ elif args.subcommand in ("init", "init-rules"):
482
+ from tokenjar.rules.manager import RulesManager
483
+
484
+ if getattr(args, "clean", False):
485
+ results = RulesManager.remove_rules(args.path)
486
+ for name, ok, msg in results:
487
+ print(f"๐Ÿ”ด Rules: {msg}")
488
+ else:
489
+ results = RulesManager.install_rules(
490
+ args.path,
491
+ compact_output=args.compact_output,
492
+ only_installed=not args.all,
493
+ )
494
+ all_ok = True
495
+ for name, ok, msg in results:
496
+ if "skipped" in msg.lower():
497
+ status = "โšช"
498
+ else:
499
+ status = "โœ…" if ok else "โŒ"
500
+ if not ok:
501
+ all_ok = False
502
+ print(f"{status} {name}: {msg}")
503
+ if not all_ok:
504
+ sys.exit(1)
505
+
506
+ elif args.subcommand == "output":
507
+ from tokenjar.rules.manager import RulesManager
508
+
509
+ if args.state == "on":
510
+ ok, msg, files = RulesManager.set_output_mode(args.path, enabled=True)
511
+ if ok:
512
+ print("๐ŸŸข Output Optimization: ON (Compact Mode Active)")
513
+ print(" โ€ข Enforces surgical diffs and targeted block replacements.")
514
+ print(" โ€ข ZERO TRUNCATION MANDATE active (no lazy comments).")
515
+ print(f" โ€ข Updated files: {', '.join(files)}")
516
+ else:
517
+ print(f"โŒ Error: {msg}")
518
+ sys.exit(1)
519
+ elif args.state == "off":
520
+ ok, msg, files = RulesManager.set_output_mode(args.path, enabled=False)
521
+ if ok:
522
+ print("โšช Output Optimization: OFF (Default Output Restored)")
523
+ print(" โ€ข AI assistant will use standard, unrestricted output.")
524
+ print(" โ€ข Output format returned to default.")
525
+ print(f" โ€ข Updated files: {', '.join(files)}")
526
+ else:
527
+ print(f"โŒ Error: {msg}")
528
+ sys.exit(1)
529
+ else:
530
+ active = RulesManager.get_output_mode(args.path)
531
+ state_str = "๐ŸŸข ON (Compact Mode Active)" if active else "โšช OFF (Default Output)"
532
+ print(f"Output Optimization Status: {state_str}")
533
+ print("\nUsage:")
534
+ print(" tokenjar output on -> Activate compact surgical diffs & zero-truncation")
535
+ print(" tokenjar output off -> Revert to default normal/verbose output")
536
+
537
+ elif args.subcommand in ("cache-prune", "cache-clear", "cache-reset"):
538
+ from tokenjar.cache.persistent_cache import PersistentCache
539
+
540
+ p = PersistentCache()
541
+ before = p.count_entries()
542
+ if getattr(args, "all", False) or args.subcommand in ("cache-clear", "cache-reset"):
543
+ p.clear()
544
+ after = p.count_entries()
545
+ print(f"๐Ÿงน L2 Cache Reset: {before} entries cleared. ({after} remaining in SQLite)")
546
+ else:
547
+ deleted = p.prune(max_entries=args.max_entries, max_age_days=args.ttl_days)
548
+ after = p.count_entries()
549
+ print(f"L2 Cache Pruned: {deleted} entries removed. ({before} -> {after} entries remaining)")
550
+
551
+ elif args.subcommand == "ui":
552
+ from tokenjar.hooks.manager import HookManager
553
+
554
+ HookManager.ensure_in_user_path()
555
+ from tokenjar.ui.server import start_ui_server
556
+
557
+ start_ui_server(port=args.port, open_browser=not args.no_open)
558
+
559
+ elif args.subcommand in ("uninstall", "purge", "self-destruct"):
560
+ if not getattr(args, "yes", False):
561
+ try:
562
+ confirm = input("โš ๏ธ Are you sure you want to completely uninstall TokenJar from this computer? (y/N): ")
563
+ if confirm.strip().lower() not in ("y", "yes"):
564
+ print("Aborted.")
565
+ sys.exit(0)
566
+ except (KeyboardInterrupt, EOFError):
567
+ print("\nAborted.")
568
+ sys.exit(0)
569
+
570
+ from tokenjar.hooks.manager import HookManager
571
+
572
+ HookManager.full_uninstall()
573
+
574
+ elif args.subcommand in ("update", "upgrade"):
575
+ import json
576
+ import subprocess
577
+ import urllib.request
578
+
579
+ from tokenjar import __version__
580
+
581
+ print("=" * 60)
582
+ print("๐Ÿ”„ TOKENJAR AUTOMATIC UPDATE MANAGER")
583
+ print("=" * 60)
584
+ print(f"Current Engine Version : v{__version__}")
585
+ print("Checking for latest release on PyPI...")
586
+
587
+ latest_version = None
588
+ try:
589
+ req = urllib.request.Request(
590
+ "https://pypi.org/pypi/tokenjar-engine/json",
591
+ headers={"User-Agent": f"tokenjar/{__version__}"},
592
+ )
593
+ with urllib.request.urlopen(req, timeout=5) as response:
594
+ data = json.loads(response.read().decode("utf-8"))
595
+ latest_version = data.get("info", {}).get("version")
596
+ except Exception as e:
597
+ print(f"Note: Could not reach PyPI index directly ({e}). Proceeding to pip upgrade check...")
598
+
599
+ if latest_version:
600
+ print(f"Latest PyPI Release : v{latest_version}")
601
+
602
+ if latest_version and latest_version == __version__ and not getattr(args, "force", False):
603
+ print("\nโœจ TokenJar is already on the latest version!")
604
+ print(f" (No action needed. Current: v{__version__})")
605
+ return
606
+
607
+ if latest_version and latest_version != __version__:
608
+ print(f"\n๐Ÿš€ New version detected: v{latest_version} (installed: v{__version__})")
609
+ print("\n๐Ÿ“ฆ Upgrading tokenjar-engine via pip...")
610
+
611
+ try:
612
+ cmd = [sys.executable, "-m", "pip", "install", "--upgrade", "tokenjar-engine"]
613
+ if getattr(args, "force", False):
614
+ cmd.append("--force-reinstall")
615
+ res = subprocess.run(cmd, capture_output=True, text=True)
616
+ if res.returncode == 0:
617
+ target_v = latest_version or "latest"
618
+ print(f"\n๐ŸŽ‰ TokenJar has been successfully updated to v{target_v}!")
619
+ print("๐Ÿ’ก Tip: Restart any open AI coding sessions or IDE windows to load updated middleware.")
620
+ else:
621
+ print(f"\nโŒ Pip update command returned error:\n{res.stderr or res.stdout}")
622
+ sys.exit(res.returncode)
623
+ except Exception as e:
624
+ print(f"\nโŒ Failed to execute pip updater: {e}")
625
+ sys.exit(1)
626
+
627
+
628
+ if __name__ == "__main__":
629
+ main()
@@ -0,0 +1 @@
1
+ """TokenJar cache package โ€” Session-level file caching."""