anolisa-tokenless 0.7.12 → 0.7.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Agent adapters are available for:
18
18
  - **Hermes Agent plugin** — response compression, TOON encoding, command rewriting (block + suggest), and registered but hard-disabled Tool Ready via Hermes's native plugin system.
19
19
  - **Qoder CLI plugin** — registered but hard-disabled Tool Ready, command rewriting, and response compression via Qoder's native hook system.
20
20
  - **Claude Code plugin** — RTK command rewriting, response/TOON compression, and registered but hard-disabled Tool Ready via Claude Code's official plugin marketplace.
21
- - **Codex plugin** — response compression, TOON encoding, registered but hard-disabled Tool Ready, and command rewriting via Codex's native hook system.
21
+ - **Codex plugin** — RTK command rewriting, environment-failure diagnostics, and registered but hard-disabled Tool Ready via Codex's native hook system.
22
22
  - **OpenCode plugin** — schema/response/TOON compression, registered but hard-disabled Tool Ready, and command rewriting via OpenCode's local plugin API.
23
23
  - **DeepSeek Harness plugin** — native response compression and environment-error attribution through DSH's `tools/post-execute` seam.
24
24
 
@@ -40,7 +40,7 @@ cover schema compression, RTK rewriting, response compression, TOON, retrieval,
40
40
  | Hermes Agent plugin | — | Tool Ready ⛔ hard-disabled, Command rewriting ✅, Response compression ✅, TOON ✅, Schema compression ⏳ |
41
41
  | Qoder CLI plugin | — | Tool Ready ⛔ hard-disabled, Command rewriting ✅, Response compression ✅ |
42
42
  | Claude Code plugin | — | Tool Ready ⛔ hard-disabled, Command rewriting ✅, Response compression ✅, TOON ✅ |
43
- | Codex plugin | — | Tool Ready ⛔ hard-disabled, Command rewriting ✅, Response compression ✅, TOON |
43
+ | Codex plugin | — | Tool Ready ⛔ hard-disabled, Command rewriting ✅, Environment diagnostics ✅, Response compression protocol-blocked |
44
44
  | OpenCode plugin | — | Tool Ready ⛔ hard-disabled, Command rewriting ✅, Schema compression ✅, Response compression ✅, TOON ✅ |
45
45
  | DeepSeek Harness plugin | — | Response compression ✅, Environment-error attribution ✅ |
46
46
  | AgentScope framework integration | — | Schema ✅, RTK ✅, Response ✅, TOON ✅, Retrieval ✅ |
@@ -316,16 +316,18 @@ tokenless retrieve c30ccf5ed1125e0ed871ba8e
316
316
 
317
317
  # Or paste the whole truncation line — the hash is extracted automatically.
318
318
  # (Use the FULL 24-hex hash from your output; the value below is shorthand.)
319
- tokenless retrieve "<... 160 items truncated, retrieve with <<tokenless:c30ccf5ed1125e0ed871ba8e>>"
319
+ tokenless retrieve "<... 160 items truncated, run: tokenless retrieve '<<tokenless:c30ccf5ed1125e0ed871ba8e>>'>"
320
320
  ```
321
321
 
322
322
  ### compress-toon / decompress-toon
323
323
 
324
- Encode JSON to TOON format (or decode back to JSON):
324
+ Encode JSON to TOON format (or decode back to JSON). Payloads shorter than
325
+ 500 characters pass through unchanged by default (the same minimum the
326
+ adapter hooks apply); use `--min-toon-chars 0` to encode them anyway:
325
327
 
326
328
  ```bash
327
- # Encode JSON to TOON
328
- echo '{"name":"Alice","age":30}' | tokenless compress-toon
329
+ # Encode JSON to TOON (short payload, gate disabled for this call)
330
+ echo '{"name":"Alice","age":30}' | tokenless compress-toon --min-toon-chars 0
329
331
  # name: Alice
330
332
  # age: 30
331
333
 
@@ -571,9 +573,12 @@ The plugin registers hooks at four Codex events, covering four strategies:
571
573
  | Session check | `SessionStart` | Verifies tokenless CLI is installed and functional (non-blocking) | ✅ Active |
572
574
  | Tool Ready | `PreToolUse` | Registered silent pass-through; no check, repair, context, or block | ⛔ Hard-disabled |
573
575
  | Command rewriting | `PreToolUse` | Rewrites shell commands via RTK for token savings | ✅ Active |
574
- | Response compression | `PostToolUse` | Compresses tool responses and encodes to TOON format, injects compressed summary as `additionalContext` | ✅ Active |
576
+ | Environment diagnostics | `PostToolUse` | Adds actionable context only for classified environment failures | ✅ Active |
575
577
 
576
- > **Codex Protocol Constraint**: PostToolUse hooks cannot suppress the original tool output. The plugin injects a compressed *summary* as `additionalContext` — the model sees both the original output and the compressed summary.
578
+ > **Codex protocol constraint**: `PostToolUse` cannot replace or suppress the
579
+ > original tool output. Tokenless therefore does not append compressed content,
580
+ > which would make the model-visible payload larger. First-pass savings for
581
+ > supported shell commands come from RTK rewriting the command before execution.
577
582
 
578
583
  ### Install
579
584
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenless",
3
- "version": "0.7.12",
3
+ "version": "0.7.14",
4
4
  "description": "Token-Less context compression for Claude Code — RTK command rewriting, response/TOON compression, and Tool Ready environment pre-check",
5
5
  "author": { "name": "ANOLISA" },
6
6
  "license": "Apache-2.0",
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "tokenless",
3
- "version": "0.7.12",
4
- "description": "Intelligent tool response compression, environment error detection, and token optimization. Strips noise (debug fields, nulls, empty values), truncates long strings/arrays, applies TOON encoding for JSON responses, and classifies environment errors with actionable fix hints.",
3
+ "version": "0.7.14",
4
+ "description": "Shell command output reduction and environment error diagnostics for Codex. Rewrites supported commands through RTK before execution and classifies environment failures with actionable fix hints.",
5
5
  "keywords": [
6
- "compression",
6
+ "rtk",
7
+ "shell",
7
8
  "token",
8
9
  "context",
9
10
  "optimization",
@@ -13,7 +14,7 @@
13
14
  "hooks": "./hooks/hooks.json",
14
15
  "interface": {
15
16
  "displayName": "Tokenless",
16
- "shortDescription": "Compress tool responses and detect environment errors to save tokens",
17
+ "shortDescription": "Reduce shell output with RTK and diagnose environment errors",
17
18
  "category": "developer-tools",
18
19
  "capabilities": ["hook"]
19
20
  }
@@ -1,18 +1,17 @@
1
1
  # Tokenless Plugin for Codex
2
2
 
3
- Intelligent tool response compression and environment error detection plugin for
4
- [Codex](https://github.com/openai/codex). Reduces token consumption by stripping
5
- noise, truncating verbose output, and classifying environment errors with
6
- actionable fix hints.
3
+ Command-output reduction and environment error detection plugin for
4
+ [Codex](https://github.com/openai/codex). It rewrites supported shell commands
5
+ through RTK before execution and classifies environment errors with actionable
6
+ fix hints.
7
7
 
8
8
  ## Features
9
9
 
10
10
  | Feature | Description |
11
11
  |---------|-------------|
12
- | **Response Compression** | Strips debug fields, nulls, empty values; truncates long strings (512 chars) and arrays (16 items); limits depth to 8 levels |
13
- | **TOON Encoding** | Further compresses valid JSON responses (15-40% additional savings vs. compressed JSON) |
12
+ | **Command Rewriting** | Rewrites supported shell commands through RTK so verbose output is reduced at its source |
14
13
  | **Environment Error Detection** | Classifies tool failures as dependency/permission/file/network/package issues; injects fix hints to prevent retry loops |
15
- | **Statistics Tracking** | Records every compression operation to SQLite for auditing and optimization |
14
+ | **Statistics Tracking** | Attributes RTK rewrites to the Codex session for auditing and optimization |
16
15
 
17
16
  ## How It Works
18
17
 
@@ -28,17 +27,14 @@ The plugin registers four hooks with Codex:
28
27
  - Rewrites commands via `rtk rewrite` for token optimization
29
28
  - Only applies to Bash/Shell/terminal/programmatic tools
30
29
  4. **`PostToolUse`** — runs after every tool execution:
31
- - Skips content-reading tools (Read, Glob) and small responses (< 500 chars)
30
+ - Skips content-reading and task-management tools
32
31
  - Classifies environment errors and injects fix hints
33
- - Compresses large JSON responses via `tokenless compress-response`
34
- - Applies TOON encoding for additional savings
35
- - Injects a compressed summary as `additionalContext`
36
32
 
37
- > **Codex Protocol Constraint**: PostToolUse hooks cannot suppress the original
38
- > tool output (`suppressOutput` is rejected). The plugin therefore injects a
39
- > compressed *summary* as `additionalContext` the model sees both the original
40
- > output and the compressed summary, and can use the summary for efficient
41
- > processing of large outputs.
33
+ > **Codex protocol constraint**: `PostToolUse` rejects output suppression and
34
+ > replacement. Injecting compressed content through `additionalContext` would
35
+ > leave the original in place and increase the prompt. The plugin therefore
36
+ > reserves `additionalContext` for actionable environment diagnostics. Actual
37
+ > first-pass savings come from RTK rewriting supported commands before they run.
42
38
 
43
39
  ## Installation
44
40
 
@@ -77,15 +73,12 @@ Or install from the marketplace (once published).
77
73
 
78
74
  ## Hook Output Format
79
75
 
80
- The plugin injects `additionalContext` in the following format:
76
+ For a classified environment failure, the plugin injects `additionalContext`
77
+ in the following format:
81
78
 
82
79
  ```
83
- [tokenless:compressed] Bash: 45,230 → 2,100 chars (95% reduction)
84
80
  [tokenless:env:ENV_DEPENDENCY_MISSING] Missing dependency detected. ...
85
81
  Do NOT retry the same command — fix the environment first.
86
- --- compressed content ---
87
- {... compressed JSON or TOON content ...}
88
- --- end compressed content ---
89
82
  ```
90
83
 
91
84
  ## Configuration
@@ -114,21 +107,18 @@ tokenless stats list --limit 20
114
107
  tokenless stats show <id>
115
108
  ```
116
109
 
117
- ## Compression Pipeline
110
+ ## Savings Path
118
111
 
119
112
  ```
120
- Raw tool_response (JSON)
113
+ Shell command
121
114
 
122
- ├─ Step 1: ResponseCompressor
123
- ├─ Drop: debug, trace, stack, stacktrace, logs, logging fields
124
- │ ├─ Truncate: strings > 512 chars, arrays > 16 items
125
- │ ├─ Drop: null values, empty strings, empty arrays, empty objects
126
- │ └─ Limit: max depth 8 levels
115
+ ├─ PreToolUse: rtk rewrite
116
+ └─ Replaces the command with an output-reducing equivalent
127
117
 
128
- ├─ Step 2: TOON Encoding (if result is still valid JSON)
129
- │ └─ Binary-to-TOON format encoding (~15-40% additional savings)
118
+ ├─ Tool execution
119
+ │ └─ Codex receives the already-reduced output
130
120
 
131
- └─ Guard: output only if smaller than input (safety check)
121
+ └─ PostToolUse: environment diagnostics only
132
122
  ```
133
123
 
134
124
  ## Architecture
@@ -139,7 +129,7 @@ codex-plugin-tokenless/
139
129
  ├── hooks/
140
130
  │ └── hooks.json # Hook definitions (SessionStart, PreToolUse, PostToolUse)
141
131
  ├── scripts/
142
- │ ├── compress-response # PostToolUse: response compression + env error detection
132
+ │ ├── response-diagnostics # PostToolUse: environment error detection
143
133
  │ ├── rewrite-hook # PreToolUse: RTK command rewriting
144
134
  │ ├── tool-ready # PreToolUse: registered hard-disabled pass-through
145
135
  │ ├── check-tokenless # SessionStart: version/availability check
@@ -29,9 +29,9 @@
29
29
  "hooks": [
30
30
  {
31
31
  "type": "command",
32
- "command": "${PLUGIN_ROOT}/scripts/compress-response",
33
- "timeout": 15,
34
- "statusMessage": "Compressing tool response..."
32
+ "command": "${PLUGIN_ROOT}/scripts/response-diagnostics",
33
+ "timeout": 10,
34
+ "statusMessage": "Checking tool response..."
35
35
  }
36
36
  ]
37
37
  }
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env python3
2
+ """Codex PostToolUse environment diagnostics for the Tokenless plugin.
3
+
4
+ Codex does not let PostToolUse hooks replace or suppress the original tool
5
+ response. Adding compressed content through ``additionalContext`` therefore
6
+ increases the model-visible payload instead of reducing it. This hook only
7
+ adds actionable context for classified environment failures; successful and
8
+ unclassified responses pass through unchanged.
9
+
10
+ Protocol:
11
+ stdin <- Codex PostToolUse JSON (tool_name, tool_response, ...)
12
+ stdout -> {"hookSpecificOutput": {"hookEventName": "PostToolUse",
13
+ "additionalContext": "..."}}
14
+
15
+ Fail-open: every error path exits successfully with empty stdout so a
16
+ diagnostic failure never blocks the agent session.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import os
23
+ import sys
24
+
25
+ # Resolve shared hook utilities (common/hooks/) with FHS fallback paths.
26
+ # Primary: relative path (works for source-tree and FHS/RPM install).
27
+ # Fallbacks: system and user FHS paths (works when Codex caches only the
28
+ # plugin subdirectory and the relative path resolves outside the cache).
29
+ #
30
+ # Trust model (aligned with bash is_trusted_file / Rust is_trusted_path):
31
+ # - System FHS paths (/usr/share, /usr/local/share) are unconditional.
32
+ # - Other paths must be owned by the current user or root, and their parent
33
+ # directory must not be world-writable.
34
+ _HERE = os.path.dirname(os.path.abspath(__file__))
35
+
36
+
37
+ def _is_trusted_dir(path: str) -> bool:
38
+ """Check whether a directory is trusted for Python module imports."""
39
+ for prefix in (
40
+ "/usr/share/",
41
+ "/usr/local/share/",
42
+ "/usr/libexec/",
43
+ "/usr/lib/anolisa/",
44
+ ):
45
+ if path.startswith(prefix):
46
+ return True
47
+
48
+ try:
49
+ stat = os.stat(path)
50
+ parent_stat = os.stat(os.path.dirname(path))
51
+ except OSError:
52
+ return False
53
+ if stat.st_uid not in (os.getuid(), 0):
54
+ return False
55
+ if parent_stat.st_uid not in (os.getuid(), 0):
56
+ return False
57
+ return not parent_stat.st_mode & 0o002
58
+
59
+
60
+ try:
61
+ import pwd as _pwd
62
+
63
+ _REAL_HOME = _pwd.getpwuid(os.getuid()).pw_dir
64
+ except (ImportError, KeyError):
65
+ _REAL_HOME = ""
66
+ if not os.path.isabs(_REAL_HOME):
67
+ _REAL_HOME = ""
68
+
69
+ _HOOK_UTILS_CANDIDATES = [
70
+ os.path.join(_HERE, "..", "..", "common", "hooks"),
71
+ "/usr/share/anolisa/adapters/tokenless/common/hooks",
72
+ "/usr/local/share/anolisa/adapters/tokenless/common/hooks",
73
+ (
74
+ os.path.join(
75
+ _REAL_HOME,
76
+ ".local",
77
+ "share",
78
+ "anolisa",
79
+ "adapters",
80
+ "tokenless",
81
+ "common",
82
+ "hooks",
83
+ )
84
+ if _REAL_HOME
85
+ else ""
86
+ ),
87
+ ]
88
+ _HOOK_UTILS_RESOLVED = ""
89
+ for _candidate in _HOOK_UTILS_CANDIDATES:
90
+ resolved = os.path.realpath(_candidate) if _candidate else ""
91
+ if resolved and _is_trusted_dir(resolved) and os.path.isdir(resolved):
92
+ _HOOK_UTILS_RESOLVED = resolved
93
+ sys.path.insert(0, resolved)
94
+ break
95
+
96
+ if _HOOK_UTILS_RESOLVED:
97
+ from hook_utils import ( # noqa: E402
98
+ SKIP_TOOLS as _SKIP_TOOLS_SHARED,
99
+ classify_env_error as _classify_env_error,
100
+ skip_silent as _skip,
101
+ )
102
+ else:
103
+
104
+ def _skip() -> None: # type: ignore[assignment,misc]
105
+ sys.exit(0)
106
+
107
+ def _classify_env_error( # type: ignore[assignment,misc]
108
+ tool_response: object,
109
+ ) -> tuple[str | None, str | None]:
110
+ return None, None
111
+
112
+ _SKIP_TOOLS_SHARED: set[str] = { # type: ignore[assignment,misc]
113
+ "Read",
114
+ "read",
115
+ "read_file",
116
+ "read_many_files",
117
+ "Glob",
118
+ "glob",
119
+ "list_directory",
120
+ "Grep",
121
+ "grep",
122
+ "grep_search",
123
+ "search_files",
124
+ "Lsp",
125
+ "lsp",
126
+ "NotebookRead",
127
+ "notebook_read",
128
+ "notebookread",
129
+ }
130
+
131
+
132
+ SKIP_TOOLS: set[str] = _SKIP_TOOLS_SHARED | {
133
+ "TodoWrite",
134
+ "Task",
135
+ "TaskStatus",
136
+ }
137
+
138
+
139
+ def main() -> None:
140
+ try:
141
+ raw = sys.stdin.read()
142
+ if not raw.strip():
143
+ _skip()
144
+ input_data = json.loads(raw)
145
+ except (json.JSONDecodeError, EOFError, ValueError):
146
+ _skip()
147
+
148
+ tool_name = input_data.get("tool_name", "")
149
+ tool_response = input_data.get("tool_response")
150
+ if tool_name in SKIP_TOOLS or tool_response is None:
151
+ _skip()
152
+
153
+ env_category, env_hint = _classify_env_error(tool_response)
154
+ if not env_category or not env_hint:
155
+ _skip()
156
+
157
+ response = {
158
+ "hookSpecificOutput": {
159
+ "hookEventName": "PostToolUse",
160
+ "additionalContext": (
161
+ f"[tokenless:env:{env_category}] {env_hint} "
162
+ "Do NOT retry the same command — fix the environment first."
163
+ ),
164
+ }
165
+ }
166
+ print(json.dumps(response, ensure_ascii=False))
167
+
168
+
169
+ if __name__ == "__main__":
170
+ main()
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenless",
3
- "version": "0.7.12",
3
+ "version": "0.7.14",
4
4
  "contextFileName": "COPILOT.md",
5
5
  "hooks": {
6
6
  "PreToolUse": [