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 +28 -1
- package/hooks/pre_tool_use.py +22 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/rules/AGENTS.md +5 -0
- package/skills/token-optimizer/SKILL.md +67 -29
- package/src/__init__.py +1 -1
- package/src/command_sanitizer.py +34 -6
- package/src/dashboard.py +2 -0
- package/src/graph_engine.py +37 -9
- package/src/installer.py +97 -12
- package/src/mailer.py +327 -0
- package/src/mcp_server.py +83 -2
- package/src/skeletonizer.py +31 -11
- package/src/slicer.py +2 -1
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
|
-
- **
|
|
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
|
|
package/hooks/pre_tool_use.py
CHANGED
|
@@ -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,
|
|
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
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agy-token-optimizer",
|
|
3
3
|
"displayName": "TokenSafe",
|
|
4
|
-
"version": "1.
|
|
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)
|
|
5
|
+
navigate codebases via zero-token AST architecture graphs, and operate the Token-Optimized Communication Protocol (TOCP).
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# TokenSafe Optimization & Architecture Intelligence Skill
|
|
9
9
|
|
|
10
|
-
This skill
|
|
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
|
-
##
|
|
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
|
-
|
|
18
|
+
## Core MCP Tools (`tokensafe`)
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
./bin/agy-tokens test "Слушай, перепиши функцию get_balance в wallet.ts, добавь redis cache"
|
|
29
|
+
## Agent Graph-First Workflow
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
+
## CLI Usage
|
|
34
71
|
|
|
35
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
package/src/command_sanitizer.py
CHANGED
|
@@ -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
|
|
84
|
-
if re.search(r"\b
|
|
85
|
-
if "
|
|
86
|
-
|
|
87
|
-
|
|
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",
|
package/src/graph_engine.py
CHANGED
|
@@ -415,20 +415,48 @@ class CodeGraph:
|
|
|
415
415
|
|
|
416
416
|
|
|
417
417
|
class GraphEngine:
|
|
418
|
-
"""
|
|
418
|
+
"""Multi-Project Manager for automatic background graph execution."""
|
|
419
419
|
|
|
420
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
429
|
-
if time.time() -
|
|
430
|
-
|
|
431
|
-
return
|
|
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
|
|
153
|
-
"
|
|
154
|
-
"- When inspecting code
|
|
155
|
-
"
|
|
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
|
|
233
|
-
"Always prioritize `tokensafe` MCP tools
|
|
234
|
-
"
|
|
235
|
-
"- When inspecting code
|
|
236
|
-
"
|
|
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
|
-
|
|
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.
|
|
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():
|
package/src/skeletonizer.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|