anolisa-tokenless 0.7.7
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/LICENSE +190 -0
- package/README.md +770 -0
- package/adapters/tokenless/claude-code/.claude-plugin/marketplace.json +15 -0
- package/adapters/tokenless/claude-code/.claude-plugin/plugin.json +9 -0
- package/adapters/tokenless/claude-code/hooks/hooks.json +38 -0
- package/adapters/tokenless/claude-code/scripts/detect.sh +193 -0
- package/adapters/tokenless/claude-code/scripts/install.sh +92 -0
- package/adapters/tokenless/claude-code/scripts/uninstall.sh +50 -0
- package/adapters/tokenless/codex/.codex-plugin/plugin.json +20 -0
- package/adapters/tokenless/codex/README.md +160 -0
- package/adapters/tokenless/codex/hooks/hooks.json +52 -0
- package/adapters/tokenless/codex/scripts/_common.sh +25 -0
- package/adapters/tokenless/codex/scripts/check-tokenless +94 -0
- package/adapters/tokenless/codex/scripts/compress-response +441 -0
- package/adapters/tokenless/codex/scripts/detect.sh +61 -0
- package/adapters/tokenless/codex/scripts/install.sh +151 -0
- package/adapters/tokenless/codex/scripts/rewrite-hook +282 -0
- package/adapters/tokenless/codex/scripts/tool-ready +303 -0
- package/adapters/tokenless/codex/scripts/uninstall.sh +78 -0
- package/adapters/tokenless/common/commands/tokenless-stats.toml +2 -0
- package/adapters/tokenless/common/cosh-extension.json +65 -0
- package/adapters/tokenless/common/hooks/compress_response_hook.py +487 -0
- package/adapters/tokenless/common/hooks/compress_schema_hook.py +144 -0
- package/adapters/tokenless/common/hooks/compress_toon_hook.py +160 -0
- package/adapters/tokenless/common/hooks/hook_utils.py +584 -0
- package/adapters/tokenless/common/hooks/rewrite_hook.py +223 -0
- package/adapters/tokenless/common/hooks/run-hook.sh +62 -0
- package/adapters/tokenless/common/hooks/tool_categories.json +99 -0
- package/adapters/tokenless/common/hooks/tool_ready_hook.sh +571 -0
- package/adapters/tokenless/common/tokenless-env-fix.sh +730 -0
- package/adapters/tokenless/common/tool-ready-spec.json +113 -0
- package/adapters/tokenless/dsh/cordis.patch.yml +8 -0
- package/adapters/tokenless/dsh/dist/index.js +399 -0
- package/adapters/tokenless/dsh/package.json +26 -0
- package/adapters/tokenless/hermes/__init__.py +572 -0
- package/adapters/tokenless/hermes/plugin.yaml +9 -0
- package/adapters/tokenless/hermes/scripts/detect.sh +80 -0
- package/adapters/tokenless/hermes/scripts/install.sh +60 -0
- package/adapters/tokenless/hermes/scripts/uninstall.sh +45 -0
- package/adapters/tokenless/manifest.json +147 -0
- package/adapters/tokenless/openclaw/dist/index.d.ts +27 -0
- package/adapters/tokenless/openclaw/dist/index.js +598 -0
- package/adapters/tokenless/openclaw/dist/tool_categories.json +99 -0
- package/adapters/tokenless/openclaw/index.ts +720 -0
- package/adapters/tokenless/openclaw/openclaw.plugin.json +40 -0
- package/adapters/tokenless/openclaw/package.json +24 -0
- package/adapters/tokenless/openclaw/scripts/detect.sh +102 -0
- package/adapters/tokenless/openclaw/scripts/install.sh +75 -0
- package/adapters/tokenless/openclaw/scripts/uninstall.sh +46 -0
- package/adapters/tokenless/openclaw/tsconfig.json +13 -0
- package/adapters/tokenless/opencode/plugin.js +246 -0
- package/adapters/tokenless/opencode/scripts/detect.sh +38 -0
- package/adapters/tokenless/opencode/scripts/install.sh +56 -0
- package/adapters/tokenless/opencode/scripts/uninstall.sh +29 -0
- package/adapters/tokenless/qoder/.qoder-plugin/plugin.json +11 -0
- package/adapters/tokenless/qoder/commands/tokenless-stats.md +8 -0
- package/adapters/tokenless/qoder/hooks/hooks.json +36 -0
- package/adapters/tokenless/qoder/hooks/run-hook.sh +51 -0
- package/adapters/tokenless/qoder/scripts/detect.sh +35 -0
- package/adapters/tokenless/qoder/scripts/install.sh +136 -0
- package/adapters/tokenless/qoder/scripts/uninstall.sh +106 -0
- package/adapters/tokenless/qwencode/qwen-extension.json +69 -0
- package/adapters/tokenless/qwencode/scripts/detect.sh +125 -0
- package/adapters/tokenless/qwencode/scripts/install.sh +128 -0
- package/adapters/tokenless/qwencode/scripts/uninstall.sh +62 -0
- package/bin/rtk +6 -0
- package/bin/tokenless +6 -0
- package/bin/toon +6 -0
- package/package.json +57 -0
- package/scripts/postinstall.js +208 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Codex PreToolUse Tool Ready adapter, currently a silent pass-through.
|
|
3
|
+
|
|
4
|
+
Release builds of ``tokenless env-check`` return ``UNKNOWN`` with
|
|
5
|
+
``enabled:false``, which this adapter silently passes through. The retained
|
|
6
|
+
legacy pipeline below remains dormant until the readiness model is redesigned
|
|
7
|
+
and intentionally re-enabled in source.
|
|
8
|
+
|
|
9
|
+
Legacy pipeline: Check → Fix → Re-check
|
|
10
|
+
1. CHECK: ``tokenless env-check --json --tool <name>``
|
|
11
|
+
2. FIX: ``tokenless env-check --fix --json --tool <name>``
|
|
12
|
+
3. RE-CHECK: parse result after fix attempt
|
|
13
|
+
|
|
14
|
+
Protocol:
|
|
15
|
+
stdin ← codex PreToolUse JSON (tool_name, ...)
|
|
16
|
+
stdout → {"hookSpecificOutput": {"hookEventName": "PreToolUse",
|
|
17
|
+
"permissionDecision": "allow"|"deny",
|
|
18
|
+
"additionalContext": "..."}}
|
|
19
|
+
|
|
20
|
+
Status mapping (from tokenless env-check --json):
|
|
21
|
+
READY → passthrough (empty stdout)
|
|
22
|
+
PARTIAL → allow + additionalContext warning
|
|
23
|
+
NOT_READY → auto-fix → re-check → deny if still failing
|
|
24
|
+
UNKNOWN → passthrough (fail-open)
|
|
25
|
+
|
|
26
|
+
Fail-open: every error path silently passes through (exit 0 with empty
|
|
27
|
+
stdout) so the agent session is never blocked by a hook failure.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
import shutil
|
|
33
|
+
import subprocess
|
|
34
|
+
import sys
|
|
35
|
+
from typing import Optional
|
|
36
|
+
|
|
37
|
+
# Resolve shared hook utilities (common/hooks/) with FHS fallback paths.
|
|
38
|
+
# Primary: relative path (works for source-tree and FHS/RPM install).
|
|
39
|
+
# Fallbacks: system and user FHS paths (works when Codex caches only the
|
|
40
|
+
# plugin subdirectory and the relative path resolves outside the cache).
|
|
41
|
+
#
|
|
42
|
+
# Trust model (aligned with bash is_trusted_file / Rust is_trusted_path):
|
|
43
|
+
# - System FHS paths (/usr/share, /usr/local/share) are unconditional.
|
|
44
|
+
# - User-relative paths use passwd DB home (NOT $HOME — env-controllable),
|
|
45
|
+
# with ownership and parent-dir permission checks.
|
|
46
|
+
# - Relative ../../common/hooks is trusted only if the resolved target
|
|
47
|
+
# exists and is under a system prefix or a passwd-validated home path.
|
|
48
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _is_trusted_dir(path: str) -> bool:
|
|
52
|
+
"""Check whether a directory is trusted for Python module imports.
|
|
53
|
+
|
|
54
|
+
Mirrors the trust criteria in bash tool_ready_hook.sh (is_trusted_file)
|
|
55
|
+
and Rust env_check.rs (is_trusted_path): system paths unconditional,
|
|
56
|
+
user paths validated via passwd + ownership + parent permission checks.
|
|
57
|
+
|
|
58
|
+
NOTE: Callers should pass realpath-resolved paths (not just abspath)
|
|
59
|
+
so that symlink targets are validated before the trust check — a
|
|
60
|
+
symlinked /usr/share/anolisa → /tmp/ would bypass the prefix check
|
|
61
|
+
if only abspath is used.
|
|
62
|
+
"""
|
|
63
|
+
# System FHS prefixes are always trusted
|
|
64
|
+
for prefix in ("/usr/share/", "/usr/local/share/", "/usr/libexec/", "/usr/lib/anolisa/"):
|
|
65
|
+
if path.startswith(prefix):
|
|
66
|
+
return True
|
|
67
|
+
# For paths outside system prefixes (e.g. source-tree cloned to
|
|
68
|
+
# /opt/anolisa/), trust them if owned by current uid or root AND
|
|
69
|
+
# parent directory is not world-writable. This covers developer
|
|
70
|
+
# worktrees without requiring the path to be under passwd home.
|
|
71
|
+
try:
|
|
72
|
+
st = os.stat(path)
|
|
73
|
+
except OSError:
|
|
74
|
+
return False
|
|
75
|
+
if st.st_uid != os.getuid() and st.st_uid != 0:
|
|
76
|
+
# Not owned by us or root — refuse. Home paths must be owned by
|
|
77
|
+
# us or root; source-tree paths outside system prefixes are
|
|
78
|
+
# already covered by the ownership check above (current uid/root).
|
|
79
|
+
return False
|
|
80
|
+
# Ownership OK — check parent directory is not world-writable
|
|
81
|
+
parent = os.path.dirname(path)
|
|
82
|
+
try:
|
|
83
|
+
pst = os.stat(parent)
|
|
84
|
+
except OSError:
|
|
85
|
+
return False
|
|
86
|
+
if pst.st_uid != os.getuid() and pst.st_uid != 0:
|
|
87
|
+
return False
|
|
88
|
+
if pst.st_mode & 0o002: # world-writable
|
|
89
|
+
return False
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
# Resolve real home from passwd DB for user-install fallback path.
|
|
93
|
+
# Falls back to "" (empty) when unavailable — candidate is then skipped.
|
|
94
|
+
try:
|
|
95
|
+
import pwd as _pwd
|
|
96
|
+
_REAL_HOME = _pwd.getpwuid(os.getuid()).pw_dir
|
|
97
|
+
except (ImportError, KeyError):
|
|
98
|
+
_REAL_HOME = ""
|
|
99
|
+
if not os.path.isabs(_REAL_HOME):
|
|
100
|
+
_REAL_HOME = ""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _user_path(*parts: str) -> str:
|
|
104
|
+
return os.path.join(_REAL_HOME, *parts) if _REAL_HOME else ""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
_HOOK_UTILS_CANDIDATES = [
|
|
108
|
+
os.path.join(_HERE, "..", "..", "common", "hooks"), # source-tree / FHS
|
|
109
|
+
"/usr/share/anolisa/adapters/tokenless/common/hooks", # RPM system
|
|
110
|
+
"/usr/local/share/anolisa/adapters/tokenless/common/hooks", # manual system
|
|
111
|
+
os.path.join(_REAL_HOME, ".local", "share",
|
|
112
|
+
"anolisa", "adapters", "tokenless", "common", "hooks") if _REAL_HOME else "",
|
|
113
|
+
]
|
|
114
|
+
_HOOK_UTILS_RESOLVED = ""
|
|
115
|
+
for _C in _HOOK_UTILS_CANDIDATES:
|
|
116
|
+
if _C and _is_trusted_dir(os.path.realpath(_C)) and os.path.isdir(_C):
|
|
117
|
+
_HOOK_UTILS_RESOLVED = os.path.realpath(_C)
|
|
118
|
+
sys.path.insert(0, _C)
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
if _HOOK_UTILS_RESOLVED:
|
|
122
|
+
from hook_utils import skip_silent as _skip # noqa: E402
|
|
123
|
+
else:
|
|
124
|
+
# Fail-open: no trusted hook_utils found → provide inline fallbacks
|
|
125
|
+
# so the hook script exits 0 (passthrough) rather than ImportError → exit 1.
|
|
126
|
+
def _skip() -> None: # type: ignore[assignment,misc]
|
|
127
|
+
sys.exit(0)
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# constants
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
AGENT_ID = os.environ.get("TOKENLESS_AGENT_ID", "codex")
|
|
134
|
+
FIX_TIMEOUT = 60 # auto-fix may involve package installs (dnf/pip)
|
|
135
|
+
|
|
136
|
+
# -- tokenless binary search paths (ANOLISA FHS spec) ------------------------
|
|
137
|
+
# Codex can cache this script without common/hooks. Keep this standalone list
|
|
138
|
+
# aligned with hook_utils.py::_known_binary_paths.
|
|
139
|
+
|
|
140
|
+
_TOKENLESS_FALLBACK = "/usr/bin/tokenless"
|
|
141
|
+
_TOKENLESS_SYSTEM_FALLBACK = "/usr/local/bin/tokenless"
|
|
142
|
+
_TOKENLESS_LOCAL_BIN = _user_path(".local", "bin", "tokenless")
|
|
143
|
+
_TOKENLESS_LOCAL_SHARE = _user_path(
|
|
144
|
+
".local", "share", "anolisa", "tokenless", "tokenless"
|
|
145
|
+
)
|
|
146
|
+
_TOKENLESS_LOCAL_LIB = _user_path(
|
|
147
|
+
".local", "lib", "anolisa", "tokenless", "tokenless"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# helpers
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _warn(msg: str) -> None:
|
|
156
|
+
print(f"[tokenless:ready] {msg}", file=sys.stderr)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _find_tokenless() -> Optional[str]:
|
|
160
|
+
explicit = os.environ.get("TOKENLESS_BIN", "")
|
|
161
|
+
if explicit and os.path.isfile(explicit) and os.access(explicit, os.X_OK):
|
|
162
|
+
return explicit
|
|
163
|
+
path = shutil.which("tokenless")
|
|
164
|
+
if path:
|
|
165
|
+
return path
|
|
166
|
+
for fp in (
|
|
167
|
+
_TOKENLESS_LOCAL_BIN,
|
|
168
|
+
_TOKENLESS_SYSTEM_FALLBACK,
|
|
169
|
+
_TOKENLESS_FALLBACK,
|
|
170
|
+
_TOKENLESS_LOCAL_SHARE,
|
|
171
|
+
_TOKENLESS_LOCAL_LIB,
|
|
172
|
+
):
|
|
173
|
+
if os.path.isfile(fp) and os.access(fp, os.X_OK):
|
|
174
|
+
return fp
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _run_env_check(
|
|
179
|
+
tokenless_bin: str, tool_name: str, fix: bool = False
|
|
180
|
+
) -> Optional[dict]:
|
|
181
|
+
"""Run tokenless env-check and return parsed JSON result, or None."""
|
|
182
|
+
cmd = [tokenless_bin, "env-check", "--json", "--tool", tool_name]
|
|
183
|
+
if fix:
|
|
184
|
+
cmd.append("--fix")
|
|
185
|
+
timeout = FIX_TIMEOUT if fix else 10
|
|
186
|
+
try:
|
|
187
|
+
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
|
188
|
+
except Exception:
|
|
189
|
+
return None
|
|
190
|
+
if proc.returncode != 0:
|
|
191
|
+
_warn(f"env-check{' --fix' if fix else ''} exit {proc.returncode}")
|
|
192
|
+
return None
|
|
193
|
+
try:
|
|
194
|
+
return json.loads(proc.stdout.strip())
|
|
195
|
+
except json.JSONDecodeError:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _build_response(
|
|
200
|
+
tool_name: str,
|
|
201
|
+
status: str,
|
|
202
|
+
missing: list[str],
|
|
203
|
+
permission: str,
|
|
204
|
+
) -> str:
|
|
205
|
+
"""Build a codex PreToolUse JSON response and return it as a string."""
|
|
206
|
+
missing_str = ", ".join(missing) if missing else "unknown"
|
|
207
|
+
if status == "PARTIAL":
|
|
208
|
+
ctx = (
|
|
209
|
+
f"[tokenless:ready] {tool_name}: PARTIAL — "
|
|
210
|
+
f"recommended deps not installed: {missing_str}. "
|
|
211
|
+
f"Core tool is functional, extended features may be unavailable."
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
ctx = (
|
|
215
|
+
f"[tokenless:ready] {tool_name}: NOT_READY — "
|
|
216
|
+
f"auto-fix attempted but dependencies still missing: {missing_str}. "
|
|
217
|
+
f"Do NOT retry the same tool — fix the environment first."
|
|
218
|
+
)
|
|
219
|
+
response = {
|
|
220
|
+
"hookSpecificOutput": {
|
|
221
|
+
"hookEventName": "PreToolUse",
|
|
222
|
+
"permissionDecision": permission,
|
|
223
|
+
"additionalContext": ctx,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return json.dumps(response, ensure_ascii=False)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# ---------------------------------------------------------------------------
|
|
230
|
+
# main
|
|
231
|
+
# ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def main() -> None:
|
|
235
|
+
# 1. Locate tokenless binary
|
|
236
|
+
tokenless_bin = _find_tokenless()
|
|
237
|
+
if not tokenless_bin:
|
|
238
|
+
_skip()
|
|
239
|
+
|
|
240
|
+
# 2. Read stdin JSON
|
|
241
|
+
try:
|
|
242
|
+
raw = sys.stdin.read()
|
|
243
|
+
if not raw.strip():
|
|
244
|
+
_skip()
|
|
245
|
+
input_data = json.loads(raw)
|
|
246
|
+
except (json.JSONDecodeError, EOFError, ValueError):
|
|
247
|
+
_skip()
|
|
248
|
+
|
|
249
|
+
# 3. Extract tool_name
|
|
250
|
+
tool_name = input_data.get("tool_name", "")
|
|
251
|
+
if not tool_name:
|
|
252
|
+
_skip()
|
|
253
|
+
|
|
254
|
+
# === Phase 1: CHECK ===
|
|
255
|
+
result = _run_env_check(tokenless_bin, tool_name, fix=False)
|
|
256
|
+
if result is None:
|
|
257
|
+
_skip()
|
|
258
|
+
|
|
259
|
+
status = result.get("status", "UNKNOWN")
|
|
260
|
+
missing = result.get("missing", [])
|
|
261
|
+
|
|
262
|
+
if status == "READY":
|
|
263
|
+
_skip()
|
|
264
|
+
|
|
265
|
+
if status == "UNKNOWN":
|
|
266
|
+
_skip()
|
|
267
|
+
|
|
268
|
+
if status == "PARTIAL":
|
|
269
|
+
print(_build_response(tool_name, status, missing, "allow"))
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
# === Phase 2: FIX (auto-install missing required dependencies) ===
|
|
273
|
+
_warn(f"{tool_name}: {status}, attempting auto-fix"
|
|
274
|
+
+ (f" (missing: {', '.join(missing)})" if missing else ""))
|
|
275
|
+
|
|
276
|
+
fix_result = _run_env_check(tokenless_bin, tool_name, fix=True)
|
|
277
|
+
if fix_result is None:
|
|
278
|
+
# Fix command itself failed — report what we know from check
|
|
279
|
+
if status == "PARTIAL":
|
|
280
|
+
print(_build_response(tool_name, status, missing, "allow"))
|
|
281
|
+
return
|
|
282
|
+
print(_build_response(tool_name, status, missing, "deny"))
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
fix_status = fix_result.get("status", "UNKNOWN")
|
|
286
|
+
|
|
287
|
+
# === Phase 3: RE-CHECK ===
|
|
288
|
+
if fix_status == "READY":
|
|
289
|
+
_warn(f"{tool_name}: auto-fix succeeded, tool is now READY")
|
|
290
|
+
_skip()
|
|
291
|
+
|
|
292
|
+
# Fix didn't fully resolve — report remaining issues
|
|
293
|
+
fix_missing = fix_result.get("missing", [])
|
|
294
|
+
if fix_status == "PARTIAL":
|
|
295
|
+
print(_build_response(tool_name, fix_status, fix_missing, "allow"))
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
# Still NOT_READY after fix
|
|
299
|
+
print(_build_response(tool_name, fix_status, fix_missing, "deny"))
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
if __name__ == "__main__":
|
|
303
|
+
main()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Uninstall the tokenless Codex plugin and clean up plugin data.
|
|
3
|
+
#
|
|
4
|
+
# Removes:
|
|
5
|
+
# - Codex plugin registration (codex plugin remove)
|
|
6
|
+
# - Codex marketplace entry (codex plugin marketplace remove)
|
|
7
|
+
# - Marketplace symlink directory
|
|
8
|
+
# - The tokenless binary from the install prefix
|
|
9
|
+
#
|
|
10
|
+
# NOTE: This script does NOT remove $HOME/.tokenless/ — that directory
|
|
11
|
+
# holds the SQLite stats DB and rewrite context shared by every tokenless
|
|
12
|
+
# adapter (cosh, qoder, hermes, openclaw, claude-code). Removing it here
|
|
13
|
+
# would destroy data belonging to plugins still installed.
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# ./uninstall.sh # Interactive (asks confirmation)
|
|
17
|
+
# ./uninstall.sh --non-interactive # CI / automated (no confirmation)
|
|
18
|
+
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
|
|
21
|
+
INTERACTIVE=1
|
|
22
|
+
if [[ "${1:-}" == "--non-interactive" ]]; then
|
|
23
|
+
INTERACTIVE=0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
PREFIX="${TOKENLESS_INSTALL_PREFIX:-$HOME/.local}"
|
|
27
|
+
BINDIR="$PREFIX/bin"
|
|
28
|
+
CODEX_BIN="${CODEX_BIN:-}"
|
|
29
|
+
MARKETPLACE_NAME="anolisa-tokenless"
|
|
30
|
+
MARKETPLACE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/anolisa/codex-marketplace"
|
|
31
|
+
|
|
32
|
+
# Resolve codex binary via shared helper (see _common.sh).
|
|
33
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
34
|
+
# shellcheck source=./_common.sh
|
|
35
|
+
source "$SCRIPT_DIR/_common.sh"
|
|
36
|
+
|
|
37
|
+
echo "[tokenless] Codex plugin uninstall"
|
|
38
|
+
|
|
39
|
+
# 1. Remove codex plugin registration
|
|
40
|
+
CODEX_BIN="$(resolve_codex)"
|
|
41
|
+
if [[ -n "$CODEX_BIN" ]]; then
|
|
42
|
+
if "$CODEX_BIN" plugin list 2>/dev/null | grep -q "tokenless"; then
|
|
43
|
+
echo "[tokenless] Removing codex plugin 'tokenless@${MARKETPLACE_NAME}'..."
|
|
44
|
+
"$CODEX_BIN" plugin remove "tokenless@${MARKETPLACE_NAME}" 2>&1 || true
|
|
45
|
+
fi
|
|
46
|
+
if "$CODEX_BIN" plugin marketplace list 2>/dev/null | grep -q "^${MARKETPLACE_NAME}[[:space:]]"; then
|
|
47
|
+
echo "[tokenless] Removing marketplace '${MARKETPLACE_NAME}'..."
|
|
48
|
+
"$CODEX_BIN" plugin marketplace remove "$MARKETPLACE_NAME" 2>&1 || true
|
|
49
|
+
fi
|
|
50
|
+
else
|
|
51
|
+
echo "[tokenless] codex CLI not found, skipping plugin unregistration."
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# 2. Remove marketplace symlink directory
|
|
55
|
+
if [[ -d "$MARKETPLACE_DIR" ]]; then
|
|
56
|
+
rm -rf "$MARKETPLACE_DIR"
|
|
57
|
+
echo "[tokenless] Removed marketplace directory: $MARKETPLACE_DIR"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# 3. Remove binary
|
|
61
|
+
if [[ -f "$BINDIR/tokenless" ]]; then
|
|
62
|
+
if [[ $INTERACTIVE -eq 1 ]]; then
|
|
63
|
+
read -rp "Remove $BINDIR/tokenless? [y/N] " answer
|
|
64
|
+
if [[ "$answer" =~ ^[Yy]$ ]]; then
|
|
65
|
+
rm -f "$BINDIR/tokenless"
|
|
66
|
+
echo "[tokenless] Removed: $BINDIR/tokenless"
|
|
67
|
+
else
|
|
68
|
+
echo "[tokenless] Skipped: $BINDIR/tokenless"
|
|
69
|
+
fi
|
|
70
|
+
else
|
|
71
|
+
rm -f "$BINDIR/tokenless"
|
|
72
|
+
echo "[tokenless] Removed: $BINDIR/tokenless"
|
|
73
|
+
fi
|
|
74
|
+
else
|
|
75
|
+
echo "[tokenless] Binary not found: $BINDIR/tokenless"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
echo "[tokenless] Uninstall complete."
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tokenless",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"contextFileName": "COPILOT.md",
|
|
5
|
+
"hooks": {
|
|
6
|
+
"PreToolUse": [
|
|
7
|
+
{
|
|
8
|
+
"matcher": "",
|
|
9
|
+
"sequential": true,
|
|
10
|
+
"hooks": [
|
|
11
|
+
{
|
|
12
|
+
"type": "command",
|
|
13
|
+
"name": "tokenless-tool-ready",
|
|
14
|
+
"description": "Registered hard-disabled Tool Ready pass-through; performs no check, repair, or block",
|
|
15
|
+
"command": "bash ${extensionPath}/hooks/tool_ready_hook.sh",
|
|
16
|
+
"timeout": 10000,
|
|
17
|
+
"fail_open": true,
|
|
18
|
+
"env": { "TOKENLESS_AGENT_ID": "copilot-shell" }
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"matcher": "^(Bash|run_shell_command|terminal|Shell|exec|process)$",
|
|
24
|
+
"hooks": [
|
|
25
|
+
{
|
|
26
|
+
"type": "command",
|
|
27
|
+
"name": "tokenless-rewrite",
|
|
28
|
+
"description": "Rewrites shell commands via rtk for token savings",
|
|
29
|
+
"command": "python3 ${extensionPath}/hooks/rewrite_hook.py",
|
|
30
|
+
"timeout": 5000,
|
|
31
|
+
"env": { "TOKENLESS_AGENT_ID": "copilot-shell" }
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"PostToolUse": [
|
|
37
|
+
{
|
|
38
|
+
"hooks": [
|
|
39
|
+
{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"name": "tokenless-compress-response",
|
|
42
|
+
"description": "Compresses tool responses and encodes to TOON format",
|
|
43
|
+
"command": "python3 ${extensionPath}/hooks/compress_response_hook.py",
|
|
44
|
+
"timeout": 10000,
|
|
45
|
+
"env": { "TOKENLESS_AGENT_ID": "copilot-shell" }
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"BeforeModel": [
|
|
51
|
+
{
|
|
52
|
+
"hooks": [
|
|
53
|
+
{
|
|
54
|
+
"type": "command",
|
|
55
|
+
"name": "tokenless-compress-schema",
|
|
56
|
+
"description": "Compresses tool schema definitions for token savings",
|
|
57
|
+
"command": "python3 ${extensionPath}/hooks/compress_schema_hook.py",
|
|
58
|
+
"timeout": 10000,
|
|
59
|
+
"env": { "TOKENLESS_AGENT_ID": "copilot-shell" }
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|