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.
Files changed (70) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +770 -0
  3. package/adapters/tokenless/claude-code/.claude-plugin/marketplace.json +15 -0
  4. package/adapters/tokenless/claude-code/.claude-plugin/plugin.json +9 -0
  5. package/adapters/tokenless/claude-code/hooks/hooks.json +38 -0
  6. package/adapters/tokenless/claude-code/scripts/detect.sh +193 -0
  7. package/adapters/tokenless/claude-code/scripts/install.sh +92 -0
  8. package/adapters/tokenless/claude-code/scripts/uninstall.sh +50 -0
  9. package/adapters/tokenless/codex/.codex-plugin/plugin.json +20 -0
  10. package/adapters/tokenless/codex/README.md +160 -0
  11. package/adapters/tokenless/codex/hooks/hooks.json +52 -0
  12. package/adapters/tokenless/codex/scripts/_common.sh +25 -0
  13. package/adapters/tokenless/codex/scripts/check-tokenless +94 -0
  14. package/adapters/tokenless/codex/scripts/compress-response +441 -0
  15. package/adapters/tokenless/codex/scripts/detect.sh +61 -0
  16. package/adapters/tokenless/codex/scripts/install.sh +151 -0
  17. package/adapters/tokenless/codex/scripts/rewrite-hook +282 -0
  18. package/adapters/tokenless/codex/scripts/tool-ready +303 -0
  19. package/adapters/tokenless/codex/scripts/uninstall.sh +78 -0
  20. package/adapters/tokenless/common/commands/tokenless-stats.toml +2 -0
  21. package/adapters/tokenless/common/cosh-extension.json +65 -0
  22. package/adapters/tokenless/common/hooks/compress_response_hook.py +487 -0
  23. package/adapters/tokenless/common/hooks/compress_schema_hook.py +144 -0
  24. package/adapters/tokenless/common/hooks/compress_toon_hook.py +160 -0
  25. package/adapters/tokenless/common/hooks/hook_utils.py +584 -0
  26. package/adapters/tokenless/common/hooks/rewrite_hook.py +223 -0
  27. package/adapters/tokenless/common/hooks/run-hook.sh +62 -0
  28. package/adapters/tokenless/common/hooks/tool_categories.json +99 -0
  29. package/adapters/tokenless/common/hooks/tool_ready_hook.sh +571 -0
  30. package/adapters/tokenless/common/tokenless-env-fix.sh +730 -0
  31. package/adapters/tokenless/common/tool-ready-spec.json +113 -0
  32. package/adapters/tokenless/dsh/cordis.patch.yml +8 -0
  33. package/adapters/tokenless/dsh/dist/index.js +399 -0
  34. package/adapters/tokenless/dsh/package.json +26 -0
  35. package/adapters/tokenless/hermes/__init__.py +572 -0
  36. package/adapters/tokenless/hermes/plugin.yaml +9 -0
  37. package/adapters/tokenless/hermes/scripts/detect.sh +80 -0
  38. package/adapters/tokenless/hermes/scripts/install.sh +60 -0
  39. package/adapters/tokenless/hermes/scripts/uninstall.sh +45 -0
  40. package/adapters/tokenless/manifest.json +147 -0
  41. package/adapters/tokenless/openclaw/dist/index.d.ts +27 -0
  42. package/adapters/tokenless/openclaw/dist/index.js +598 -0
  43. package/adapters/tokenless/openclaw/dist/tool_categories.json +99 -0
  44. package/adapters/tokenless/openclaw/index.ts +720 -0
  45. package/adapters/tokenless/openclaw/openclaw.plugin.json +40 -0
  46. package/adapters/tokenless/openclaw/package.json +24 -0
  47. package/adapters/tokenless/openclaw/scripts/detect.sh +102 -0
  48. package/adapters/tokenless/openclaw/scripts/install.sh +75 -0
  49. package/adapters/tokenless/openclaw/scripts/uninstall.sh +46 -0
  50. package/adapters/tokenless/openclaw/tsconfig.json +13 -0
  51. package/adapters/tokenless/opencode/plugin.js +246 -0
  52. package/adapters/tokenless/opencode/scripts/detect.sh +38 -0
  53. package/adapters/tokenless/opencode/scripts/install.sh +56 -0
  54. package/adapters/tokenless/opencode/scripts/uninstall.sh +29 -0
  55. package/adapters/tokenless/qoder/.qoder-plugin/plugin.json +11 -0
  56. package/adapters/tokenless/qoder/commands/tokenless-stats.md +8 -0
  57. package/adapters/tokenless/qoder/hooks/hooks.json +36 -0
  58. package/adapters/tokenless/qoder/hooks/run-hook.sh +51 -0
  59. package/adapters/tokenless/qoder/scripts/detect.sh +35 -0
  60. package/adapters/tokenless/qoder/scripts/install.sh +136 -0
  61. package/adapters/tokenless/qoder/scripts/uninstall.sh +106 -0
  62. package/adapters/tokenless/qwencode/qwen-extension.json +69 -0
  63. package/adapters/tokenless/qwencode/scripts/detect.sh +125 -0
  64. package/adapters/tokenless/qwencode/scripts/install.sh +128 -0
  65. package/adapters/tokenless/qwencode/scripts/uninstall.sh +62 -0
  66. package/bin/rtk +6 -0
  67. package/bin/tokenless +6 -0
  68. package/bin/toon +6 -0
  69. package/package.json +57 -0
  70. package/scripts/postinstall.js +208 -0
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env bash
2
+ # install.sh — Register tokenless plugin for Qwen Code via `qwen extensions link`.
3
+ #
4
+ # Responsibility boundary:
5
+ # - This script ONLY deploys an already-stamped plugin manifest.
6
+ # - Manifest stamping (qwen-extension.json.in -> qwen-extension.json) is the
7
+ # Makefile's job:
8
+ # make -C src/tokenless stamp-adapter-templates
9
+ # which `make install` runs automatically before `install-adapter-resources`
10
+ # copies the result into $SHARE_DIR/qwencode.
11
+ # - A dev-only fallback stamps the manifest in place when called outside the
12
+ # RPM/Makefile flow, so adapter-install works on a freshly checked-out tree.
13
+ #
14
+ # Qwen Code extensions can be installed via `link` (local path, no npm build
15
+ # needed) or `install` (local path, copies files). We use `link` so that
16
+ # changes to the adapter directory are reflected immediately without reinstall.
17
+ set -euo pipefail
18
+
19
+ AGENT="${ANOLISA_TARGET:-qwencode}"
20
+ COMPONENT="${ANOLISA_COMPONENT:-tokenless}"
21
+ ADAPTER_DIR="${ANOLISA_ADAPTER_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}"
22
+
23
+ PLUGIN_SRC="$ADAPTER_DIR/qwencode"
24
+ EXTENSION_NAME="tokenless"
25
+
26
+ QWEN_BIN="${QWEN_BIN:-}"
27
+ export PATH="$HOME/.local/bin:/usr/local/bin:$PATH"
28
+ if [ -z "$QWEN_BIN" ]; then
29
+ QWEN_BIN="$(command -v qwen 2>/dev/null || true)"
30
+ fi
31
+
32
+ DRY_RUN="${ANOLISA_DRY_RUN:-0}"
33
+
34
+ echo "[${COMPONENT}] Installing ${AGENT} plugin..."
35
+
36
+ if ! command -v "$QWEN_BIN" &>/dev/null; then
37
+ echo "[${COMPONENT}] qwen CLI not found (QWEN_BIN=${QWEN_BIN}) — skipping plugin installation."
38
+ echo "[${COMPONENT}] Install Qwen Code first, then run this script again."
39
+ exit 0
40
+ fi
41
+
42
+ if [ ! -d "$PLUGIN_SRC" ]; then
43
+ echo "[${COMPONENT}] Plugin source not found: $PLUGIN_SRC" >&2
44
+ exit 1
45
+ fi
46
+
47
+ # Dev-only fallback: stamp qwen-extension.json from .in template when Makefile
48
+ # hasn't run yet. Production installs (RPM, `make install`) ship a stamped
49
+ # manifest inside SHARE_DIR, so this branch is a no-op in those flows.
50
+ EXTENSION_MANIFEST="$PLUGIN_SRC/qwen-extension.json"
51
+ EXTENSION_TEMPLATE="$PLUGIN_SRC/qwen-extension.json.in"
52
+ if [ ! -f "$EXTENSION_MANIFEST" ] && [ -f "$EXTENSION_TEMPLATE" ]; then
53
+ VERSION="${TOKENLESS_VERSION:-${ANOLISA_VERSION:-0.0.0-dev}}"
54
+ sed "s/@VERSION@/${VERSION}/g" "$EXTENSION_TEMPLATE" > "$EXTENSION_MANIFEST"
55
+ echo "[${COMPONENT}] dev-fallback: stamped qwen-extension.json (version=${VERSION}) — production builds should stamp via Makefile"
56
+ fi
57
+
58
+ if [ ! -f "$EXTENSION_MANIFEST" ]; then
59
+ echo "[${COMPONENT}] ERROR: $EXTENSION_MANIFEST missing." >&2
60
+ echo "[${COMPONENT}] Stamp the manifest first:" >&2
61
+ echo "[${COMPONENT}] make -C src/tokenless stamp-adapter-templates" >&2
62
+ exit 1
63
+ fi
64
+
65
+ # Idempotent: unlink first if already linked, then link again.
66
+ # `qwen extensions list` may not reliably show link state, so we
67
+ # always re-link to ensure the latest version is active.
68
+ if "$QWEN_BIN" extensions list 2>/dev/null | grep -qE "(^|[[:space:]])${EXTENSION_NAME}([[:space:]]|$)"; then
69
+ echo "[${COMPONENT}] extension '${EXTENSION_NAME}' already registered, re-linking..."
70
+ if ! "$QWEN_BIN" extensions uninstall "$EXTENSION_NAME" 2>/dev/null; then
71
+ echo "[${COMPONENT}] WARNING: qwen extensions uninstall failed (non-fatal, will re-link)"
72
+ fi
73
+ fi
74
+
75
+ if [ "$DRY_RUN" = "1" ]; then
76
+ echo "DRY-RUN: $QWEN_BIN extensions link $PLUGIN_SRC"
77
+ exit 0
78
+ fi
79
+
80
+ echo "[${COMPONENT}] linking extension from ${PLUGIN_SRC}..."
81
+ "$QWEN_BIN" extensions link "$PLUGIN_SRC" \
82
+ || { echo "[${COMPONENT}] ERROR: qwen extensions link failed" >&2; exit 1; }
83
+
84
+ # Verify the extension was registered.
85
+ if "$QWEN_BIN" extensions list 2>/dev/null | grep -qE "(^|[[:space:]])${EXTENSION_NAME}([[:space:]]|$)"; then
86
+ echo "[${COMPONENT}] ${AGENT} plugin installed and linked via qwen CLI."
87
+ else
88
+ echo "[${COMPONENT}] WARNING: extension '${EXTENSION_NAME}' may not be visible in extensions list yet."
89
+ echo "[${COMPONENT}] Run 'qwen extensions list' or restart qwen-code to confirm."
90
+ fi
91
+
92
+ # Clean up tokenless-specific hook entries from settings.json — the
93
+ # extension's qwen-extension.json already provides all hooks, so duplicate
94
+ # entries in settings.json cause double registration (2x invocations per
95
+ # event). Only remove hook entries whose name starts with "tokenless-",
96
+ # preserving any user-defined hooks that may also exist.
97
+ # Qwen Code stores global settings under ~/.qwen/settings.json.
98
+ SETTINGS_JSON="$HOME/.qwen/settings.json"
99
+ if [ -f "$SETTINGS_JSON" ] && command -v python3 &>/dev/null; then
100
+ if python3 -c "
101
+ import json, sys
102
+ path = sys.argv[1]
103
+ with open(path) as f:
104
+ d = json.load(f)
105
+ if 'hooks' not in d:
106
+ sys.exit(1)
107
+ changed = False
108
+ for event, definitions in d['hooks'].items():
109
+ if not isinstance(definitions, list):
110
+ continue
111
+ filtered = [defn for defn in definitions
112
+ if not any(h.get('name', '').startswith('tokenless-')
113
+ for h in defn.get('hooks', []))]
114
+ if len(filtered) != len(definitions):
115
+ d['hooks'][event] = filtered
116
+ changed = True
117
+ # If all hook events are now empty, remove the entire hooks key.
118
+ if all(not v for v in d['hooks'].values()):
119
+ del d['hooks']
120
+ if changed:
121
+ with open(path, 'w') as f:
122
+ json.dump(d, f, indent=2)
123
+ sys.exit(0)
124
+ sys.exit(1)
125
+ " "$SETTINGS_JSON" 2>/dev/null; then
126
+ echo "[${COMPONENT}] Cleaned up tokenless hook entries from ${SETTINGS_JSON} (extension provides them via qwen-extension.json)."
127
+ fi
128
+ fi
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+ # uninstall.sh — Remove tokenless plugin from Qwen Code via `qwen extensions uninstall`.
3
+ # Falls back to manual cleanup of the extension directory when qwen is unavailable.
4
+ set -euo pipefail
5
+
6
+ AGENT="${ANOLISA_TARGET:-qwencode}"
7
+ COMPONENT="${ANOLISA_COMPONENT:-tokenless}"
8
+
9
+ EXTENSION_NAME="tokenless"
10
+
11
+ QWEN_BIN="${QWEN_BIN:-}"
12
+ export PATH="$HOME/.local/bin:/usr/local/bin:$PATH"
13
+
14
+ echo "[${COMPONENT}] Uninstalling ${AGENT} plugin..."
15
+
16
+ if [ -z "$QWEN_BIN" ]; then
17
+ QWEN_BIN="$(command -v qwen 2>/dev/null || true)"
18
+ fi
19
+
20
+ if [ -n "$QWEN_BIN" ] && [ -x "$QWEN_BIN" ]; then
21
+ "$QWEN_BIN" extensions uninstall "$EXTENSION_NAME" 2>&1 || true
22
+ echo "[${COMPONENT}] ${AGENT} plugin removed via qwen CLI."
23
+ exit 0
24
+ fi
25
+
26
+ echo "[${COMPONENT}] qwen CLI not found — falling back to manual cleanup."
27
+
28
+ # Qwen Code stores installed extensions under ~/.qwen/extensions/ and
29
+ # metadata in ~/.qwen/.qwen-extension-install.json.
30
+ QWEN_HOME="${HOME}/.qwen"
31
+ EXTENSIONS_DIR="${QWEN_HOME}/extensions"
32
+
33
+ # Find and remove the tokenless extension directory.
34
+ for subdir in "$EXTENSIONS_DIR"/*; do
35
+ [ -d "$subdir" ] || continue
36
+ # Match by name field in qwen-extension.json.
37
+ manifest="$subdir/qwen-extension.json"
38
+ if [ -f "$manifest" ] && grep -q '"name": *"tokenless"' "$manifest" 2>/dev/null; then
39
+ rm -rf "$subdir"
40
+ echo "[${COMPONENT}] removed $subdir"
41
+ fi
42
+ done
43
+
44
+ # Also clean up the install-metadata file that may reference the link source.
45
+ for subdir in "$EXTENSIONS_DIR"/*; do
46
+ [ -d "$subdir" ] || continue
47
+ metadata="$subdir/.qwen-extension-install.json"
48
+ if [ -f "$metadata" ] && grep -q '"name": *"tokenless"' "$metadata" 2>/dev/null; then
49
+ rm -rf "$subdir"
50
+ echo "[${COMPONENT}] removed $subdir (via install-metadata)"
51
+ fi
52
+ done
53
+
54
+ # Remove from extension-enablement.json if present.
55
+ ENABLEMENT_FILE="${QWEN_HOME}/extension-enablement.json"
56
+ if [ -f "$ENABLEMENT_FILE" ] && command -v jq &>/dev/null; then
57
+ tmp="$(mktemp "${ENABLEMENT_FILE}.XXXXXX")"
58
+ jq 'del(.tokenless)' "$ENABLEMENT_FILE" > "$tmp" && mv "$tmp" "$ENABLEMENT_FILE"
59
+ echo "[${COMPONENT}] cleaned $ENABLEMENT_FILE"
60
+ fi
61
+
62
+ echo "[${COMPONENT}] manual cleanup complete."
package/bin/rtk ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ console.error(
3
+ 'anolisa-tokenless: postinstall has not run yet. ' +
4
+ 'Run "npm rebuild anolisa-tokenless" to fix.',
5
+ );
6
+ process.exit(1);
package/bin/tokenless ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ console.error(
3
+ 'anolisa-tokenless: postinstall has not run yet. ' +
4
+ 'Run "npm rebuild anolisa-tokenless" to fix.',
5
+ );
6
+ process.exit(1);
package/bin/toon ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ console.error(
3
+ 'anolisa-tokenless: postinstall has not run yet. ' +
4
+ 'Run "npm rebuild anolisa-tokenless" to fix.',
5
+ );
6
+ process.exit(1);
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "anolisa-tokenless",
3
+ "type": "module",
4
+ "version": "0.7.7",
5
+ "description": "Token-Less — LLM token optimization toolkit (schema/response compression, command rewriting, tool readiness)",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/alibaba/anolisa.git",
10
+ "directory": "src/tokenless"
11
+ },
12
+ "homepage": "https://github.com/alibaba/anolisa/tree/main/src/tokenless",
13
+ "keywords": [
14
+ "anolisa",
15
+ "tokenless",
16
+ "llm",
17
+ "token-optimization",
18
+ "compression",
19
+ "cli"
20
+ ],
21
+ "bin": {
22
+ "tokenless": "bin/tokenless",
23
+ "rtk": "bin/rtk",
24
+ "toon": "bin/toon"
25
+ },
26
+ "files": [
27
+ "bin/",
28
+ "scripts/",
29
+ "adapters/",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "scripts": {
34
+ "postinstall": "node scripts/postinstall.js"
35
+ },
36
+ "engines": {
37
+ "node": ">=16.0.0"
38
+ },
39
+ "os": [
40
+ "linux",
41
+ "darwin"
42
+ ],
43
+ "cpu": [
44
+ "x64",
45
+ "arm64"
46
+ ],
47
+ "optionalDependencies": {
48
+ "@anolisa/tokenless-linux-x64": "0.7.7",
49
+ "@anolisa/tokenless-linux-arm64": "0.7.7",
50
+ "@anolisa/tokenless-darwin-x64": "0.7.7",
51
+ "@anolisa/tokenless-darwin-arm64": "0.7.7"
52
+ },
53
+ "publishConfig": {
54
+ "registry": "https://registry.npmjs.org/",
55
+ "access": "public"
56
+ }
57
+ }
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Alibaba Cloud
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+
9
+ /**
10
+ * postinstall script for anolisa-tokenless
11
+ *
12
+ * Resolves the platform-specific binary package and creates launcher
13
+ * symlinks at bin/ that delegate to the native binaries, then installs the
14
+ * bundled Agent adapters into the user-level data directory searched by
15
+ * the adapter hook dispatcher (run-hook.sh).
16
+ *
17
+ * Platform packages follow the naming convention:
18
+ * @anolisa/tokenless-{os}-{arch}
19
+ *
20
+ * Each platform package ships three native binaries:
21
+ * bin/tokenless, bin/rtk, bin/toon
22
+ *
23
+ * Exit codes: on a supported platform, a missing platform package or a
24
+ * missing binary is a hard failure (non-zero exit) so `npm install` fails
25
+ * loudly instead of leaving broken bin stubs behind. Unsupported platforms
26
+ * are already rejected by the root package's os/cpu constraints.
27
+ */
28
+
29
+ import {
30
+ existsSync,
31
+ mkdirSync,
32
+ symlinkSync,
33
+ unlinkSync,
34
+ chmodSync,
35
+ cpSync,
36
+ rmSync,
37
+ } from 'node:fs';
38
+ import { execSync } from 'node:child_process';
39
+ import { join, dirname } from 'node:path';
40
+ import { fileURLToPath } from 'node:url';
41
+ import { createRequire } from 'node:module';
42
+ import { platform, arch, homedir } from 'node:os';
43
+
44
+ const __filename = fileURLToPath(import.meta.url);
45
+ const __dirname = dirname(__filename);
46
+ const require = createRequire(import.meta.url);
47
+ const packageRoot = join(__dirname, '..');
48
+ const binDir = join(packageRoot, 'bin');
49
+
50
+ const BINARIES = ['tokenless', 'rtk', 'toon'];
51
+
52
+ // Map Node.js platform/arch to package names
53
+ const PLATFORM_MAP = {
54
+ 'linux-x64': '@anolisa/tokenless-linux-x64',
55
+ 'linux-arm64': '@anolisa/tokenless-linux-arm64',
56
+ 'darwin-x64': '@anolisa/tokenless-darwin-x64',
57
+ 'darwin-arm64': '@anolisa/tokenless-darwin-arm64',
58
+ };
59
+
60
+ function resolvePackageDir(pkgName) {
61
+ // Resolve platform package using createRequire (compatible with Node 16+)
62
+ try {
63
+ const resolved = require.resolve(`${pkgName}/package.json`);
64
+ return dirname(resolved);
65
+ } catch {
66
+ // Fallback: walk up to find node_modules
67
+ let current = packageRoot;
68
+ while (current !== dirname(current)) {
69
+ const candidate = join(current, 'node_modules', ...pkgName.split('/'));
70
+ if (existsSync(candidate)) {
71
+ return candidate;
72
+ }
73
+ current = dirname(current);
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+
79
+ function isMusl() {
80
+ // The platform packages declare libc=glibc, but npm CLI versions prior to
81
+ // ~8.3 (e.g. 8.19.4) do not check the libc field in checkPlatform. On a
82
+ // musl-based distribution such as Alpine, the linux-x64 package will still
83
+ // be installed and its glibc-linked ELF will not run. Detect this at
84
+ // postinstall time and fail with a clear message instead of leaving broken
85
+ // bin stubs.
86
+ if (platform() !== 'linux') return false;
87
+ try {
88
+ const out = execSync('ldd --version 2>&1 || true', {
89
+ encoding: 'utf-8',
90
+ stdio: ['ignore', 'pipe', 'pipe'],
91
+ });
92
+ return /\bmusl\b/i.test(out);
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+
98
+ function main() {
99
+ const key = `${platform()}-${arch()}`;
100
+ const pkgName = PLATFORM_MAP[key];
101
+
102
+ if (!pkgName) {
103
+ console.warn(
104
+ `anolisa-tokenless: No prebuilt binary available for ${platform()}-${arch()}.`,
105
+ );
106
+ console.warn('You can build from source: https://github.com/alibaba/anolisa/tree/main/src/tokenless');
107
+ process.exit(0);
108
+ }
109
+
110
+ if (isMusl()) {
111
+ console.error(
112
+ 'anolisa-tokenless: musl-based Linux distributions (e.g. Alpine) are not supported by the prebuilt binaries.',
113
+ );
114
+ console.error(
115
+ 'Build from source for your libc: https://github.com/alibaba/anolisa/tree/main/src/tokenless',
116
+ );
117
+ process.exit(1);
118
+ }
119
+
120
+ const pkgDir = resolvePackageDir(pkgName);
121
+
122
+ if (!pkgDir || !existsSync(pkgDir)) {
123
+ console.error(
124
+ `anolisa-tokenless: Platform package ${pkgName} not found.`,
125
+ );
126
+ console.error(
127
+ 'This usually means optionalDependencies were skipped during install.',
128
+ );
129
+ console.error(
130
+ 'Try: npm install --include=optional or check npm install logs for details.',
131
+ );
132
+ console.error(
133
+ 'You can also build from source: https://github.com/alibaba/anolisa/tree/main/src/tokenless',
134
+ );
135
+ // The platform is supported, so the platform package is effectively a
136
+ // required dependency — fail the install instead of leaving bin stubs.
137
+ process.exit(1);
138
+ }
139
+
140
+ // Ensure bin/ directory exists
141
+ if (!existsSync(binDir)) {
142
+ mkdirSync(binDir, { recursive: true });
143
+ }
144
+
145
+ const missing = [];
146
+ for (const binary of BINARIES) {
147
+ const nativeBinary = join(pkgDir, 'bin', binary);
148
+ if (!existsSync(nativeBinary)) {
149
+ console.error(`anolisa-tokenless: Binary ${binary} not found in ${pkgName}`);
150
+ missing.push(binary);
151
+ continue;
152
+ }
153
+
154
+ const linkPath = join(binDir, binary);
155
+
156
+ // Remove existing symlink or file
157
+ if (existsSync(linkPath)) {
158
+ unlinkSync(linkPath);
159
+ }
160
+
161
+ symlinkSync(nativeBinary, linkPath);
162
+ chmodSync(linkPath, 0o755);
163
+ }
164
+
165
+ if (missing.length > 0) {
166
+ console.error(
167
+ `anolisa-tokenless: Incomplete platform package ${pkgName} — missing: ${missing.join(', ')}. Failing install.`,
168
+ );
169
+ process.exit(1);
170
+ }
171
+
172
+ console.log(`anolisa-tokenless: Linked ${BINARIES.length} binaries for ${platform()}-${arch()}`);
173
+
174
+ installAdapters();
175
+ }
176
+
177
+ /**
178
+ * Install the bundled Agent adapters (hook scripts and install helpers —
179
+ * plain bash/python, OS independent) into the user-level data directory that
180
+ * the hook dispatcher (common/hooks/run-hook.sh) already searches:
181
+ * ~/.local/share/anolisa/adapters/tokenless
182
+ *
183
+ * Fail-open: adapter installation is supplementary — a failure here warns
184
+ * but never fails the npm install, and the files remain available inside
185
+ * the package under adapters/.
186
+ */
187
+ function installAdapters() {
188
+ const adaptersSrc = join(packageRoot, 'adapters', 'tokenless');
189
+ if (!existsSync(adaptersSrc)) return;
190
+
191
+ const destParent = join(homedir(), '.local', 'share', 'anolisa', 'adapters');
192
+ const dest = join(destParent, 'tokenless');
193
+ try {
194
+ rmSync(dest, { recursive: true, force: true });
195
+ mkdirSync(destParent, { recursive: true });
196
+ cpSync(adaptersSrc, dest, { recursive: true });
197
+ console.log(`anolisa-tokenless: Installed Agent adapters to ${dest}`);
198
+ console.log(
199
+ 'anolisa-tokenless: To register an adapter with an Agent product, run its install script, e.g.:',
200
+ );
201
+ console.log(` bash ${join(dest, 'claude-code', 'scripts', 'install.sh')}`);
202
+ } catch (err) {
203
+ console.warn(`anolisa-tokenless: Could not install adapters to ${dest}: ${err.message}`);
204
+ console.warn(`anolisa-tokenless: Adapter files remain available at ${adaptersSrc}`);
205
+ }
206
+ }
207
+
208
+ main();