tokensafe 1.1.1 → 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/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/installer.py +97 -12
- package/src/mailer.py +327 -0
- package/src/mcp_server.py +32 -1
- package/src/skeletonizer.py +31 -11
- package/src/slicer.py +2 -1
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/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
|
@@ -22,10 +22,11 @@ from src.loop_detector import LoopDetector
|
|
|
22
22
|
from src.metrics import MetricsTracker
|
|
23
23
|
from src.dashboard import Dashboard
|
|
24
24
|
from src.graph_engine import GraphEngine
|
|
25
|
+
from src.tokenizer import count_tokens
|
|
25
26
|
|
|
26
27
|
SERVER_INFO = {
|
|
27
28
|
"name": "tokensafe-mcp",
|
|
28
|
-
"version": "1.
|
|
29
|
+
"version": "1.2.0"
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
TOOLS = [
|
|
@@ -127,6 +128,18 @@ def handle_tool_call(name: str, args: dict) -> dict:
|
|
|
127
128
|
graph, rel_path = GraphEngine.for_file(path)
|
|
128
129
|
graph_skel = graph.graph_guided_skeletonize(path, target_symbol=symbol)
|
|
129
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
|
+
)
|
|
130
143
|
header = f"// [TokenSafe Graph-Guided Skeleton for '{symbol}' | Full body + 1-hop dependencies]\n"
|
|
131
144
|
return {"content": [{"type": "text", "text": (prefetch_block or "") + header + graph_skel}]}
|
|
132
145
|
except Exception:
|
|
@@ -186,12 +199,30 @@ def handle_tool_call(name: str, args: dict) -> dict:
|
|
|
186
199
|
"total_symbols": len(graph.symbols),
|
|
187
200
|
"god_nodes": god_nodes
|
|
188
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
|
+
)
|
|
189
211
|
return {"content": [{"type": "text", "text": json.dumps(payload, indent=2, ensure_ascii=False)}]}
|
|
190
212
|
|
|
191
213
|
elif name == "get_api_surface":
|
|
192
214
|
target = args.get("project_path") or os.getcwd()
|
|
193
215
|
graph = GraphEngine.get_instance(target)
|
|
194
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
|
+
)
|
|
195
226
|
return {"content": [{"type": "text", "text": surface_md}]}
|
|
196
227
|
|
|
197
228
|
return {"isError": True, "content": [{"type": "text", "text": f"Unknown tool: {name}"}]}
|
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
|
|