tokensafe 1.1.0 → 1.2.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/README.md CHANGED
@@ -56,13 +56,34 @@ Traditional AI code interactions suffer from massive context bloat:
56
56
  3. **Stuck Loops & Bloated Diffs:** Terminal `git diff` and repetitive failed commands spiral context into overflow.
57
57
 
58
58
  ### TokenSafe Solves All Three Autonomously:
59
+ - **Graph-Guided AST Skeletonization:** Folds inactive function bodies into signatures, keeping target functions and 1-hop dependencies full while collapsing unrelated code (`-85% to -92%`).
60
+ - **God Nodes & Architectural Hub Detection:** Calculates Degree Centrality to identify structural project hubs and injects topology awareness into agent sessions without expensive file crawling.
59
61
  - **TOCP Prompt Distillation:** Intercepts prompts and distills intent into lean S-Expressions via **Google Gemini (2.0 Flash-Lite), OpenAI ChatGPT (gpt-4o-mini), or DeepSeek-Flash** (`-40% to -70%`). Automatically detects `GEMINI_API_KEY`, `OPENAI_API_KEY`, or `DEEPSEEK_API_KEY`.
60
- - **AST Skeletonization & Slicing:** Folds inactive function bodies into signatures and windows targeted symbols (`-80% to -92%`).
62
+ - **API Surface Map Generator:** Generates high-density `tokensafe-out/API_SURFACE.md` (~1.5k tokens) for instant agent codebase onboarding.
61
63
  - **Predictive Dependency Prefetcher:** Attaches imported symbol signatures to eliminate downstream exploratory `read_file` roundtrips.
64
+ - **Local Secret Redactor:** Sanitizes Stripe, AWS, JWT, and database credentials before network transmission.
62
65
  - **Output Guard & Loop Guard:** Blocks destructive full-file rewrites on files $\ge 40$ lines and halts command loops.
63
66
 
64
67
  ---
65
68
 
69
+ ## ⚡ TokenSafe vs. Graphify: Dynamic In-Flight Defense vs. Static Reports
70
+
71
+ How TokenSafe complements or supersedes static graph tools like **Graphify** (`graphify-labs/graphify`):
72
+
73
+ | Capability | Graphify | TokenSafe |
74
+ |---|---|---|
75
+ | **Primary Focus** | Static knowledge graph reports & MCP queries | **In-flight real-time token compression & defense** |
76
+ | **Indexing Cost** | Expensive LLM tokens / heavy graph extraction | **0 tokens, 100% deterministic local AST (< 30ms)** |
77
+ | **Execution Latency** | Offline / CLI-initiated build | **Sub-millisecond inline proxy & lifecycle hooks** |
78
+ | **Active Slicing** | Static file output (`GRAPH_REPORT.md`) | **Dynamic call-graph skeletonization (85–92% saved)** |
79
+ | **Multi-Project Workspace** | Requires manual directory targeting | **Auto-detects project roots per file on the fly** |
80
+ | **Agent Prompt Optimization** | None | **TOCP S-Expression prompt distillation (-40% to -70%)** |
81
+ | **Safety & Runaway Prevention** | None | **Output rewrite guard, loop detector, secret scrubbing** |
82
+
83
+ > **Complements & Replaces**: If you already use Graphify for repository mapping, TokenSafe works alongside it by compressing real-time agent tool reads on the wire. Or use TokenSafe standalone for a zero-configuration, zero-cost, sub-30ms architecture engine out of the box.
84
+
85
+ ---
86
+
66
87
  ## 🤖 Universal LLM Distillation Providers
67
88
 
68
89
  TokenSafe supports any key you already have:
@@ -110,6 +131,12 @@ Verified across 5 real-world production scenarios:
110
131
  ## 🛠️ CLI Quickstart
111
132
 
112
133
  ```bash
134
+ # Analyze repository God Nodes and dependency topology
135
+ tokensafe graph
136
+
137
+ # Generate compact tokensafe-out/API_SURFACE.md interface snapshot
138
+ tokensafe map
139
+
113
140
  # View live savings dashboard in your terminal
114
141
  tokensafe stats
115
142
 
@@ -110,8 +110,29 @@ def main():
110
110
  print(json.dumps(response))
111
111
  return
112
112
 
113
- # Step 3B: If a specific symbol/function is targeted in user prompt, slice to it
113
+ # Step 3B: If a specific symbol/function is targeted in user prompt, check graph-guided skeleton first
114
114
  overwrite_args, metrics = ToolSlicer.optimize_read_file(args, context_hint=last_user_input)
115
+ target_sym = metrics.get("target_symbol")
116
+ if target_sym and path and os.path.exists(path):
117
+ skel_path, orig_t, skel_t = Skeletonizer.get_skeleton_file(path, target_symbol=target_sym)
118
+ if skel_path and orig_t > skel_t:
119
+ saved = orig_t - skel_t
120
+ response["overwrite"] = {"Path": skel_path}
121
+ response["reason"] = f"Token Optimizer: Graph-guided AST view for '{target_sym}' (Saved ~{saved} tokens)"
122
+ tracker.record_event(
123
+ session_id=conversation_id,
124
+ event_type="GRAPH_SKELETON",
125
+ original_tokens=orig_t,
126
+ optimized_tokens=skel_t,
127
+ details={
128
+ "target_file": os.path.basename(path),
129
+ "symbol": target_sym,
130
+ "saved_tokens": saved
131
+ }
132
+ )
133
+ print(json.dumps(response))
134
+ return
135
+
115
136
  if overwrite_args and metrics.get("saved_tokens", 0) > 0:
116
137
  response["overwrite"] = overwrite_args
117
138
  response["reason"] = (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokensafe",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Autonomous Zero-Loss Token Economy & Context Optimizer for AI Coding Agents",
5
5
  "main": "bin/tokensafe.js",
6
6
  "bin": {
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agy-token-optimizer",
3
3
  "displayName": "TokenSafe",
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "description": "Zero-Loss Token Economy & Context Optimizer for AI Agents. Compresses prompts, slices AST/tool outputs, and prevents stuck loops.",
6
6
  "logo": "assets/logo.svg",
7
7
  "author": {
package/rules/AGENTS.md CHANGED
@@ -24,3 +24,8 @@ Execute the specified action directly based on these extracted parameters.
24
24
  2. **Surgical Code Edits**: Prefer `edit_file` with concise `Target` and `Replacement` over `write_file` on large existing files.
25
25
  3. **Diff-Centric Responses**: Never reprint entire unmodified files or long boilerplate blocks. Reference file lines instead (e.g., `wallet.ts#L42-L50`).
26
26
  4. **Dense Formatting**: Use compact bullet points and direct technical prose.
27
+
28
+ ### 3. Architecture & Code Navigation Directives (Graph-Guided)
29
+ 1. **Never Crawl with Raw Grep/Find**: Before executing exploratory searches or answering architecture questions, inspect `tokensafe:get_architecture_graph` or `tokensafe-out/API_SURFACE.md`. It provides project God Nodes and dependencies in < 250 tokens without file crawling.
30
+ 2. **Graph-Guided File Reading**: When inspecting code implementations, call `tokensafe:smart_read_file` with the targeted `symbol`. It preserves full executable bodies for the target function and its 1-hop callees while folding inert code into signatures (-85% to -92% tokens).
31
+ 3. **Bounded Reads**: Never call `read_file` without `StartLine`/`EndLine` on files > 100 lines unless inspecting a skeleton.
@@ -2,47 +2,85 @@
2
2
  name: token-optimizer
3
3
  description: >-
4
4
  Inspect token efficiency metrics, view cost savings dashboard, test prompt compression,
5
- and operate the Token-Optimized Communication Protocol (TOCP) in Antigravity.
5
+ navigate codebases via zero-token AST architecture graphs, and operate the Token-Optimized Communication Protocol (TOCP).
6
6
  ---
7
7
 
8
- # Token Optimizer Skill
8
+ # TokenSafe Optimization & Architecture Intelligence Skill
9
9
 
10
- This skill allows the agent to inspect token savings, query the metrics database,
11
- and report resource utilization efficiency.
10
+ This skill provides autonomous AI coding assistants with deterministic, zero-token AST architecture indexing, graph-guided code slicing, command sanitization, and context optimization metrics.
12
11
 
13
- ## Quick CLI Commands
12
+ ## When to Activate This Skill
13
+ 1. **Architecture Discovery**: Mapping new or unfamiliar codebases, finding God Nodes and key dependency hubs without LLM token burn.
14
+ 2. **Graph-Guided Code Inspection**: Reading functions or classes with their 1-hop dependencies while folding inert boilerplate (-85% to -92% tokens).
15
+ 3. **Telemetry & Economics**: Inspecting token savings, breakdown by optimization source, and ROI.
16
+ 4. **Command & Prompt Optimization**: Sanitizing terminal output and testing TOCP compression.
14
17
 
15
- Use the bundled `agy-tokens` CLI from this plugin directory:
18
+ ## Core MCP Tools (`tokensafe`)
16
19
 
17
- ```bash
18
- # Display visual terminal dashboard
19
- ./bin/agy-tokens stats
20
+ | MCP Tool | Purpose | Token Cost |
21
+ |---|---|---|
22
+ | `tokensafe:get_architecture_graph` | Analyzes AST topology, God Nodes, and hub centrality for `project_path`. | ~150-250 tokens |
23
+ | `tokensafe:get_api_surface` | Exports public signatures and types for the entire project. | ~300-600 tokens |
24
+ | `tokensafe:smart_read_file` | Reads `path` focusing on `symbol` + 1-hop dependencies; folds inert code into signatures. | -85% to -92% vs raw read |
25
+ | `tokensafe:sanitize_command` | Strips lockfiles, limits test/build output, and prevents context poisoning. | Saves 10k-50k tokens |
26
+ | `tokensafe:compress_prompt` | Compresses verbose user instructions into lean TOCP S-expressions. | -40% to -65% |
27
+ | `tokensafe:get_token_savings_metrics` | Returns total tokens saved, USD value, and breakdown by source. | ~100 tokens |
20
28
 
21
- # Test compression on a prompt and view savings
22
- ./bin/agy-tokens test "Слушай, перепиши функцию get_balance в wallet.ts, добавь redis cache"
29
+ ## Agent Graph-First Workflow
23
30
 
24
- # Export Markdown summary for artifacts or chat
25
- ./bin/agy-tokens markdown
31
+ ### 1. Zero-Token Architecture Survey
32
+ Before running recursive searches or reading multi-file structures, query the architecture graph:
33
+ ```json
34
+ {
35
+ "ServerName": "tokensafe",
36
+ "ToolName": "get_architecture_graph",
37
+ "Arguments": {
38
+ "project_path": "/absolute/path/to/project"
39
+ }
40
+ }
41
+ ```
42
+ * **Output**: Centrality scores, dependent counts, and identified God Nodes (e.g., `src/graph_engine.py (71 deps)`).
26
43
 
27
- # Reset metrics database
28
- ./bin/agy-tokens reset
44
+ ### 2. Graph-Guided Symbol Inspection
45
+ To read a targeted function or class without reading hundreds of lines of inert code:
46
+ ```json
47
+ {
48
+ "ServerName": "tokensafe",
49
+ "ToolName": "smart_read_file",
50
+ "Arguments": {
51
+ "path": "/absolute/path/to/file.py",
52
+ "symbol": "TargetClassName"
53
+ }
54
+ }
29
55
  ```
56
+ * **Output**: The target definition and its direct 1-hop callees are shown in full; all other classes/methods are folded into 1-line signatures.
30
57
 
31
- ## Programmatic Usage
58
+ ### 3. Command Sanitization
59
+ Before executing potentially verbose terminal commands (`git diff`, `git grep`, `pytest`, `find`):
60
+ ```json
61
+ {
62
+ "ServerName": "tokensafe",
63
+ "ToolName": "sanitize_command",
64
+ "Arguments": {
65
+ "command": "git diff HEAD~1"
66
+ }
67
+ }
68
+ ```
32
69
 
33
- To inspect current metrics programmatically:
70
+ ## CLI Usage
34
71
 
35
- ```python
36
- from src.metrics import MetricsTracker
37
- from src.dashboard import Dashboard
72
+ Developers and agents can run `tokensafe` (or `./bin/agy-tokens`) directly from the terminal:
38
73
 
39
- tracker = MetricsTracker()
40
- dashboard = Dashboard(tracker)
41
- print(dashboard.render_markdown())
42
- ```
74
+ ```bash
75
+ # View terminal breakdown & recent optimizations
76
+ tokensafe stats
77
+
78
+ # Open interactive Apple-style HTML dashboard in browser
79
+ tokensafe html
43
80
 
44
- ## How It Works Under the Hood
45
- 1. **PreInvocation Hook**: Intercepts verbose user prompts and generates a concise S-expression `(ACTION target symbol [traits] "intent")`, stripping politeness filler.
46
- 2. **PreToolUse Hook**: Prevents dumping 800+ lines into the context window by slicing `read_file` to relevant line ranges.
47
- 3. **Prompt Cache Anchor**: Static TOCP rules in `rules/AGENTS.md` hit Gemini's prompt cache with 100% stability.
48
- 4. **Persistent Metrics**: Every token saved is recorded in SQLite (`data/metrics.db`) with timestamp, percentage reduction, and estimated USD value.
81
+ # Run universal agent installer/synchronizer
82
+ tokensafe install
83
+
84
+ # Test prompt compression
85
+ tokensafe test "Refactor get_user_balance with redis caching"
86
+ ```
package/src/__init__.py CHANGED
@@ -2,5 +2,5 @@
2
2
  TokenSafe: Autonomous Zero-Loss Token Economy & Context Optimizer for AI Coding Agents.
3
3
  """
4
4
 
5
- __version__ = "1.1.0"
5
+ __version__ = "1.2.0"
6
6
  __author__ = "TokenSafe Team"
@@ -10,14 +10,17 @@ from typing import Tuple, Dict, Any, Optional
10
10
  class CommandSanitizer:
11
11
  """Optimizes terminal command lines to eliminate token waste."""
12
12
 
13
- # Lockfiles and binary patterns that should never be dumped in git diffs
13
+ # Lockfiles and binary patterns that should never be dumped in git diffs or searches
14
14
  LOCKFILE_EXCLUDES = [
15
15
  "':!package-lock.json'",
16
16
  "':!pnpm-lock.yaml'",
17
17
  "':!yarn.lock'",
18
+ "':!bun.lock'",
19
+ "':!bun.lockb'",
18
20
  "':!Cargo.lock'",
19
21
  "':!go.sum'",
20
22
  "':!composer.lock'",
23
+ "':!*.lock'",
21
24
  "':!*.min.js'",
22
25
  "':!*.min.css'",
23
26
  "':!*.bundle.js'",
@@ -80,11 +83,36 @@ class CommandSanitizer:
80
83
  est_saved = 1500
81
84
  return new_cmd, est_saved, "Pruned .git, node_modules, and venv from find"
82
85
 
83
- # 5. grep / ripgrep unbounded search
84
- if re.search(r"\b(grep|egrep)\b", cmd):
85
- if "--exclude-dir" not in cmd and "node_modules" not in cmd:
86
- new_cmd = f"{cmd} --exclude-dir={{dist,build,node_modules,.git,coverage,__pycache__,.next,data}}"
87
- if "|" not in new_cmd:
86
+ # 5. git grep search (Exclude lockfiles & directories via pathspecs)
87
+ if re.search(r"\bgit\b.*?\bgrep\b", cmd):
88
+ if ":!*.lock" not in cmd and ":!bun.lock" not in cmd:
89
+ git_excludes = "':!*.lock' ':!bun.lock' ':!bun.lockb' ':!package-lock.json' ':!dist' ':!build' ':!node_modules'"
90
+ stages = [s.strip() for s in re.split(r'(?<!\|)\|(?!\|)', cmd)]
91
+ for i, stage in enumerate(stages):
92
+ if re.search(r"\bgit\b.*?\bgrep\b", stage):
93
+ if " -- " in stage:
94
+ stages[i] = f"{stage} {git_excludes}"
95
+ else:
96
+ stages[i] = f"{stage} -- . {git_excludes}"
97
+ new_cmd = " | ".join(stages)
98
+ has_limiter = any(re.search(r'\b(head|tail|less)\b', s) for s in stages)
99
+ if not has_limiter:
100
+ new_cmd = f"{new_cmd} | head -n 30"
101
+ est_saved = 3500
102
+ return new_cmd, est_saved, "Excluded lockfiles from git grep and capped output"
103
+
104
+ # 6. grep / ripgrep unbounded search (only when recursive or scanning directory)
105
+ if re.search(r"\b(grep|egrep)\b", cmd) and not re.search(r"\bgit\b.*?\bgrep\b", cmd):
106
+ is_recursive = bool(re.search(r"\b(grep|egrep)\b.*?\s-[a-zA-Z]*[rR]", cmd))
107
+ if is_recursive and "--exclude-dir" not in cmd and "node_modules" not in cmd:
108
+ excludes = "--exclude='*.lock' --exclude='*.lockb' --exclude-dir={dist,build,node_modules,.git,coverage,__pycache__,.next,data}"
109
+ stages = [s.strip() for s in re.split(r'(?<!\|)\|(?!\|)', cmd)]
110
+ for i, stage in enumerate(stages):
111
+ if re.search(r"\b(grep|egrep)\b", stage):
112
+ stages[i] = f"{stage} {excludes}"
113
+ new_cmd = " | ".join(stages)
114
+ has_limiter = any(re.search(r'\b(head|tail|less)\b', s) for s in stages)
115
+ if not has_limiter:
88
116
  new_cmd = f"{new_cmd} | head -n 30"
89
117
  est_saved = 2500
90
118
  return new_cmd, est_saved, "Sanitized grep directories and capped output to 30 matches"
package/src/dashboard.py CHANGED
@@ -35,6 +35,8 @@ EVENT_NAMES = {
35
35
  "PROMPT": "Prompt Compactor (TOCP)",
36
36
  "TOOL_READ": "File Line Slicer (AST)",
37
37
  "AST_SKELETON": "AST Skeletonizer (Signatures)",
38
+ "GRAPH_SKELETON": "Graph-Guided Slicer (Call-Graph)",
39
+ "GRAPH_TOPOLOGY": "Architecture Graph (God Nodes)",
38
40
  "COMMAND_FILTER": "Command Sanitizer (CLI)",
39
41
  "OUTPUT_GUARD": "Output Token Guard (write_file)",
40
42
  "LOOP_GUARD": "Anti-Stuck Loop Guard",
@@ -415,20 +415,48 @@ class CodeGraph:
415
415
 
416
416
 
417
417
  class GraphEngine:
418
- """Singleton Manager for automatic background graph execution."""
418
+ """Multi-Project Manager for automatic background graph execution."""
419
419
 
420
- _instance: Optional[CodeGraph] = None
420
+ _instances: Dict[str, CodeGraph] = {}
421
+
422
+ @classmethod
423
+ def find_project_root(cls, start_path: str) -> str:
424
+ """Walks up from start_path to locate project boundary markers."""
425
+ curr = os.path.abspath(start_path)
426
+ if os.path.isfile(curr):
427
+ curr = os.path.dirname(curr)
428
+
429
+ root = os.path.abspath(os.sep)
430
+ while curr and curr != root:
431
+ for marker in (".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "requirements.txt"):
432
+ if os.path.exists(os.path.join(curr, marker)):
433
+ return curr
434
+ parent = os.path.dirname(curr)
435
+ if parent == curr:
436
+ break
437
+ curr = parent
438
+ return os.path.abspath(start_path if os.path.isdir(start_path) else os.path.dirname(start_path))
421
439
 
422
440
  @classmethod
423
441
  def get_instance(cls, root_dir: str = ".") -> CodeGraph:
424
- if cls._instance is None or cls._instance.root_dir != os.path.abspath(root_dir):
425
- cls._instance = CodeGraph(root_dir)
426
- cls._instance.scan()
442
+ root_abs = os.path.abspath(root_dir)
443
+ if root_abs not in cls._instances:
444
+ graph = CodeGraph(root_abs)
445
+ graph.scan()
446
+ cls._instances[root_abs] = graph
427
447
  else:
428
- # Incremental scan if older than 3 seconds
429
- if time.time() - cls._instance.last_scanned > 3.0:
430
- cls._instance.scan()
431
- return cls._instance
448
+ graph = cls._instances[root_abs]
449
+ if time.time() - graph.last_scanned > 3.0:
450
+ graph.scan()
451
+ return graph
452
+
453
+ @classmethod
454
+ def for_file(cls, file_path: str) -> Tuple[CodeGraph, str]:
455
+ """Resolves the owning project graph and relative file path for any arbitrary file."""
456
+ root = cls.find_project_root(file_path)
457
+ graph = cls.get_instance(root)
458
+ rel_path = os.path.relpath(os.path.abspath(file_path), root).replace("\\", "/")
459
+ return graph, rel_path
432
460
 
433
461
  def main():
434
462
  import sys
package/src/installer.py CHANGED
@@ -57,6 +57,14 @@ AGENTS_REGISTRY = {
57
57
  "config_path": os.path.join(HOME, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
58
58
  "detect_path": os.path.join(HOME, "Library", "Application Support", "Code")
59
59
  },
60
+ "claude_code": {
61
+ "name": "Claude Code CLI",
62
+ "type": "claude_md",
63
+ "config_path": os.path.join(HOME, ".claude.json"),
64
+ "agents_md_path": os.path.join(HOME, ".claude", "CLAUDE.md"),
65
+ "skills_path": os.path.join(HOME, ".claude", "skills", "token-optimizer"),
66
+ "detect_path": os.path.join(HOME, ".claude")
67
+ },
60
68
  "codex": {
61
69
  "name": "OpenAI Codex",
62
70
  "type": "codex_toml",
@@ -66,12 +74,19 @@ AGENTS_REGISTRY = {
66
74
  "plugin_path": os.path.join(HOME, ".codex", "plugins", "tokensafe"),
67
75
  "detect_path": os.path.join(HOME, ".codex")
68
76
  },
77
+ "copilot": {
78
+ "name": "GitHub Copilot",
79
+ "type": "copilot_json",
80
+ "config_path": os.path.join(HOME, ".copilot", "mcp-config.json"),
81
+ "detect_path": os.path.join(HOME, ".copilot")
82
+ },
69
83
  "zcode": {
70
84
  "name": "Z.ai Code",
71
85
  "type": "zcode_json",
72
86
  "config_path": os.path.join(HOME, ".zcode", "cli", "config.json"),
73
87
  "skills_path": os.path.join(HOME, ".zcode", "skills", "token-optimizer"),
74
88
  "plugin_path": os.path.join(HOME, ".zcode", "cli", "plugins", "tokensafe"),
89
+ "agents_md_path": os.path.join(HOME, ".zcode", "AGENTS.md"),
75
90
  "detect_path": os.path.join(HOME, ".zcode")
76
91
  }
77
92
  }
@@ -144,15 +159,16 @@ class AgentInstaller:
144
159
  if "rules_path" in info:
145
160
  rule_text = (
146
161
  "---\n"
147
- "description: TokenSafe token economy and context optimization protocol\n"
162
+ "description: TokenSafe token economy, graph-first navigation and context optimization protocol\n"
148
163
  "globs: *\n"
149
164
  "alwaysApply: true\n"
150
165
  "---\n\n"
151
- "# TokenSafe Optimization Protocol\n"
152
- "Always prioritize `tokensafe` MCP tools (`smart_read_file`, `compress_prompt`, `sanitize_command`) "
153
- "over default raw file reads or high-output terminal executions to preserve context tokens and prevent context overflow.\n"
154
- "- When inspecting code files, use `smart_read_file` with `symbol` or line range.\n"
155
- "- Sanitize verbose commands (`git diff`, `pytest`, `find`, `grep`) via `sanitize_command`.\n"
166
+ "# TokenSafe Graph-First Optimization Protocol\n"
167
+ "Always prioritize `tokensafe` MCP tools over default raw file reads or high-output terminal executions to prevent context window exhaustion.\n\n"
168
+ "1. **Architecture Discovery**: Always call `tokensafe:get_architecture_graph` before grep or multi-file reading. Returns project God Nodes and key dependency hubs in < 250 tokens without file crawling.\n"
169
+ "2. **Graph-Guided Symbol Inspection**: When inspecting code implementations, prioritize `tokensafe:smart_read_file` with the targeted `symbol`. Returns full executable bodies for the symbol and 1-hop dependencies while folding inert code into signatures (-85% to -92% tokens).\n"
170
+ "3. **No Unconstrained Shell Grep/Find**: Strictly avoid running raw recursive grep/find across projects. Use `get_architecture_graph` or `get_api_surface`.\n"
171
+ "4. **Command Sanitization**: Use `sanitize_command` on high-output terminal commands (`git diff`, `pytest`, `find`, `grep`) to prevent lockfile or test log context contamination.\n"
156
172
  )
157
173
  cls._write_file_safely(info["rules_path"], rule_text)
158
174
 
@@ -229,16 +245,74 @@ class AgentInstaller:
229
245
 
230
246
  if "agents_md_path" in info:
231
247
  codex_rule = (
232
- "## TOKENSAFE TOKEN OPTIMIZATION PROTOCOL\n"
233
- "Always prioritize `tokensafe` MCP tools (`smart_read_file`, `compress_prompt`, `sanitize_command`) "
234
- "over default raw file reads or high-token shell executions to preserve context tokens and prevent context overflow.\n"
235
- "- When inspecting code files, use `smart_read_file` with `symbol` or line range.\n"
236
- "- When running verbose commands (`git diff`, `pytest`, `find`, `grep`), sanitize them with `sanitize_command`.\n"
248
+ "## TOKENSAFE GRAPH-FIRST OPTIMIZATION PROTOCOL\n"
249
+ "Always prioritize `tokensafe` MCP tools over default raw file reads or high-output terminal executions to prevent context window exhaustion.\n"
250
+ "1. **Architecture Discovery**: Always call `tokensafe:get_architecture_graph` before grep or multi-file reading. Returns project God Nodes and key dependency hubs in < 250 tokens without file crawling.\n"
251
+ "2. **Graph-Guided Symbol Inspection**: When inspecting code implementations, prioritize `tokensafe:smart_read_file` with the targeted `symbol`. Returns full executable bodies for the symbol and 1-hop dependencies while folding inert code into signatures (-85% to -92% tokens).\n"
252
+ "3. **No Unconstrained Shell Grep/Find**: Strictly avoid running raw recursive grep/find across projects. Use `get_architecture_graph` or `get_api_surface`.\n"
253
+ "4. **Command Sanitization**: Use `sanitize_command` on high-output terminal commands (`git diff`, `pytest`, `find`, `grep`) to prevent lockfile or test log context contamination.\n"
237
254
  )
238
255
  cls._inject_global_rule(info["agents_md_path"], codex_rule)
239
256
 
240
257
  return True, f"Configured MCP, Plugin, Skill & AGENTS.md in {config_file}"
241
258
 
259
+ elif info["type"] == "claude_md":
260
+ if "config_path" in info:
261
+ config_file = info["config_path"]
262
+ data = {}
263
+ if os.path.exists(config_file):
264
+ try:
265
+ with open(config_file, "r", encoding="utf-8") as f:
266
+ data = json.load(f)
267
+ except Exception:
268
+ data = {}
269
+ if "mcpServers" not in data or not isinstance(data["mcpServers"], dict):
270
+ data["mcpServers"] = {}
271
+ data["mcpServers"]["tokensafe"] = {
272
+ "command": PYTHON_BIN,
273
+ "args": [MCP_SERVER_PATH]
274
+ }
275
+ with open(config_file, "w", encoding="utf-8") as f:
276
+ json.dump(data, f, indent=2)
277
+
278
+ if "agents_md_path" in info:
279
+ claude_rule = (
280
+ "## TOKENSAFE GRAPH-FIRST OPTIMIZATION PROTOCOL\n"
281
+ "Always prioritize `tokensafe` MCP tools over default raw file reads or recursive grep/find.\n"
282
+ "1. **Architecture Discovery**: Always call `tokensafe:get_architecture_graph` before grep or multi-file reading. Returns project God Nodes and key dependency hubs in < 250 tokens without file crawling.\n"
283
+ "2. **Graph-Guided Symbol Inspection**: When inspecting code implementations, prioritize `tokensafe:smart_read_file` with the targeted `symbol`. Returns full executable bodies for the symbol and 1-hop dependencies while folding inert code into signatures (-85% to -92% tokens).\n"
284
+ "3. **No Unconstrained Shell Grep/Find**: Strictly avoid running raw recursive grep/find across projects. Use `get_architecture_graph` or `get_api_surface`.\n"
285
+ "4. **Command Sanitization**: Use `sanitize_command` on high-output terminal commands (`git diff`, `pytest`, `find`, `grep`) to prevent lockfile or test log context contamination.\n"
286
+ )
287
+ cls._inject_global_rule(info["agents_md_path"], claude_rule)
288
+
289
+ if "skills_path" in info:
290
+ cls._symlink_skill(info["skills_path"])
291
+
292
+ return True, f"Configured MCP, Rule & Skill in {info.get('agents_md_path')}"
293
+
294
+ elif info["type"] == "copilot_json":
295
+ config_file = info["config_path"]
296
+ os.makedirs(os.path.dirname(config_file), exist_ok=True)
297
+ data = {}
298
+ if os.path.exists(config_file):
299
+ try:
300
+ with open(config_file, "r", encoding="utf-8") as f:
301
+ data = json.load(f)
302
+ except Exception:
303
+ data = {}
304
+ if "mcpServers" not in data or not isinstance(data["mcpServers"], dict):
305
+ data["mcpServers"] = {}
306
+ data["mcpServers"]["tokensafe"] = {
307
+ "type": "stdio",
308
+ "command": PYTHON_BIN,
309
+ "args": [MCP_SERVER_PATH],
310
+ "tools": ["*"]
311
+ }
312
+ with open(config_file, "w", encoding="utf-8") as f:
313
+ json.dump(data, f, indent=2)
314
+ return True, f"Configured MCP for GitHub Copilot in {config_file}"
315
+
242
316
  elif info["type"] == "zcode_json":
243
317
  config_file = info["config_path"]
244
318
  os.makedirs(os.path.dirname(config_file), exist_ok=True)
@@ -271,7 +345,18 @@ class AgentInstaller:
271
345
  if "plugin_path" in info:
272
346
  cls._symlink_dir(PROJECT_ROOT, info["plugin_path"])
273
347
 
274
- return True, f"Configured MCP, Plugin & Skill in {config_file}"
348
+ if "agents_md_path" in info:
349
+ zcode_rule = (
350
+ "## TOKENSAFE GRAPH-FIRST OPTIMIZATION PROTOCOL\n"
351
+ "Always prioritize `tokensafe` MCP tools over default raw file reads or high-output terminal executions to prevent context window exhaustion.\n"
352
+ "1. **Architecture Discovery**: Always call `tokensafe:get_architecture_graph` before grep or multi-file reading. Returns project God Nodes and key dependency hubs in < 250 tokens without file crawling.\n"
353
+ "2. **Graph-Guided Symbol Inspection**: When inspecting code implementations, prioritize `tokensafe:smart_read_file` with the targeted `symbol`. Returns full executable bodies for the symbol and 1-hop dependencies while folding inert code into signatures (-85% to -92% tokens).\n"
354
+ "3. **No Unconstrained Shell Grep/Find**: Strictly avoid running raw recursive grep/find across projects. Use `get_architecture_graph` or `get_api_surface`.\n"
355
+ "4. **Command Sanitization**: Use `sanitize_command` on high-output terminal commands (`git diff`, `pytest`, `find`, `grep`) to prevent lockfile or test log context contamination.\n"
356
+ )
357
+ cls._inject_global_rule(info["agents_md_path"], zcode_rule)
358
+
359
+ return True, f"Configured MCP, Plugin, Skill & AGENTS.md in {config_file}"
275
360
 
276
361
  return False, "Unsupported type"
277
362
 
package/src/mailer.py ADDED
@@ -0,0 +1,327 @@
1
+ """
2
+ TokenSafe Transactional Email Service.
3
+ Handles transactional emails via ImprovMX SMTP (port 587 TLS)
4
+ with beautiful responsive dark-mode HTML templates and asynchronous delivery.
5
+ """
6
+
7
+ import os
8
+ import ssl
9
+ import smtplib
10
+ import threading
11
+ from email.mime.multipart import MIMEMultipart
12
+ from email.mime.text import MIMEText
13
+ from typing import Optional, Dict, Any
14
+
15
+ class TokenSafeMailer:
16
+ """Dispatches transactional notifications (Welcome, Team Seat Invites)."""
17
+
18
+ SMTP_HOST = os.environ.get("SMTP_HOST", "smtp.improvmx.com")
19
+ SMTP_PORT = int(os.environ.get("SMTP_PORT", "587"))
20
+ SMTP_USER = os.environ.get("SMTP_USER", "any-alias-1@tokensafe.dev")
21
+ SMTP_PASS = os.environ.get("SMTP_PASS", "g7Qw8nB1U1Q9")
22
+ SMTP_FROM = os.environ.get("SMTP_FROM", "TokenSafe <support@tokensafe.dev>")
23
+ SMTP_TIMEOUT = 10
24
+
25
+ @classmethod
26
+ def _send_raw(cls, recipient: str, subject: str, text_content: str, html_content: str) -> bool:
27
+ """Sends an email synchronously via SMTP with STARTTLS."""
28
+ msg = MIMEMultipart("alternative")
29
+ msg["Subject"] = subject
30
+ msg["From"] = cls.SMTP_FROM
31
+ msg["To"] = recipient
32
+
33
+ # Attach plain text and HTML alternatives
34
+ msg.attach(MIMEText(text_content, "plain", "utf-8"))
35
+ msg.attach(MIMEText(html_content, "html", "utf-8"))
36
+
37
+ try:
38
+ server = smtplib.SMTP(cls.SMTP_HOST, cls.SMTP_PORT, timeout=cls.SMTP_TIMEOUT)
39
+ server.ehlo()
40
+ context = ssl.create_default_context()
41
+ server.starttls(context=context)
42
+ server.ehlo()
43
+ server.login(cls.SMTP_USER, cls.SMTP_PASS)
44
+ server.sendmail(cls.SMTP_USER, [recipient], msg.as_string())
45
+ server.quit()
46
+ return True
47
+ except Exception as e:
48
+ # Log error in production
49
+ print(f"[Mailer Error] Failed to send email to {recipient}: {e}")
50
+ return False
51
+
52
+ @classmethod
53
+ def send_async(cls, recipient: str, subject: str, text_content: str, html_content: str):
54
+ """Dispatches email sending in a non-blocking background daemon thread."""
55
+ thread = threading.Thread(
56
+ target=cls._send_raw,
57
+ args=(recipient, subject, text_content, html_content),
58
+ daemon=True
59
+ )
60
+ thread.start()
61
+
62
+ @classmethod
63
+ def send_welcome_email(cls, recipient: str, license_key: str, tier: str = "solo", is_trial: bool = True):
64
+ """Sends welcome email with license key and 1-click onboarding instructions."""
65
+ plan_name = "Solo Developer (7-Day Trial)" if tier == "solo" else "Engineering Team (7-Day Trial)"
66
+ subject = f"Welcome to TokenSafe — Your {plan_name} License Key"
67
+
68
+ text_content = f"""Welcome to TokenSafe!
69
+
70
+ Your {plan_name} has been activated.
71
+
72
+ Your License Key:
73
+ {license_key}
74
+
75
+ Quick Setup (1 command):
76
+ npx tokensafe
77
+
78
+ Or connect existing tools:
79
+ tokensafe auth {license_key}
80
+
81
+ Open your cloud dashboard:
82
+ https://tokensafe.dev/dashboard
83
+
84
+ If you need any assistance, simply reply to this email.
85
+ — The TokenSafe Team
86
+ https://tokensafe.dev
87
+ """
88
+
89
+ html_content = f"""<!DOCTYPE html>
90
+ <html>
91
+ <head>
92
+ <meta charset="utf-8">
93
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
94
+ <title>{subject}</title>
95
+ </head>
96
+ <body style="margin: 0; padding: 0; background-color: #030712; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #f3f4f6;">
97
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #030712; padding: 40px 20px;">
98
+ <tr>
99
+ <td align="center">
100
+ <table role="presentation" width="100%" style="max-width: 580px; background-color: #0f172a; border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 16px; padding: 36px; box-shadow: 0 20px 40px rgba(0,0,0,0.4);">
101
+
102
+ <!-- Logo / Header -->
103
+ <tr>
104
+ <td style="padding-bottom: 24px; border-bottom: 1px solid rgba(255, 255, 255, 0.06);">
105
+ <span style="font-size: 20px; font-weight: 800; color: #ffffff; letter-spacing: -0.5px;">
106
+ Token<span style="color: #10b981;">Safe</span>
107
+ </span>
108
+ <span style="float: right; font-size: 11px; font-weight: 700; color: #10b981; background: rgba(16, 185, 129, 0.1); border: 1px solid rgba(16, 185, 129, 0.2); padding: 4px 10px; border-radius: 999px; text-transform: uppercase;">
109
+ {tier.upper()} ACTIVE
110
+ </span>
111
+ </td>
112
+ </tr>
113
+
114
+ <!-- Title -->
115
+ <tr>
116
+ <td style="padding-top: 28px; padding-bottom: 12px;">
117
+ <h1 style="margin: 0; font-size: 22px; font-weight: 700; color: #ffffff; line-height: 1.3;">
118
+ Welcome to Autonomous Token Economy
119
+ </h1>
120
+ <p style="margin: 8px 0 0; font-size: 14px; color: #94a3b8; line-height: 1.6;">
121
+ Your plan <strong style="color: #f1f5f9;">{plan_name}</strong> is ready. Your agents will now compress context, eliminate stuck loops, and protect token budgets.
122
+ </p>
123
+ </td>
124
+ </tr>
125
+
126
+ <!-- License Key Box -->
127
+ <tr>
128
+ <td style="padding: 20px 0;">
129
+ <div style="background-color: #020617; border: 1px solid rgba(16, 185, 129, 0.3); border-radius: 12px; padding: 18px; text-align: center;">
130
+ <div style="font-size: 11px; font-weight: 600; text-transform: uppercase; color: #94a3b8; letter-spacing: 0.5px; margin-bottom: 8px;">
131
+ Your Cryptographic License Key
132
+ </div>
133
+ <div style="font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 15px; font-weight: 700; color: #34d399; letter-spacing: 0.5px; word-break: break-all;">
134
+ {license_key}
135
+ </div>
136
+ </div>
137
+ </td>
138
+ </tr>
139
+
140
+ <!-- Quick Setup Steps -->
141
+ <tr>
142
+ <td style="padding-bottom: 24px;">
143
+ <h3 style="font-size: 14px; font-weight: 600; color: #e2e8f0; margin: 0 0 12px;">
144
+ 🚀 Quick Start (Instant Installation)
145
+ </h3>
146
+
147
+ <!-- Command 1 -->
148
+ <div style="background-color: #020617; border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 8px; padding: 12px 16px; margin-bottom: 10px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px; color: #f8fafc;">
149
+ <span style="color: #10b981;">$</span> npx tokensafe
150
+ </div>
151
+
152
+ <!-- Command 2 -->
153
+ <div style="background-color: #020617; border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 8px; padding: 12px 16px; margin-bottom: 16px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px; color: #f8fafc;">
154
+ <span style="color: #10b981;">$</span> tokensafe auth {license_key}
155
+ </div>
156
+
157
+ <p style="margin: 0; font-size: 13px; color: #94a3b8; line-height: 1.5;">
158
+ Or install via Python PyPI: <code style="color: #38bdf8; background: rgba(56, 189, 248, 0.1); padding: 2px 6px; border-radius: 4px;">pip install tokensafe</code>. Compatible with Cursor, Claude Desktop, Antigravity, and Codex.
159
+ </p>
160
+ </td>
161
+ </tr>
162
+
163
+ <!-- CTA Button -->
164
+ <tr>
165
+ <td align="center" style="padding: 16px 0 28px;">
166
+ <a href="https://tokensafe.dev/dashboard?key={license_key}" style="display: inline-block; background-color: #10b981; color: #020617; font-size: 14px; font-weight: 700; text-decoration: none; padding: 14px 32px; border-radius: 10px; box-shadow: 0 8px 16px rgba(16, 185, 129, 0.2);">
167
+ Open Cloud Analytics Dashboard →
168
+ </a>
169
+ </td>
170
+ </tr>
171
+
172
+ <!-- Footer -->
173
+ <tr>
174
+ <td style="border-top: 1px solid rgba(255, 255, 255, 0.06); padding-top: 20px; font-size: 12px; color: #64748b; line-height: 1.5;">
175
+ Need help or custom integrations? Just reply to this email or reach us at <a href="mailto:support@tokensafe.dev" style="color: #10b981; text-decoration: none;">support@tokensafe.dev</a>.<br>
176
+ © 2026 TokenSafe. Autonomous Zero-Loss Token Economy.
177
+ </td>
178
+ </tr>
179
+
180
+ </table>
181
+ </td>
182
+ </tr>
183
+ </table>
184
+ </body>
185
+ </html>
186
+ """
187
+ cls.send_async(recipient, subject, text_content, html_content)
188
+
189
+ @classmethod
190
+ def send_team_invite_email(cls, recipient: str, seat_license_key: str, owner_email: str):
191
+ """Sends team member onboarding email with instructions on connecting IDEs."""
192
+ subject = f"Your TokenSafe Team Seat License — Invited by {owner_email}"
193
+
194
+ text_content = f"""Hi there!
195
+
196
+ Your team lead ({owner_email}) has granted you a TokenSafe Team developer seat.
197
+
198
+ Your Personal Seat Key:
199
+ {seat_license_key}
200
+
201
+ Setup Guide:
202
+ 1. Run the instant setup:
203
+ npx tokensafe
204
+
205
+ 2. Connect your personal seat license:
206
+ tokensafe auth {seat_license_key}
207
+
208
+ 3. IDE Integrations:
209
+ - Cursor: Extension is activated automatically.
210
+ - Claude Desktop: MCP server is configured in ~/Library/Application Support/Claude/claude_desktop_config.json.
211
+ - Terminal: Run 'tokensafe stats' anytime to see your saved context and tokens.
212
+
213
+ Track your developer economics:
214
+ https://tokensafe.dev/dashboard
215
+
216
+ If you have any questions, contact your team lead ({owner_email}) or reply to this email.
217
+ — The TokenSafe Team
218
+ """
219
+
220
+ html_content = f"""<!DOCTYPE html>
221
+ <html>
222
+ <head>
223
+ <meta charset="utf-8">
224
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
225
+ <title>{subject}</title>
226
+ </head>
227
+ <body style="margin: 0; padding: 0; background-color: #030712; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #f3f4f6;">
228
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #030712; padding: 40px 20px;">
229
+ <tr>
230
+ <td align="center">
231
+ <table role="presentation" width="100%" style="max-width: 580px; background-color: #0f172a; border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 16px; padding: 36px; box-shadow: 0 20px 40px rgba(0,0,0,0.4);">
232
+
233
+ <!-- Logo / Header -->
234
+ <tr>
235
+ <td style="padding-bottom: 24px; border-bottom: 1px solid rgba(255, 255, 255, 0.06);">
236
+ <span style="font-size: 20px; font-weight: 800; color: #ffffff; letter-spacing: -0.5px;">
237
+ Token<span style="color: #10b981;">Safe</span>
238
+ </span>
239
+ <span style="float: right; font-size: 11px; font-weight: 700; color: #38bdf8; background: rgba(56, 189, 248, 0.1); border: 1px solid rgba(56, 189, 248, 0.2); padding: 4px 10px; border-radius: 999px; text-transform: uppercase;">
240
+ TEAM SEAT INVITE
241
+ </span>
242
+ </td>
243
+ </tr>
244
+
245
+ <!-- Title -->
246
+ <tr>
247
+ <td style="padding-top: 28px; padding-bottom: 12px;">
248
+ <h1 style="margin: 0; font-size: 22px; font-weight: 700; color: #ffffff; line-height: 1.3;">
249
+ You've been added to TokenSafe Team
250
+ </h1>
251
+ <p style="margin: 8px 0 0; font-size: 14px; color: #94a3b8; line-height: 1.6;">
252
+ Team lead <strong style="color: #38bdf8;">{owner_email}</strong> has provisioned a dedicated TokenSafe developer seat for you. Context compression and token guards are now active for your workflow.
253
+ </p>
254
+ </td>
255
+ </tr>
256
+
257
+ <!-- License Key Box -->
258
+ <tr>
259
+ <td style="padding: 20px 0;">
260
+ <div style="background-color: #020617; border: 1px solid rgba(56, 189, 248, 0.3); border-radius: 12px; padding: 18px; text-align: center;">
261
+ <div style="font-size: 11px; font-weight: 600; text-transform: uppercase; color: #94a3b8; letter-spacing: 0.5px; margin-bottom: 8px;">
262
+ Your Dedicated Team Seat Key
263
+ </div>
264
+ <div style="font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 15px; font-weight: 700; color: #38bdf8; letter-spacing: 0.5px; word-break: break-all;">
265
+ {seat_license_key}
266
+ </div>
267
+ </div>
268
+ </td>
269
+ </tr>
270
+
271
+ <!-- Setup Instructions -->
272
+ <tr>
273
+ <td style="padding-bottom: 24px;">
274
+ <h3 style="font-size: 14px; font-weight: 600; color: #e2e8f0; margin: 0 0 12px;">
275
+ ⚙️ Quick 3-Step Setup
276
+ </h3>
277
+
278
+ <div style="margin-bottom: 16px;">
279
+ <div style="font-size: 12px; font-weight: 600; color: #cbd5e1; margin-bottom: 4px;">Step 1: Install TokenSafe CLI</div>
280
+ <div style="background-color: #020617; border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 8px; padding: 10px 14px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px; color: #f8fafc;">
281
+ <span style="color: #10b981;">$</span> npx tokensafe
282
+ </div>
283
+ </div>
284
+
285
+ <div style="margin-bottom: 16px;">
286
+ <div style="font-size: 12px; font-weight: 600; color: #cbd5e1; margin-bottom: 4px;">Step 2: Bind your seat license</div>
287
+ <div style="background-color: #020617; border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 8px; padding: 10px 14px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px; color: #f8fafc;">
288
+ <span style="color: #10b981;">$</span> tokensafe auth {seat_license_key}
289
+ </div>
290
+ </div>
291
+
292
+ <div style="margin-bottom: 16px;">
293
+ <div style="font-size: 12px; font-weight: 600; color: #cbd5e1; margin-bottom: 4px;">Step 3: IDE Agents Automatic Hook</div>
294
+ <ul style="margin: 6px 0 0 18px; padding: 0; font-size: 13px; color: #94a3b8; line-height: 1.6;">
295
+ <li><strong>Cursor / VSCode:</strong> Hooks directly into extension background loop.</li>
296
+ <li><strong>Claude Desktop:</strong> MCP server runs at <code style="color: #34d399;">tokensafe-mcp</code>.</li>
297
+ <li><strong>Terminal:</strong> Check your stats with <code style="color: #38bdf8;">tokensafe stats</code>.</li>
298
+ </ul>
299
+ </div>
300
+ </td>
301
+ </tr>
302
+
303
+ <!-- CTA Button -->
304
+ <tr>
305
+ <td align="center" style="padding: 16px 0 28px;">
306
+ <a href="https://tokensafe.dev/dashboard?key={seat_license_key}" style="display: inline-block; background-color: #0284c7; color: #ffffff; font-size: 14px; font-weight: 700; text-decoration: none; padding: 14px 32px; border-radius: 10px; box-shadow: 0 8px 16px rgba(2, 132, 199, 0.25);">
307
+ View Your Developer Portal →
308
+ </a>
309
+ </td>
310
+ </tr>
311
+
312
+ <!-- Footer -->
313
+ <tr>
314
+ <td style="border-top: 1px solid rgba(255, 255, 255, 0.06); padding-top: 20px; font-size: 12px; color: #64748b; line-height: 1.5;">
315
+ Invited by {owner_email}. Have questions? Contact your team lead or reach us at <a href="mailto:support@tokensafe.dev" style="color: #38bdf8; text-decoration: none;">support@tokensafe.dev</a>.<br>
316
+ © 2026 TokenSafe. Autonomous Zero-Loss Token Economy.
317
+ </td>
318
+ </tr>
319
+
320
+ </table>
321
+ </td>
322
+ </tr>
323
+ </table>
324
+ </body>
325
+ </html>
326
+ """
327
+ cls.send_async(recipient, subject, text_content, html_content)
package/src/mcp_server.py CHANGED
@@ -21,10 +21,12 @@ from src.command_sanitizer import CommandSanitizer
21
21
  from src.loop_detector import LoopDetector
22
22
  from src.metrics import MetricsTracker
23
23
  from src.dashboard import Dashboard
24
+ from src.graph_engine import GraphEngine
25
+ from src.tokenizer import count_tokens
24
26
 
25
27
  SERVER_INFO = {
26
28
  "name": "tokensafe-mcp",
27
- "version": "1.0.0"
29
+ "version": "1.2.0"
28
30
  }
29
31
 
30
32
  TOOLS = [
@@ -35,13 +37,33 @@ TOOLS = [
35
37
  "type": "object",
36
38
  "properties": {
37
39
  "path": {"type": "string", "description": "Absolute path to the code file"},
38
- "symbol": {"type": "string", "description": "Optional: targeted function or class name"},
40
+ "symbol": {"type": "string", "description": "Optional: targeted function or class name for graph-guided skeletonization"},
39
41
  "start_line": {"type": "integer", "description": "Optional: starting line number"},
40
42
  "end_line": {"type": "integer", "description": "Optional: ending line number"}
41
43
  },
42
44
  "required": ["path"]
43
45
  }
44
46
  },
47
+ {
48
+ "name": "get_architecture_graph",
49
+ "description": "Returns top architectural God Nodes and dependency topology for the target project.",
50
+ "inputSchema": {
51
+ "type": "object",
52
+ "properties": {
53
+ "project_path": {"type": "string", "description": "Optional: root directory or file path of the project"}
54
+ }
55
+ }
56
+ },
57
+ {
58
+ "name": "get_api_surface",
59
+ "description": "Generates and returns a compact markdown map of all exported interfaces, classes, and types in the project.",
60
+ "inputSchema": {
61
+ "type": "object",
62
+ "properties": {
63
+ "project_path": {"type": "string", "description": "Optional: root directory or file path of the project"}
64
+ }
65
+ }
66
+ },
45
67
  {
46
68
  "name": "compress_prompt",
47
69
  "description": "Compresses a verbose developer prompt into an ultra-dense TOCP S-Expression via DeepSeek-V3 / Regex.",
@@ -102,6 +124,27 @@ def handle_tool_call(name: str, args: dict) -> dict:
102
124
  return {"content": [{"type": "text", "text": (prefetch_block or "") + content}]}
103
125
 
104
126
  if symbol:
127
+ try:
128
+ graph, rel_path = GraphEngine.for_file(path)
129
+ graph_skel = graph.graph_guided_skeletonize(path, target_symbol=symbol)
130
+ if graph_skel:
131
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
132
+ raw_code = f.read()
133
+ orig_t = count_tokens(raw_code)
134
+ skel_t = count_tokens(graph_skel)
135
+ saved = max(0, orig_t - skel_t)
136
+ tracker.record_event(
137
+ session_id="mcp_session",
138
+ event_type="GRAPH_SKELETON",
139
+ original_tokens=orig_t,
140
+ optimized_tokens=skel_t,
141
+ details={"file": os.path.basename(path), "symbol": symbol, "saved_tokens": saved}
142
+ )
143
+ header = f"// [TokenSafe Graph-Guided Skeleton for '{symbol}' | Full body + 1-hop dependencies]\n"
144
+ return {"content": [{"type": "text", "text": (prefetch_block or "") + header + graph_skel}]}
145
+ except Exception:
146
+ pass
147
+
105
148
  overwrite, metrics = ToolSlicer.optimize_read_file({"Path": path}, context_hint=symbol)
106
149
  if overwrite:
107
150
  with open(path, "r", encoding="utf-8", errors="replace") as f:
@@ -144,6 +187,44 @@ def handle_tool_call(name: str, args: dict) -> dict:
144
187
  dashboard = Dashboard(tracker)
145
188
  return {"content": [{"type": "text", "text": dashboard.render_markdown()}]}
146
189
 
190
+ elif name == "get_architecture_graph":
191
+ target = args.get("project_path") or os.getcwd()
192
+ graph = GraphEngine.get_instance(target)
193
+ god_nodes = graph.get_god_nodes(7)
194
+ banner = graph.format_topology_banner()
195
+ payload = {
196
+ "project_root": graph.root_dir,
197
+ "banner": banner,
198
+ "total_files": len(graph.file_symbols),
199
+ "total_symbols": len(graph.symbols),
200
+ "god_nodes": god_nodes
201
+ }
202
+ est_crawl_tokens = max(5000, len(graph.file_symbols) * 800)
203
+ topo_tokens = 250
204
+ tracker.record_event(
205
+ session_id="mcp_session",
206
+ event_type="GRAPH_TOPOLOGY",
207
+ original_tokens=est_crawl_tokens,
208
+ optimized_tokens=topo_tokens,
209
+ details={"hubs_count": len(god_nodes), "indexed_files": len(graph.file_symbols)}
210
+ )
211
+ return {"content": [{"type": "text", "text": json.dumps(payload, indent=2, ensure_ascii=False)}]}
212
+
213
+ elif name == "get_api_surface":
214
+ target = args.get("project_path") or os.getcwd()
215
+ graph = GraphEngine.get_instance(target)
216
+ surface_md = graph.generate_api_surface()
217
+ surface_tokens = count_tokens(surface_md)
218
+ est_orig_tokens = max(10000, len(graph.file_symbols) * 1200)
219
+ tracker.record_event(
220
+ session_id="mcp_session",
221
+ event_type="GRAPH_TOPOLOGY",
222
+ original_tokens=est_orig_tokens,
223
+ optimized_tokens=surface_tokens,
224
+ details={"type": "API_SURFACE", "symbols_count": len(graph.symbols)}
225
+ )
226
+ return {"content": [{"type": "text", "text": surface_md}]}
227
+
147
228
  return {"isError": True, "content": [{"type": "text", "text": f"Unknown tool: {name}"}]}
148
229
 
149
230
  def run_mcp_server():
@@ -77,9 +77,10 @@ class Skeletonizer:
77
77
  return "\n".join(out)
78
78
 
79
79
  @classmethod
80
- def get_skeleton_file(cls, original_path: str) -> Tuple[Optional[str], int, int]:
80
+ def get_skeleton_file(cls, original_path: str, target_symbol: Optional[str] = None) -> Tuple[Optional[str], int, int]:
81
81
  """
82
82
  Reads original_path, checks mtime cache, generates skeleton if modified or absent.
83
+ If target_symbol is provided, applies Graph-Guided AST skeletonization retaining 1-hop dependencies.
83
84
  Returns: (skeleton_path_or_none, orig_tokens, skeleton_tokens)
84
85
  """
85
86
  if not original_path or not os.path.exists(original_path):
@@ -95,9 +96,10 @@ class Skeletonizer:
95
96
  os.makedirs(cls.CACHE_DIR, exist_ok=True)
96
97
  path_hash = hashlib.md5(original_path.encode("utf-8")).hexdigest()[:8]
97
98
  base_name = os.path.basename(original_path)
98
- skeleton_filename = f"{path_hash}_{base_name}"
99
+ sym_suffix = f"_{target_symbol}" if target_symbol else ""
100
+ skeleton_filename = f"{path_hash}{sym_suffix}_{base_name}"
99
101
  skeleton_path = os.path.join(cls.CACHE_DIR, skeleton_filename)
100
- meta_path = os.path.join(cls.CACHE_DIR, f"{path_hash}_{base_name}.meta.json")
102
+ meta_path = os.path.join(cls.CACHE_DIR, f"{path_hash}{sym_suffix}_{base_name}.meta.json")
101
103
 
102
104
  # 1. Check incremental mtime cache
103
105
  if os.path.exists(skeleton_path) and os.path.exists(meta_path):
@@ -122,7 +124,18 @@ class Skeletonizer:
122
124
  return None, 0, 0
123
125
 
124
126
  ext = os.path.splitext(original_path)[1]
125
- skeleton_content = cls.skeletonize_code(code, ext)
127
+
128
+ # If target symbol specified, use Graph-Guided skeletonization
129
+ if target_symbol:
130
+ try:
131
+ from src.graph_engine import GraphEngine
132
+ graph, rel_path = GraphEngine.for_file(original_path)
133
+ graph_content = graph.graph_guided_skeletonize(original_path, target_symbol=target_symbol)
134
+ skeleton_content = graph_content if graph_content else cls.skeletonize_code(code, ext)
135
+ except Exception:
136
+ skeleton_content = cls.skeletonize_code(code, ext)
137
+ else:
138
+ skeleton_content = cls.skeletonize_code(code, ext)
126
139
 
127
140
  # Prefetch local dependency signatures
128
141
  from src.dependency_prefetcher import DependencyPrefetcher
@@ -134,13 +147,20 @@ class Skeletonizer:
134
147
  if skel_tokens >= orig_tokens * 0.7:
135
148
  return None, 0, 0
136
149
 
137
- header_comment = (
138
- f"// [TOCP AST Skeleton: {lines_count} lines folded into API signatures]\n"
139
- f"// [To inspect implementation of a specific method, call read_file with StartLine/EndLine]\n\n"
140
- ) if ext != ".py" else (
141
- f"# [TOCP AST Skeleton: {lines_count} lines folded into API signatures]\n"
142
- f"# [To inspect implementation of a specific method, call read_file with StartLine/EndLine]\n\n"
143
- )
150
+ if target_symbol:
151
+ header_comment = (
152
+ f"// [TokenSafe Graph-Guided Skeleton for '{target_symbol}': target & 1-hop dependencies kept full]\n\n"
153
+ ) if ext != ".py" else (
154
+ f"# [TokenSafe Graph-Guided Skeleton for '{target_symbol}': target & 1-hop dependencies kept full]\n\n"
155
+ )
156
+ else:
157
+ header_comment = (
158
+ f"// [TOCP AST Skeleton: {lines_count} lines folded into API signatures]\n"
159
+ f"// [To inspect implementation of a specific method, call read_file with StartLine/EndLine]\n\n"
160
+ ) if ext != ".py" else (
161
+ f"# [TOCP AST Skeleton: {lines_count} lines folded into API signatures]\n"
162
+ f"# [To inspect implementation of a specific method, call read_file with StartLine/EndLine]\n\n"
163
+ )
144
164
 
145
165
  full_content = header_comment + (prefetch_block + "\n" if prefetch_block else "") + skeleton_content
146
166
 
package/src/slicer.py CHANGED
@@ -91,7 +91,8 @@ class ToolSlicer:
91
91
  "original_tokens": total_tokens,
92
92
  "optimized_tokens": sliced_tokens,
93
93
  "lines_total": total_lines,
94
- "lines_sliced": (new_end - new_start + 1)
94
+ "lines_sliced": (new_end - new_start + 1),
95
+ "target_symbol": target_symbol
95
96
  }
96
97
  return overwrite, metrics
97
98