cxs is a local-first CLI for searching Codex session logs. It is designed for progressive retrieval: find the right session first, then read
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: Track B Phase 1 — Node 化(better-sqlite3 + vitest + esbuild bundle)

让 cxs 不依赖 Bun runtime 也能跑,完成"双发布"双轨的另一条腿。
用户 npm i -g cxs 装 ~13 MB(better-sqlite3 prebuild + chalk + commander),
对比 standalone binary 60-117 MB 体积优势明显。Bun 仍是主开发 runtime
(bun:sqlite 路径已弃用,因为 Bun 不支持 better-sqlite3 N-API,见
oven-sh/bun#4290)。

DB 层:bun:sqlite → better-sqlite3
- db.ts / query.ts:Database 改 better-sqlite3 default import,
Database.setCustomSQLite 删除(不需要 homebrew dylib 优化),
db.query<RowType, ParamType> 改 db.prepare<ParamType, RowType>(类型参
数顺序倒置),db.run 带参数版本改 db.prepare(sql).run(...args),
db.run 不带参的 DDL 改 db.exec
- 测试文件同步改 import + db.exec/db.prepare
- query.test.ts 子进程内嵌 eval script 也从 bun:sqlite 改 better-sqlite3

测试框架:bun:test → vitest
- 7 个 .test.ts import 切到 vitest(API 完全兼容 BDD 风格)
- @types/bun + bun-types 删除
- import.meta.dir → import.meta.dirname(Node 22+ 内置标准 API)

子进程 spawn:Bun.spawn → child_process.spawn
- cli.test.ts runCli + runExecutable 重写为 promise 风格
- cli.test.ts 子进程跑 cli.ts 改用 node --import tsx 路径
- query.test.ts runReadChild 内嵌 script 也加 --import tsx
- eval/perf-bench.ts + eval/run-manual-eval.ts 同样
- eval 内 ./bin/cxs 改成 process.execPath + tsx + cli.ts
- 删除 cli.test.ts "bin/cxs symlink" 测试(bin/cxs 不存在了)

Build pipeline:
- bin/cxs bash shim 删除;package.json bin 字段直指 dist/cli.js
- esbuild 把 cli.ts bundle 成 dist/cli.js 单文件 57 KB
(better-sqlite3 / chalk / commander external 走 npm 解析)
- scripts/post-build.mjs 给 dist/cli.js 加 #!/usr/bin/env node shebang
+ chmod 0755
- npm run build 跑完整 pipeline,prepublishOnly 自动触发
- package.json files 字段改 ["dist/", "README.md", "LICENSE"];
engines 从 bun>=1.3 改 node>=22(JSON import + with type 需要)
- tsconfig.json 加 resolveJsonModule + dist/ 到 exclude

Dev runtime:
- "cxs" / "eval:manual" / "eval:perf" 全改 tsx 跑(放弃 bun run cli.ts)
- README "CLI Install Guide" 重组为三档:standalone binary / npm i -g
/ 源码开发,各档体积与依赖说明清晰

实测全过:
- bun run check:tsc + 42 vitest tests 全绿
- npm run build:dist/cli.js 57 KB single file
- node dist/cli.js 全套 (--version / current bad / sync empty / stats /
find) 完整跑通
- npm pack 19.4 KB,4 文件 tarball;npm i -g 装上 12.8 MB,跑通
- bun build --compile darwin-arm64 仍 work(无回归)
- npm run eval:perf 子进程链路通

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 160b6a90ffb6

cat ef47030c bb35ec3b

+634 -298
+2
.gitignore
··· 1 1 node_modules/ 2 + dist/ 2 3 data/ 4 + *.tgz 3 5 .claude/ 4 6 .entire/ 5 7 .intent/
+16 -9
README.md
··· 42 42 43 43 支持平台:darwin-arm64 / darwin-x64 / linux-x64 / linux-arm64 / windows-x64。如果 `~/.local/bin` 不在 PATH,脚本会提示;或先 `export CXS_INSTALL_DIR=/usr/local/bin` 再跑。 44 44 45 - ### 从源码(开发者 / 需要 PR / Bun 路线) 45 + ### npm 全局安装(需要 Node 22+) 46 + 47 + ```bash 48 + npm i -g cxs 49 + ``` 50 + 51 + 约 13 MB,不含 runtime,启动快。CI / 容器场景以及已有 Node 工具链的用户首选。 52 + 53 + ### 从源码(开发者 / 需要 PR) 46 54 47 55 ```bash 48 56 git clone https://github.com/catoncat/cxs.git 49 57 cd cxs 50 - bun install 51 - export CXS_BIN="$PWD/bin/cxs" 52 - "$CXS_BIN" --version 53 - "$CXS_BIN" --help 58 + bun install # Bun 是主开发 runtime 59 + bun run cxs --version # 通过 tsx 直接跑 cli.ts 54 60 ``` 55 61 56 - `--help` 应列出 `sync` / `find` / `read-range` / `read-page` / `list` / `stats` / `current`。 62 + 完整工程命令:`bun run check`(tsc + vitest)、`npm run build`(esbuild bundle 出 `dist/cli.js`)、`npm run eval:perf`(真实大库基准)。 57 63 58 64 ### 首次使用建立索引 59 65 60 66 ```bash 61 - cxs sync # 装的是 standalone binary 62 - # 或 "$CXS_BIN" sync # 装的是源码模式 67 + cxs sync 63 68 cxs stats --json 64 69 ``` 70 + 71 + `--help` 应列出 `sync` / `find` / `read-range` / `read-page` / `list` / `stats` / `current`。 65 72 66 73 ### 数据目录 67 74 ··· 74 81 ### 要求 75 82 76 83 - 本机可读 `~/.codex/sessions` 77 - - 源码模式需 Bun `>= 1.3`;binary 模式无运行时依赖 84 + - 三种安装路径:standalone binary 无运行时依赖;`npm i -g` 需 Node `>= 22`;源码开发需 Bun `>= 1.3` 78 85 79 86 ## 用法 80 87
-16
bin/cxs
··· 1 - #!/usr/bin/env bash 2 - set -euo pipefail 3 - 4 - SOURCE="${BASH_SOURCE[0]}" 5 - while [ -h "$SOURCE" ]; do 6 - DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)" 7 - TARGET="$(readlink "$SOURCE")" 8 - if [[ "$TARGET" == /* ]]; then 9 - SOURCE="$TARGET" 10 - else 11 - SOURCE="$DIR/$TARGET" 12 - fi 13 - done 14 - 15 - ROOT="$(cd -P "$(dirname "$SOURCE")/.." && pwd)" 16 - exec bun run "$ROOT/cli.ts" "$@"
+344 -3
bun.lock
··· 5 5 "": { 6 6 "name": "cxs", 7 7 "dependencies": { 8 + "better-sqlite3": "^12.9.0", 8 9 "chalk": "^5.6.2", 9 10 "commander": "^14.0.3", 10 11 }, 11 12 "devDependencies": { 12 - "@types/bun": "^1.3.13", 13 + "@types/better-sqlite3": "^7.6.13", 14 + "@types/node": "^25.6.0", 15 + "esbuild": "^0.28.0", 16 + "tsx": "^4.21.0", 13 17 "typescript": "^6.0.3", 18 + "vitest": "^4.1.5", 14 19 }, 15 20 }, 16 21 }, 17 22 "packages": { 18 - "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], 23 + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], 24 + 25 + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], 26 + 27 + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], 28 + 29 + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA=="], 30 + 31 + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.0", "", { "os": "android", "cpu": "arm" }, "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ=="], 32 + 33 + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.0", "", { "os": "android", "cpu": "arm64" }, "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw=="], 34 + 35 + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.0", "", { "os": "android", "cpu": "x64" }, "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA=="], 36 + 37 + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q=="], 38 + 39 + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ=="], 40 + 41 + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q=="], 42 + 43 + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw=="], 44 + 45 + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw=="], 46 + 47 + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A=="], 48 + 49 + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ=="], 50 + 51 + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg=="], 52 + 53 + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w=="], 54 + 55 + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg=="], 56 + 57 + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ=="], 58 + 59 + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q=="], 60 + 61 + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ=="], 62 + 63 + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw=="], 64 + 65 + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.0", "", { "os": "none", "cpu": "x64" }, "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw=="], 66 + 67 + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g=="], 68 + 69 + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA=="], 70 + 71 + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w=="], 72 + 73 + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw=="], 74 + 75 + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA=="], 76 + 77 + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA=="], 78 + 79 + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw=="], 80 + 81 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 82 + 83 + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], 84 + 85 + "@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="], 86 + 87 + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.17", "", { "os": "android", "cpu": "arm64" }, "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ=="], 88 + 89 + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw=="], 90 + 91 + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw=="], 92 + 93 + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw=="], 94 + 95 + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm" }, "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ=="], 96 + 97 + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q=="], 98 + 99 + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg=="], 100 + 101 + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA=="], 102 + 103 + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "s390x" }, "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA=="], 104 + 105 + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA=="], 106 + 107 + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw=="], 108 + 109 + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.17", "", { "os": "none", "cpu": "arm64" }, "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA=="], 110 + 111 + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.17", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA=="], 112 + 113 + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA=="], 114 + 115 + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "x64" }, "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg=="], 116 + 117 + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.17", "", {}, "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg=="], 118 + 119 + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], 120 + 121 + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 122 + 123 + "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="], 124 + 125 + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], 126 + 127 + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], 128 + 129 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 19 130 20 131 "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], 21 132 22 - "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], 133 + "@vitest/expect": ["@vitest/expect@4.1.5", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.5", "@vitest/utils": "4.1.5", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw=="], 134 + 135 + "@vitest/mocker": ["@vitest/mocker@4.1.5", "", { "dependencies": { "@vitest/spy": "4.1.5", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw=="], 136 + 137 + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.5", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g=="], 138 + 139 + "@vitest/runner": ["@vitest/runner@4.1.5", "", { "dependencies": { "@vitest/utils": "4.1.5", "pathe": "^2.0.3" } }, "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ=="], 140 + 141 + "@vitest/snapshot": ["@vitest/snapshot@4.1.5", "", { "dependencies": { "@vitest/pretty-format": "4.1.5", "@vitest/utils": "4.1.5", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ=="], 142 + 143 + "@vitest/spy": ["@vitest/spy@4.1.5", "", {}, "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ=="], 144 + 145 + "@vitest/utils": ["@vitest/utils@4.1.5", "", { "dependencies": { "@vitest/pretty-format": "4.1.5", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug=="], 146 + 147 + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], 148 + 149 + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], 150 + 151 + "better-sqlite3": ["better-sqlite3@12.9.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ=="], 152 + 153 + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], 154 + 155 + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], 156 + 157 + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], 158 + 159 + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], 23 160 24 161 "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], 162 + 163 + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], 25 164 26 165 "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], 27 166 167 + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 168 + 169 + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], 170 + 171 + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], 172 + 173 + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 174 + 175 + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], 176 + 177 + "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], 178 + 179 + "esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="], 180 + 181 + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], 182 + 183 + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], 184 + 185 + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], 186 + 187 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 188 + 189 + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], 190 + 191 + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], 192 + 193 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 194 + 195 + "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], 196 + 197 + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], 198 + 199 + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], 200 + 201 + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 202 + 203 + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], 204 + 205 + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], 206 + 207 + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], 208 + 209 + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], 210 + 211 + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], 212 + 213 + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], 214 + 215 + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], 216 + 217 + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], 218 + 219 + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], 220 + 221 + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], 222 + 223 + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], 224 + 225 + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], 226 + 227 + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], 228 + 229 + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], 230 + 231 + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], 232 + 233 + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 234 + 235 + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], 236 + 237 + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 238 + 239 + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], 240 + 241 + "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="], 242 + 243 + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], 244 + 245 + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 246 + 247 + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 248 + 249 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 250 + 251 + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], 252 + 253 + "postcss": ["postcss@8.5.12", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA=="], 254 + 255 + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], 256 + 257 + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], 258 + 259 + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], 260 + 261 + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], 262 + 263 + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 264 + 265 + "rolldown": ["rolldown@1.0.0-rc.17", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@rolldown/pluginutils": "1.0.0-rc.17" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-x64": "1.0.0-rc.17", "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA=="], 266 + 267 + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], 268 + 269 + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], 270 + 271 + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], 272 + 273 + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], 274 + 275 + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], 276 + 277 + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 278 + 279 + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], 280 + 281 + "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], 282 + 283 + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], 284 + 285 + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], 286 + 287 + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], 288 + 289 + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], 290 + 291 + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], 292 + 293 + "tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="], 294 + 295 + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], 296 + 297 + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], 298 + 299 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 300 + 301 + "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], 302 + 303 + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], 304 + 28 305 "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], 29 306 30 307 "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], 308 + 309 + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], 310 + 311 + "vite": ["vite@8.0.10", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.10", "rolldown": "1.0.0-rc.17", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw=="], 312 + 313 + "vitest": ["vitest@4.1.5", "", { "dependencies": { "@vitest/expect": "4.1.5", "@vitest/mocker": "4.1.5", "@vitest/pretty-format": "4.1.5", "@vitest/runner": "4.1.5", "@vitest/snapshot": "4.1.5", "@vitest/spy": "4.1.5", "@vitest/utils": "4.1.5", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.5", "@vitest/browser-preview": "4.1.5", "@vitest/browser-webdriverio": "4.1.5", "@vitest/coverage-istanbul": "4.1.5", "@vitest/coverage-v8": "4.1.5", "@vitest/ui": "4.1.5", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg=="], 314 + 315 + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], 316 + 317 + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], 318 + 319 + "tsx/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], 320 + 321 + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], 322 + 323 + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], 324 + 325 + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], 326 + 327 + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], 328 + 329 + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], 330 + 331 + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], 332 + 333 + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], 334 + 335 + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], 336 + 337 + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], 338 + 339 + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], 340 + 341 + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], 342 + 343 + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], 344 + 345 + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], 346 + 347 + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], 348 + 349 + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], 350 + 351 + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], 352 + 353 + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], 354 + 355 + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], 356 + 357 + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], 358 + 359 + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], 360 + 361 + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], 362 + 363 + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], 364 + 365 + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], 366 + 367 + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], 368 + 369 + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], 370 + 371 + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], 31 372 } 32 373 }
+27 -47
cli.test.ts
··· 1 - import { afterEach, describe, expect, test } from "bun:test"; 2 - import { Database } from "bun:sqlite"; 3 - import { chmodSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; 1 + import { afterEach, describe, expect, test } from "vitest"; 2 + import Database from "better-sqlite3"; 3 + import { spawn as childSpawn } from "node:child_process"; 4 + import { chmodSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; 4 5 import { tmpdir } from "node:os"; 5 6 import { join } from "node:path"; 6 7 import { INDEX_VERSION } from "./env"; ··· 23 24 }); 24 25 25 26 describe("cxs cli", () => { 26 - test("bin/cxs works when invoked through a symlink", async () => { 27 - const base = mkdtempSync(join(tmpdir(), "cxs-cli-symlink-")); 28 - tempDirs.push(base); 29 - const linkDir = join(base, "bin"); 30 - mkdirSync(linkDir, { recursive: true }); 31 - const linkPath = join(linkDir, "cxs-sim"); 32 - symlinkSync(join(import.meta.dir, "bin", "cxs"), linkPath); 33 - 34 - const result = await runExecutable(linkPath, ["--version"]); 35 - 36 - expect(result.exitCode).toBe(0); 37 - expect(result.stdout.trim()).toBe(packageVersion()); 38 - }); 39 - 40 27 test("help only shows current/sync/find/read-range/read-page/list/stats", async () => { 41 28 const result = await runCli(["--help"]); 42 29 expect(result.exitCode).toBe(0); ··· 56 43 tempDirs.push(base); 57 44 const stateDbPath = join(base, "state.sqlite"); 58 45 const db = new Database(stateDbPath); 59 - db.run(` 46 + db.exec(` 60 47 CREATE TABLE threads ( 61 48 id TEXT PRIMARY KEY, 62 49 rollout_path TEXT NOT NULL, ··· 65 52 updated_at_ms INTEGER 66 53 ) 67 54 `); 68 - db.run( 69 - "INSERT INTO threads (id, rollout_path, cwd, title, updated_at_ms) VALUES (?, ?, ?, ?, ?)", 70 - ["aaaa1111-1111-4111-8111-111111111111", "/tmp/one.jsonl", "/tmp/picc", "older", 100], 71 - ); 72 - db.run( 55 + const insertThread = db.prepare( 73 56 "INSERT INTO threads (id, rollout_path, cwd, title, updated_at_ms) VALUES (?, ?, ?, ?, ?)", 74 - ["bbbb2222-2222-4222-8222-222222222222", "/tmp/two.jsonl", "/tmp/picc", "newer", 200], 75 57 ); 58 + insertThread.run("aaaa1111-1111-4111-8111-111111111111", "/tmp/one.jsonl", "/tmp/picc", "older", 100); 59 + insertThread.run("bbbb2222-2222-4222-8222-222222222222", "/tmp/two.jsonl", "/tmp/picc", "newer", 200); 76 60 db.close(); 77 61 78 62 const result = await runCli([ ··· 115 99 tempDirs.push(base); 116 100 const stateDbPath = join(base, "state.sqlite"); 117 101 const db = new Database(stateDbPath); 118 - db.run("CREATE TABLE other (id INTEGER PRIMARY KEY)"); 102 + db.exec("CREATE TABLE other (id INTEGER PRIMARY KEY)"); 119 103 db.close(); 120 104 121 105 const result = await runCli(["current", "--state-db", stateDbPath, "--json"]); ··· 132 116 tempDirs.push(base); 133 117 const stateDbPath = join(base, "state.sqlite"); 134 118 const db = new Database(stateDbPath); 135 - db.run(` 119 + db.exec(` 136 120 CREATE TABLE threads ( 137 121 id TEXT PRIMARY KEY, 138 122 cwd TEXT NOT NULL, ··· 351 335 }); 352 336 } 353 337 354 - function packageVersion(): string { 355 - const raw = readFileSync(join(import.meta.dir, "package.json"), "utf8"); 356 - const parsed = JSON.parse(raw) as { version?: unknown }; 357 - if (typeof parsed.version !== "string") throw new Error("package.json version is missing"); 358 - return parsed.version; 359 - } 360 - 361 338 async function runCli(args: string[]): Promise<{ exitCode: number; stdout: string; stderr: string }> { 362 - return runExecutable(process.execPath, ["cli.ts", ...args], import.meta.dir); 339 + // Spawn cli.ts via tsx so the test works under both Bun (via bunx) and 340 + // Node (via npx tsx) without requiring a build step. process.execPath 341 + // resolves to the runtime that's running vitest. 342 + return runExecutable(process.execPath, ["--import", "tsx", "cli.ts", ...args], import.meta.dirname); 363 343 } 364 344 365 345 async function runExecutable( 366 346 executable: string, 367 347 args: string[], 368 - cwd = import.meta.dir, 348 + cwd = import.meta.dirname, 369 349 ): Promise<{ exitCode: number; stdout: string; stderr: string }> { 370 - const proc = Bun.spawn([executable, ...args], { 371 - cwd, 372 - stdout: "pipe", 373 - stderr: "pipe", 350 + return new Promise((resolve, reject) => { 351 + const proc = childSpawn(executable, args, { cwd, stdio: ["ignore", "pipe", "pipe"] }); 352 + let stdout = ""; 353 + let stderr = ""; 354 + proc.stdout!.setEncoding("utf8"); 355 + proc.stderr!.setEncoding("utf8"); 356 + proc.stdout!.on("data", (chunk: string) => { stdout += chunk; }); 357 + proc.stderr!.on("data", (chunk: string) => { stderr += chunk; }); 358 + proc.on("error", reject); 359 + proc.on("close", (code) => { 360 + resolve({ exitCode: code ?? 0, stdout, stderr }); 361 + }); 374 362 }); 375 - 376 - const [stdout, stderr, exitCode] = await Promise.all([ 377 - new Response(proc.stdout).text(), 378 - new Response(proc.stderr).text(), 379 - proc.exited, 380 - ]); 381 - 382 - return { exitCode, stdout, stderr }; 383 363 }
+105 -112
db.ts
··· 1 1 import { existsSync } from "node:fs"; 2 - import { Database } from "bun:sqlite"; 3 - import type { SQLQueryBindings } from "bun:sqlite"; 2 + import Database from "better-sqlite3"; 4 3 import { tokenizedText } from "./tokenize"; 5 4 import type { 6 5 CwdCount, ··· 11 10 SessionRecord, 12 11 } from "./types"; 13 12 14 - const CUSTOM_SQLITE = "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib"; 15 - const BUSY_TIMEOUT_MS = 5000; 16 - type SqlParams = SQLQueryBindings[]; 13 + type Db = Database.Database; 14 + type SqlParams = unknown[]; 17 15 18 - if (existsSync(CUSTOM_SQLITE)) { 19 - Database.setCustomSQLite(CUSTOM_SQLITE); 20 - } 16 + const BUSY_TIMEOUT_MS = 5000; 21 17 22 - export function openReadDb(dbPath: string): Database { 18 + export function openReadDb(dbPath: string): Db { 23 19 if (!existsSync(dbPath)) { 24 20 throw new Error(`index not found: ${dbPath}; run cxs sync first`); 25 21 } 26 22 27 23 const db = new Database(dbPath, { readonly: true }); 28 - db.run(`PRAGMA busy_timeout=${BUSY_TIMEOUT_MS}`); 29 - db.run("PRAGMA query_only=ON"); 30 - db.run("PRAGMA temp_store=MEMORY"); 24 + db.pragma(`busy_timeout = ${BUSY_TIMEOUT_MS}`); 25 + db.pragma("query_only = ON"); 26 + db.pragma("temp_store = MEMORY"); 31 27 return db; 32 28 } 33 29 34 30 // Why: callers used to do `const db = openReadDb(...); ... db.close();` which 35 31 // leaks the connection if work in between throws. Wrapping in try/finally at 36 32 // every callsite is noise — fold it once. 37 - export function withReadDb<T>(dbPath: string, fn: (db: Database) => T): T { 33 + export function withReadDb<T>(dbPath: string, fn: (db: Db) => T): T { 38 34 const db = openReadDb(dbPath); 39 35 try { 40 36 return fn(db); ··· 43 39 } 44 40 } 45 41 46 - export function openWriteDb(dbPath: string): Database { 42 + export function openWriteDb(dbPath: string): Db { 47 43 const db = new Database(dbPath); 48 - db.run(`PRAGMA busy_timeout=${BUSY_TIMEOUT_MS}`); 49 - db.run("PRAGMA journal_mode=WAL"); 50 - db.run("PRAGMA synchronous=NORMAL"); 51 - db.run("PRAGMA temp_store=MEMORY"); 52 - db.run("PRAGMA foreign_keys=ON"); 44 + db.pragma(`busy_timeout = ${BUSY_TIMEOUT_MS}`); 45 + db.pragma("journal_mode = WAL"); 46 + db.pragma("synchronous = NORMAL"); 47 + db.pragma("temp_store = MEMORY"); 48 + db.pragma("foreign_keys = ON"); 53 49 ensureSchema(db); 54 50 return db; 55 51 } 56 52 57 - function ensureSchema(db: Database): void { 58 - db.run(` 53 + function ensureSchema(db: Db): void { 54 + db.exec(` 59 55 CREATE TABLE IF NOT EXISTS sessions ( 60 56 id INTEGER PRIMARY KEY AUTOINCREMENT, 61 57 session_uuid TEXT NOT NULL UNIQUE, ··· 80 76 ensureTextColumn(db, "sessions", "compact_text"); 81 77 ensureTextColumn(db, "sessions", "reasoning_summary_text"); 82 78 83 - db.run(` 79 + db.exec(` 84 80 CREATE TABLE IF NOT EXISTS messages ( 85 81 id INTEGER PRIMARY KEY AUTOINCREMENT, 86 82 session_id INTEGER NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, ··· 94 90 ) 95 91 `); 96 92 97 - db.run("CREATE INDEX IF NOT EXISTS idx_messages_session_seq ON messages(session_uuid, seq)"); 98 - db.run("CREATE INDEX IF NOT EXISTS idx_sessions_started_at ON sessions(started_at DESC)"); 93 + db.exec("CREATE INDEX IF NOT EXISTS idx_messages_session_seq ON messages(session_uuid, seq)"); 94 + db.exec("CREATE INDEX IF NOT EXISTS idx_sessions_started_at ON sessions(started_at DESC)"); 99 95 100 - db.run(` 96 + db.exec(` 101 97 CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5( 102 98 content_text, 103 99 session_uuid UNINDEXED, ··· 113 109 dropLegacyTrigramTable(db); 114 110 } 115 111 116 - function dropLegacyTrigramTable(db: Database): void { 112 + function dropLegacyTrigramTable(db: Db): void { 117 113 // cxs <= v2 shipped a second FTS5 virtual table for CJK trigram search. 118 114 // The hybrid bigram+Segmenter tokenizer in tokenize.ts replaces it, so 119 115 // drop the old table and its shadow rows if they still exist. 120 - db.run("DROP TABLE IF EXISTS messages_fts_trigram"); 116 + db.exec("DROP TABLE IF EXISTS messages_fts_trigram"); 121 117 } 122 118 123 - function ensureSessionsFtsTable(db: Database): void { 119 + function ensureSessionsFtsTable(db: Db): void { 124 120 const existing = db 125 - .query("SELECT 1 FROM sqlite_master WHERE name = 'sessions_fts' LIMIT 1") 121 + .prepare("SELECT 1 FROM sqlite_master WHERE name = 'sessions_fts' LIMIT 1") 126 122 .get(); 127 123 128 124 if (existing) { 129 125 const columns = db 130 - .query("PRAGMA table_info(sessions_fts)") 126 + .prepare("PRAGMA table_info(sessions_fts)") 131 127 .all() as Array<{ name: string }>; 132 128 const names = new Set(columns.map((column) => column.name)); 133 129 if (!names.has("compact_text") || !names.has("reasoning_summary_text")) { 134 - db.run("DROP TABLE sessions_fts"); 130 + db.exec("DROP TABLE sessions_fts"); 135 131 } 136 132 } 137 133 138 - db.run(` 134 + db.exec(` 139 135 CREATE VIRTUAL TABLE IF NOT EXISTS sessions_fts USING fts5( 140 136 title, 141 137 summary_text, ··· 148 144 } 149 145 150 146 export function getIndexedSessionMeta( 151 - db: Database, 147 + db: Db, 152 148 filePath: string, 153 149 ): { rawFileMtime: number; rawFileSize: number; indexVersion: string } | null { 154 150 const row = db 155 - .query<{ rawFileMtime: number; rawFileSize: number; indexVersion: string }, [string]>(` 151 + .prepare<[string], { rawFileMtime: number; rawFileSize: number; indexVersion: string }>(` 156 152 SELECT raw_file_mtime AS rawFileMtime, raw_file_size AS rawFileSize, index_version AS indexVersion 157 153 FROM sessions 158 154 WHERE file_path = ? ··· 160 156 `) 161 157 .get(filePath) as 162 158 | { rawFileMtime: number; rawFileSize: number; indexVersion: string } 163 - | null; 159 + | undefined; 164 160 165 161 return row ?? null; 166 162 } 167 163 168 - export function deleteSessionByFilePath(db: Database, filePath: string): void { 164 + export function deleteSessionByFilePath(db: Db, filePath: string): void { 169 165 const row = db 170 - .query<{ sessionUuid: string }, [string]>("SELECT session_uuid AS sessionUuid FROM sessions WHERE file_path = ? LIMIT 1") 171 - .get(filePath) as { sessionUuid: string } | null; 166 + .prepare<[string], { sessionUuid: string }>("SELECT session_uuid AS sessionUuid FROM sessions WHERE file_path = ? LIMIT 1") 167 + .get(filePath) as { sessionUuid: string } | undefined; 172 168 173 169 if (!row) return; 174 170 deleteSessionByUuid(db, row.sessionUuid); 175 171 } 176 172 177 - function deleteSessionByUuid(db: Database, sessionUuid: string): void { 178 - db.run("DELETE FROM sessions_fts WHERE session_uuid = ?", [sessionUuid]); 179 - db.run("DELETE FROM messages_fts WHERE session_uuid = ?", [sessionUuid]); 180 - db.run("DELETE FROM messages WHERE session_uuid = ?", [sessionUuid]); 181 - db.run("DELETE FROM sessions WHERE session_uuid = ?", [sessionUuid]); 173 + function deleteSessionByUuid(db: Db, sessionUuid: string): void { 174 + db.prepare("DELETE FROM sessions_fts WHERE session_uuid = ?").run(sessionUuid); 175 + db.prepare("DELETE FROM messages_fts WHERE session_uuid = ?").run(sessionUuid); 176 + db.prepare("DELETE FROM messages WHERE session_uuid = ?").run(sessionUuid); 177 + db.prepare("DELETE FROM sessions WHERE session_uuid = ?").run(sessionUuid); 182 178 } 183 179 184 180 export function replaceSession( 185 - db: Database, 181 + db: Db, 186 182 session: ParsedSession, 187 183 rawFileMtime: number, 188 184 rawFileSize: number, ··· 190 186 ): void { 191 187 const tx = db.transaction(() => { 192 188 const existing = db 193 - .query<{ id: number }, [string, string]>("SELECT id FROM sessions WHERE session_uuid = ? OR file_path = ? LIMIT 1") 194 - .get(session.sessionUuid, session.filePath) as { id: number } | null; 189 + .prepare<[string, string], { id: number }>("SELECT id FROM sessions WHERE session_uuid = ? OR file_path = ? LIMIT 1") 190 + .get(session.sessionUuid, session.filePath) as { id: number } | undefined; 195 191 196 192 if (existing) { 197 - db.run( 193 + db.prepare( 198 194 ` 199 195 UPDATE sessions 200 196 SET session_uuid = ?, file_path = ?, title = ?, summary_text = ?, compact_text = ?, reasoning_summary_text = ?, ··· 202 198 message_count = ?, raw_file_mtime = ?, raw_file_size = ?, index_version = ?, updated_at = CURRENT_TIMESTAMP 203 199 WHERE id = ? 204 200 `, 205 - [ 206 - session.sessionUuid, 207 - session.filePath, 208 - session.title, 209 - session.summaryText, 210 - session.compactText ?? "", 211 - session.reasoningSummaryText ?? "", 212 - session.cwd, 213 - session.model, 214 - session.startedAt, 215 - session.endedAt, 216 - session.messages.length, 217 - rawFileMtime, 218 - rawFileSize, 219 - indexVersion, 220 - existing.id, 221 - ], 201 + ).run( 202 + session.sessionUuid, 203 + session.filePath, 204 + session.title, 205 + session.summaryText, 206 + session.compactText ?? "", 207 + session.reasoningSummaryText ?? "", 208 + session.cwd, 209 + session.model, 210 + session.startedAt, 211 + session.endedAt, 212 + session.messages.length, 213 + rawFileMtime, 214 + rawFileSize, 215 + indexVersion, 216 + existing.id, 222 217 ); 223 218 } else { 224 - db.run( 219 + db.prepare( 225 220 ` 226 221 INSERT INTO sessions ( 227 222 session_uuid, file_path, title, summary_text, compact_text, reasoning_summary_text, ··· 229 224 message_count, raw_file_mtime, raw_file_size, index_version 230 225 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 231 226 `, 232 - [ 233 - session.sessionUuid, 234 - session.filePath, 235 - session.title, 236 - session.summaryText, 237 - session.compactText ?? "", 238 - session.reasoningSummaryText ?? "", 239 - session.cwd, 240 - session.model, 241 - session.startedAt, 242 - session.endedAt, 243 - session.messages.length, 244 - rawFileMtime, 245 - rawFileSize, 246 - indexVersion, 247 - ], 227 + ).run( 228 + session.sessionUuid, 229 + session.filePath, 230 + session.title, 231 + session.summaryText, 232 + session.compactText ?? "", 233 + session.reasoningSummaryText ?? "", 234 + session.cwd, 235 + session.model, 236 + session.startedAt, 237 + session.endedAt, 238 + session.messages.length, 239 + rawFileMtime, 240 + rawFileSize, 241 + indexVersion, 248 242 ); 249 243 } 250 244 251 245 const sessionRow = db 252 - .query<{ id: number }, [string]>("SELECT id FROM sessions WHERE session_uuid = ? LIMIT 1") 246 + .prepare<[string], { id: number }>("SELECT id FROM sessions WHERE session_uuid = ? LIMIT 1") 253 247 .get(session.sessionUuid) as { id: number }; 254 248 255 - db.run("DELETE FROM messages_fts WHERE session_uuid = ?", [session.sessionUuid]); 256 - db.run("DELETE FROM messages WHERE session_uuid = ?", [session.sessionUuid]); 257 - db.run("DELETE FROM sessions_fts WHERE rowid = ? OR session_uuid = ?", [sessionRow.id, session.sessionUuid]); 249 + db.prepare("DELETE FROM messages_fts WHERE session_uuid = ?").run(session.sessionUuid); 250 + db.prepare("DELETE FROM messages WHERE session_uuid = ?").run(session.sessionUuid); 251 + db.prepare("DELETE FROM sessions_fts WHERE rowid = ? OR session_uuid = ?").run(sessionRow.id, session.sessionUuid); 258 252 259 - db.run( 253 + db.prepare( 260 254 ` 261 255 INSERT INTO sessions_fts(rowid, title, summary_text, compact_text, reasoning_summary_text, session_uuid) 262 256 VALUES (?, ?, ?, ?, ?, ?) 263 257 `, 264 - [ 265 - sessionRow.id, 266 - tokenizedText(session.title), 267 - tokenizedText(session.summaryText), 268 - tokenizedText(session.compactText ?? ""), 269 - tokenizedText(session.reasoningSummaryText ?? ""), 270 - session.sessionUuid, 271 - ], 258 + ).run( 259 + sessionRow.id, 260 + tokenizedText(session.title), 261 + tokenizedText(session.summaryText), 262 + tokenizedText(session.compactText ?? ""), 263 + tokenizedText(session.reasoningSummaryText ?? ""), 264 + session.sessionUuid, 272 265 ); 273 266 274 - const messageStmt = db.prepare<unknown, [number, string, number, string, string, string, string]>(` 267 + const messageStmt = db.prepare<[number, string, number, string, string, string, string]>(` 275 268 INSERT INTO messages (session_id, session_uuid, seq, role, content_text, timestamp, source_kind) 276 269 VALUES (?, ?, ?, ?, ?, ?, ?) 277 270 `); 278 - const ftsStmt = db.prepare<unknown, [number, string, string, number, string, string]>(` 271 + const ftsStmt = db.prepare<[number, string, string, number, string, string]>(` 279 272 INSERT INTO messages_fts(rowid, content_text, session_uuid, seq, role, timestamp) 280 273 VALUES (?, ?, ?, ?, ?, ?) 281 274 `); ··· 308 301 tx(); 309 302 } 310 303 311 - export function getSessionRecord(db: Database, sessionUuid: string): SessionRecord | null { 304 + export function getSessionRecord(db: Db, sessionUuid: string): SessionRecord | null { 312 305 const row = db 313 - .query<SessionRecord & { filePath: string }, [string]>(` 306 + .prepare<[string], SessionRecord & { filePath: string }>(` 314 307 SELECT 315 308 session_uuid AS sessionUuid, 316 309 file_path AS filePath, ··· 325 318 WHERE session_uuid = ? 326 319 LIMIT 1 327 320 `) 328 - .get(sessionUuid) as (SessionRecord & { filePath: string }) | null; 321 + .get(sessionUuid) as (SessionRecord & { filePath: string }) | undefined; 329 322 330 323 if (!row) return null; 331 324 return row; 332 325 } 333 326 334 - function ensureTextColumn(db: Database, tableName: string, columnName: string): void { 327 + function ensureTextColumn(db: Db, tableName: string, columnName: string): void { 335 328 const columns = db 336 - .query(`PRAGMA table_info(${tableName})`) 329 + .prepare(`PRAGMA table_info(${tableName})`) 337 330 .all() as Array<{ name?: string }>; 338 331 339 332 if (columns.some((column) => column.name === columnName)) return; 340 - db.run(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} TEXT NOT NULL DEFAULT ''`); 333 + db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} TEXT NOT NULL DEFAULT ''`); 341 334 } 342 335 343 336 export function getMessagesForRange( 344 - db: Database, 337 + db: Db, 345 338 sessionUuid: string, 346 339 startSeq: number, 347 340 endSeq: number, 348 341 ): MessageRecord[] { 349 342 return db 350 - .query<MessageRecord, [string, number, number]>(` 343 + .prepare<[string, number, number], MessageRecord>(` 351 344 SELECT 352 345 session_uuid AS sessionUuid, 353 346 seq, ··· 363 356 } 364 357 365 358 export function getMessagesForPage( 366 - db: Database, 359 + db: Db, 367 360 sessionUuid: string, 368 361 offset: number, 369 362 limit: number, 370 363 ): MessageRecord[] { 371 364 return db 372 - .query<MessageRecord, [string, number, number]>(` 365 + .prepare<[string, number, number], MessageRecord>(` 373 366 SELECT 374 367 session_uuid AS sessionUuid, 375 368 seq, ··· 385 378 .all(sessionUuid, limit, offset) as MessageRecord[]; 386 379 } 387 380 388 - export function listSessions(db: Database, query: SessionListQuery): SessionListEntry[] { 381 + export function listSessions(db: Db, query: SessionListQuery): SessionListEntry[] { 389 382 const conditions: string[] = []; 390 383 const params: SqlParams = []; 391 384 if (query.cwd) { ··· 409 402 params.push(query.limit); 410 403 411 404 return db 412 - .query<SessionListEntry, typeof params>(` 405 + .prepare<typeof params, SessionListEntry>(` 413 406 SELECT 414 407 session_uuid AS sessionUuid, 415 408 title, ··· 426 419 .all(...params) as SessionListEntry[]; 427 420 } 428 421 429 - export function getStatsCounts(db: Database): { 422 + export function getStatsCounts(db: Db): { 430 423 sessionCount: number; 431 424 messageCount: number; 432 425 earliestStartedAt: string | null; ··· 434 427 lastSyncAt: string | null; 435 428 } { 436 429 const row = db 437 - .query(` 430 + .prepare(` 438 431 SELECT 439 432 COUNT(*) AS sessionCount, 440 433 COALESCE(SUM(message_count), 0) AS messageCount, ··· 453 446 return row; 454 447 } 455 448 456 - export function getTopCwds(db: Database, limit: number): CwdCount[] { 449 + export function getTopCwds(db: Db, limit: number): CwdCount[] { 457 450 return db 458 - .query<CwdCount, [number]>(` 451 + .prepare<[number], CwdCount>(` 459 452 SELECT cwd, COUNT(*) AS count 460 453 FROM sessions 461 454 WHERE cwd != ''
+1 -1
eval/compare.test.ts
··· 1 - import { afterEach, describe, expect, test } from "bun:test"; 1 + import { afterEach, describe, expect, test } from "vitest"; 2 2 import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; 3 3 import { tmpdir } from "node:os"; 4 4 import { join } from "node:path";
+1 -1
eval/manual-eval-core.test.ts
··· 1 - import { describe, expect, test } from "bun:test"; 1 + import { describe, expect, test } from "vitest"; 2 2 import { evaluateManualQuery } from "./manual-eval-core"; 3 3 import type { FindResult } from "../types"; 4 4
+24 -12
eval/perf-bench.ts
··· 1 - #!/usr/bin/env bun 1 + #!/usr/bin/env -S node --import tsx 2 2 3 3 import { mkdirSync, writeFileSync } from "node:fs"; 4 4 import { homedir, tmpdir } from "node:os"; 5 5 import { join, resolve } from "node:path"; 6 6 import { existsSync } from "node:fs"; 7 + import { spawn as childSpawn } from "node:child_process"; 7 8 8 9 interface PerQueryRecord { 9 10 query: string; ··· 40 41 ]; 41 42 42 43 const RUNS_PER_QUERY = 5; // 第 1 次作为 warmup,统计后 4 次 43 - const ROOT = resolve(import.meta.dir, ".."); 44 + const ROOT = resolve(import.meta.dirname, ".."); 44 45 const OUT_BASE = resolve(ROOT, "data", "cxs-perf"); 45 46 46 47 interface CliArgs { ··· 85 86 86 87 async function run(cmd: string[]): Promise<RunResult> { 87 88 const t0 = performance.now(); 88 - const proc = Bun.spawn(cmd, { cwd: ROOT, stdout: "pipe", stderr: "pipe" }); 89 - const [stdout, stderr, exitCode] = await Promise.all([ 90 - new Response(proc.stdout).text(), 91 - new Response(proc.stderr).text(), 92 - proc.exited, 93 - ]); 89 + const result = await spawnAndCapture(cmd, ROOT); 94 90 const ms = performance.now() - t0; 95 - return { stdout, stderr, exitCode, ms }; 91 + return { ...result, ms }; 92 + } 93 + 94 + function spawnAndCapture(cmd: string[], cwd: string): Promise<{ stdout: string; stderr: string; exitCode: number }> { 95 + return new Promise((resolve, reject) => { 96 + const proc = childSpawn(cmd[0]!, cmd.slice(1), { cwd, stdio: ["ignore", "pipe", "pipe"] }); 97 + let stdout = ""; 98 + let stderr = ""; 99 + proc.stdout!.setEncoding("utf8"); 100 + proc.stderr!.setEncoding("utf8"); 101 + proc.stdout!.on("data", (chunk: string) => { stdout += chunk; }); 102 + proc.stderr!.on("data", (chunk: string) => { stderr += chunk; }); 103 + proc.on("error", reject); 104 + proc.on("close", (code) => { 105 + resolve({ stdout, stderr, exitCode: code ?? 0 }); 106 + }); 107 + }); 96 108 } 97 109 98 110 async function runOrThrow(cmd: string[]): Promise<RunResult> { ··· 138 150 } 139 151 140 152 // 1. sync 141 - const syncRun = await runOrThrow(["./bin/cxs", "sync", "--db", args.db, "--root", args.root, "--json"]); 153 + const syncRun = await runOrThrow([process.execPath, "--import", "tsx", "cli.ts", "sync", "--db", args.db, "--root", args.root, "--json"]); 142 154 const syncMs = syncRun.ms; 143 155 let sessionCount = 0; 144 156 try { ··· 153 165 for (const q of BENCH_QUERIES) { 154 166 const samplesAll: number[] = []; 155 167 for (let i = 0; i < RUNS_PER_QUERY; i++) { 156 - const r = await runOrThrow(["./bin/cxs", "find", q, "--db", args.db, "--limit", "10", "--json"]); 168 + const r = await runOrThrow([process.execPath, "--import", "tsx", "cli.ts", "find", q, "--db", args.db, "--limit", "10", "--json"]); 157 169 samplesAll.push(r.ms); 158 170 } 159 171 // 丢弃首次 warmup ··· 169 181 } 170 182 171 183 // 3. stats -> dbSizeBytes 172 - const statsRun = await runOrThrow(["./bin/cxs", "stats", "--db", args.db, "--json"]); 184 + const statsRun = await runOrThrow([process.execPath, "--import", "tsx", "cli.ts", "stats", "--db", args.db, "--json"]); 173 185 let dbSizeBytes = 0; 174 186 try { 175 187 const parsed = JSON.parse(statsRun.stdout) as { dbSizeBytes?: number; sessionCount?: number };
+28 -24
eval/run-manual-eval.ts
··· 1 - #!/usr/bin/env bun 1 + #!/usr/bin/env -S node --import tsx 2 2 3 3 import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; 4 + import { spawn as childSpawn } from "node:child_process"; 4 5 import { join, resolve } from "node:path"; 5 6 import { evaluateManualQuery, type ManualQuery, type PassMark } from "./manual-eval-core"; 6 7 import type { FindResult } from "../types"; ··· 10 11 results: FindResult[]; 11 12 } 12 13 13 - const ROOT = resolve(import.meta.dir, ".."); 14 - const QUERY_FILE = resolve(import.meta.dir, "manual-queries.json"); 14 + const ROOT = resolve(import.meta.dirname, ".."); 15 + const QUERY_FILE = resolve(import.meta.dirname, "manual-queries.json"); 15 16 const OUT_BASE = resolve(ROOT, "data", "cxs-eval"); 16 17 17 18 const stamp = new Date().toISOString().replace(/[:.]/g, "-"); ··· 48 49 49 50 for (const [index, item] of queries.entries()) { 50 51 const prefix = String(index + 1).padStart(2, "0"); 51 - const findJson = await runCommand(["./bin/cxs", "find", item.query, "--limit", "5", "--json"]); 52 - const findText = await runCommand(["./bin/cxs", "find", item.query, "--limit", "5"]); 52 + const findJson = await runCommand([process.execPath, "--import", "tsx", "cli.ts", "find", item.query, "--limit", "5", "--json"]); 53 + const findText = await runCommand([process.execPath, "--import", "tsx", "cli.ts", "find", item.query, "--limit", "5"]); 53 54 const findJsonPath = join(outDir, `${prefix}-${item.id}.find.json`); 54 55 const findTxtPath = join(outDir, `${prefix}-${item.id}.find.txt`); 55 56 writeFileSync(findJsonPath, findJson); ··· 168 169 return { 169 170 kind: "read-range", 170 171 args: [ 171 - "./bin/cxs", 172 + process.execPath, "--import", "tsx", "cli.ts", 172 173 "read-range", 173 174 top.sessionUuid, 174 175 "--seq", ··· 184 185 return { 185 186 kind: "read-page", 186 187 args: [ 187 - "./bin/cxs", 188 + process.execPath, "--import", "tsx", "cli.ts", 188 189 "read-page", 189 190 top.sessionUuid, 190 191 "--offset", ··· 195 196 }; 196 197 } 197 198 198 - async function runCommand(args: string[]): Promise<string> { 199 - const proc = Bun.spawn(args, { 200 - cwd: ROOT, 201 - stdout: "pipe", 202 - stderr: "pipe", 199 + function runCommand(args: string[]): Promise<string> { 200 + return new Promise((resolve, reject) => { 201 + const proc = childSpawn(args[0]!, args.slice(1), { 202 + cwd: ROOT, 203 + stdio: ["ignore", "pipe", "pipe"], 204 + }); 205 + let stdout = ""; 206 + let stderr = ""; 207 + proc.stdout!.setEncoding("utf8"); 208 + proc.stderr!.setEncoding("utf8"); 209 + proc.stdout!.on("data", (chunk: string) => { stdout += chunk; }); 210 + proc.stderr!.on("data", (chunk: string) => { stderr += chunk; }); 211 + proc.on("error", reject); 212 + proc.on("close", (code) => { 213 + if (code !== 0) { 214 + reject(new Error(`command failed: ${args.join(" ")}\n${stderr}`)); 215 + } else { 216 + resolve(stdout); 217 + } 218 + }); 203 219 }); 204 - 205 - const [stdout, stderr, exitCode] = await Promise.all([ 206 - new Response(proc.stdout).text(), 207 - new Response(proc.stderr).text(), 208 - proc.exited, 209 - ]); 210 - 211 - if (exitCode !== 0) { 212 - throw new Error(`command failed: ${args.join(" ")}\n${stderr}`); 213 - } 214 - 215 - return stdout; 216 220 }
+4 -4
indexer.test.ts
··· 1 - import { afterEach, describe, expect, test } from "bun:test"; 1 + import { afterEach, describe, expect, test } from "vitest"; 2 2 import { chmodSync, existsSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; 3 3 import { tmpdir } from "node:os"; 4 4 import { join } from "node:path"; ··· 35 35 expect(failure.summary.errorDetails[0]?.message.length).toBeGreaterThan(0); 36 36 37 37 const db = openReadDb(dbPath); 38 - const row = db.query("SELECT COUNT(*) AS count FROM sessions").get() as { count: number }; 38 + const row = db.prepare("SELECT COUNT(*) AS count FROM sessions").get() as { count: number }; 39 39 db.close(); 40 40 expect(row.count).toBe(0); 41 41 ··· 54 54 expect(summary.errorDetails[0]?.filePath).toBe(badFilePath); 55 55 56 56 const db = openReadDb(dbPath); 57 - const row = db.query("SELECT COUNT(*) AS count FROM sessions").get() as { count: number }; 57 + const row = db.prepare("SELECT COUNT(*) AS count FROM sessions").get() as { count: number }; 58 58 db.close(); 59 59 expect(row.count).toBe(1); 60 60 }); ··· 186 186 const child = spawn( 187 187 process.execPath, 188 188 ["--eval", script, lockPath, String(holdMs)], 189 - { cwd: import.meta.dir, stdio: ["ignore", "pipe", "pipe"] }, 189 + { cwd: import.meta.dirname, stdio: ["ignore", "pipe", "pipe"] }, 190 190 ); 191 191 192 192 let settled = false;
+19 -22
package.json
··· 13 13 "url": "https://github.com/catoncat/cxs/issues" 14 14 }, 15 15 "bin": { 16 - "cxs": "./bin/cxs" 16 + "cxs": "./dist/cli.js" 17 17 }, 18 18 "files": [ 19 - "bin/", 20 - "cli.ts", 21 - "db.ts", 22 - "env.ts", 23 - "format.ts", 24 - "indexer.ts", 25 - "parser.ts", 26 - "query.ts", 27 - "ranking.ts", 28 - "sync-lock.ts", 29 - "tokenize.ts", 30 - "types.ts", 31 - "eval/" 19 + "dist/", 20 + "README.md", 21 + "LICENSE" 32 22 ], 33 23 "keywords": [ 34 24 "codex", ··· 39 29 "session" 40 30 ], 41 31 "engines": { 42 - "bun": ">=1.3.0" 32 + "node": ">=22" 43 33 }, 44 34 "scripts": { 45 - "test": "bun test", 46 - "check": "tsc --noEmit && bun test", 47 - "cxs": "bun run ./cli.ts", 48 - "eval:manual": "bun run ./eval/run-manual-eval.ts", 49 - "eval:perf": "bun run ./eval/perf-bench.ts" 35 + "build": "esbuild cli.ts --bundle --platform=node --target=node22 --format=esm --external:better-sqlite3 --external:chalk --external:commander --outfile=dist/cli.js && node scripts/post-build.mjs", 36 + "prepublishOnly": "npm run build", 37 + "test": "vitest run", 38 + "check": "tsc --noEmit && vitest run", 39 + "cxs": "tsx ./cli.ts", 40 + "eval:manual": "tsx ./eval/run-manual-eval.ts", 41 + "eval:perf": "tsx ./eval/perf-bench.ts" 50 42 }, 51 43 "dependencies": { 44 + "better-sqlite3": "^12.9.0", 52 45 "chalk": "^5.6.2", 53 46 "commander": "^14.0.3" 54 47 }, 55 48 "devDependencies": { 56 - "@types/bun": "^1.3.13", 57 - "typescript": "^6.0.3" 49 + "@types/better-sqlite3": "^7.6.13", 50 + "@types/node": "^25.6.0", 51 + "esbuild": "^0.28.0", 52 + "tsx": "^4.21.0", 53 + "typescript": "^6.0.3", 54 + "vitest": "^4.1.5" 58 55 } 59 56 }
+1 -1
parser.test.ts
··· 1 - import { afterEach, describe, expect, test } from "bun:test"; 1 + import { afterEach, describe, expect, test } from "vitest"; 2 2 import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; 3 3 import { tmpdir } from "node:os"; 4 4 import { join } from "node:path";
+19 -25
query.test.ts
··· 1 - import { afterEach, describe, expect, test } from "bun:test"; 2 - import { Database } from "bun:sqlite"; 1 + import { afterEach, describe, expect, test } from "vitest"; 2 + import Database from "better-sqlite3"; 3 3 import { spawn } from "node:child_process"; 4 4 import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; 5 5 import { tmpdir } from "node:os"; ··· 31 31 tempDirs.push(base); 32 32 const stateDbPath = join(base, "state.sqlite"); 33 33 const db = new Database(stateDbPath); 34 - db.run(` 34 + db.exec(` 35 35 CREATE TABLE threads ( 36 36 id TEXT PRIMARY KEY, 37 37 rollout_path TEXT NOT NULL, ··· 40 40 updated_at_ms INTEGER 41 41 ) 42 42 `); 43 - db.run( 44 - "INSERT INTO threads (id, rollout_path, cwd, title, updated_at_ms) VALUES (?, ?, ?, ?, ?)", 45 - ["11111111-1111-4111-8111-111111111111", "/tmp/a.jsonl", "/tmp/project", "older", 100], 46 - ); 47 - db.run( 48 - "INSERT INTO threads (id, rollout_path, cwd, title, updated_at_ms) VALUES (?, ?, ?, ?, ?)", 49 - ["22222222-2222-4222-8222-222222222222", "/tmp/b.jsonl", "/tmp/project", "newer", 200], 50 - ); 51 - db.run( 43 + const insertThread = db.prepare( 52 44 "INSERT INTO threads (id, rollout_path, cwd, title, updated_at_ms) VALUES (?, ?, ?, ?, ?)", 53 - ["33333333-3333-4333-8333-333333333333", "/tmp/c.jsonl", "/tmp/other", "other", 300], 54 45 ); 46 + insertThread.run("11111111-1111-4111-8111-111111111111", "/tmp/a.jsonl", "/tmp/project", "older", 100); 47 + insertThread.run("22222222-2222-4222-8222-222222222222", "/tmp/b.jsonl", "/tmp/project", "newer", 200); 48 + insertThread.run("33333333-3333-4333-8333-333333333333", "/tmp/c.jsonl", "/tmp/other", "other", 300); 55 49 db.close(); 56 50 57 51 const result = getCurrentSessions(stateDbPath, "/tmp/project", 10); ··· 68 62 tempDirs.push(base); 69 63 const stateDbPath = join(base, "state.sqlite"); 70 64 const db = new Database(stateDbPath); 71 - db.run("CREATE TABLE other (id INTEGER PRIMARY KEY)"); 65 + db.exec("CREATE TABLE other (id INTEGER PRIMARY KEY)"); 72 66 db.close(); 73 67 74 68 let caught: unknown = null; ··· 88 82 const db = new Database(stateDbPath); 89 83 // Table exists but lacks rollout_path & updated_at_ms — simulates an 90 84 // upstream rename of the columns we SELECT in getCurrentSessions. 91 - db.run(` 85 + db.exec(` 92 86 CREATE TABLE threads ( 93 87 id TEXT PRIMARY KEY, 94 88 cwd TEXT NOT NULL, ··· 288 282 289 283 const db = openReadDb(dbPath); 290 284 const row = db 291 - .query<{ summaryText: string }, [string]>("SELECT summary_text AS summaryText FROM sessions WHERE session_uuid = ? LIMIT 1") 285 + .prepare<[string], { summaryText: string }>("SELECT summary_text AS summaryText FROM sessions WHERE session_uuid = ? LIMIT 1") 292 286 .get("eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee") as { summaryText: string } | null; 293 287 db.close(); 294 288 ··· 722 716 const summary = await syncSessions({ dbPath, rootDir: join(base, "sessions") }); 723 717 expect(summary.added).toBe(1); 724 718 725 - const queryModuleUrl = pathToFileURL(join(import.meta.dir, "query.ts")).href; 719 + const queryModuleUrl = pathToFileURL(join(import.meta.dirname, "query.ts")).href; 726 720 const blocker = await holdExclusiveLock(dbPath, 400); 727 721 const tasks = [ 728 722 ...Array.from({ length: 6 }, () => runReadChild(queryModuleUrl, dbPath, "find", "reverse-i-search")), ··· 770 764 `; 771 765 const child = spawn( 772 766 process.execPath, 773 - ["--eval", script, queryModuleUrl, dbPath, command, query ?? ""], 774 - { cwd: import.meta.dir, stdio: ["ignore", "ignore", "pipe"] }, 767 + ["--import", "tsx", "--eval", script, queryModuleUrl, dbPath, command, query ?? ""], 768 + { cwd: import.meta.dirname, stdio: ["ignore", "ignore", "pipe"] }, 775 769 ); 776 770 777 771 let stderr = ""; ··· 792 786 ): Promise<{ done: Promise<number | null> }> { 793 787 return new Promise((resolve, reject) => { 794 788 const script = ` 795 - import { Database } from "bun:sqlite"; 789 + import Database from "better-sqlite3"; 796 790 const [dbPath, holdMs] = process.argv.slice(1); 797 791 const db = new Database(dbPath); 798 - db.run("PRAGMA busy_timeout=5000"); 799 - db.run("PRAGMA locking_mode=EXCLUSIVE"); 800 - db.run("BEGIN EXCLUSIVE"); 792 + db.pragma("busy_timeout = 5000"); 793 + db.pragma("locking_mode = EXCLUSIVE"); 794 + db.exec("BEGIN EXCLUSIVE"); 801 795 console.log("locked"); 802 796 setTimeout(() => { 803 - db.run("COMMIT"); 797 + db.exec("COMMIT"); 804 798 db.close(); 805 799 }, Number(holdMs)); 806 800 `; 807 801 const child = spawn( 808 802 process.execPath, 809 803 ["--eval", script, dbPath, String(holdMs)], 810 - { cwd: import.meta.dir, stdio: ["ignore", "pipe", "pipe"] }, 804 + { cwd: import.meta.dirname, stdio: ["ignore", "pipe", "pipe"] }, 811 805 ); 812 806 813 807 let settled = false;
+18 -18
query.ts
··· 1 - import { Database } from "bun:sqlite"; 2 - import type { SQLQueryBindings } from "bun:sqlite"; 1 + import Database from "better-sqlite3"; 3 2 import { statSync } from "node:fs"; 4 3 import { 5 4 getMessagesForPage, ··· 24 23 } from "./types"; 25 24 26 25 export { classifyQueryProfile } from "./ranking"; 27 - type SqlParams = SQLQueryBindings[]; 26 + type Db = Database.Database; 27 + type SqlParams = unknown[]; 28 28 29 29 // Why: Codex state db lives outside cxs's control — its schema can drift 30 30 // across upstream releases. CLI translates this into a structured --json ··· 136 136 ); 137 137 } 138 138 const candidates = db 139 - .query<CurrentSessionCandidate, [string, number]>(` 139 + .prepare<[string, number], CurrentSessionCandidate>(` 140 140 SELECT 141 141 id AS sessionUuid, 142 142 title, ··· 182 182 } 183 183 184 184 function resolveAnchorSeq( 185 - db: Database, 185 + db: Db, 186 186 sessionUuid: string, 187 187 seq?: number, 188 188 query?: string, ··· 199 199 throw new Error("read-range requires explicit session_uuid plus either --seq or --query"); 200 200 } 201 201 202 - function searchTopHitInSession(db: Database, sessionUuid: string, query: string): FindResult | null { 202 + function searchTopHitInSession(db: Db, sessionUuid: string, query: string): FindResult | null { 203 203 const rows = searchMessageHits(db, query, 20, sessionUuid); 204 204 const result = rerankHits(rows, query, 1)[0]; 205 205 return result ?? null; 206 206 } 207 207 208 - function searchMessageHits(db: Database, query: string, limit: number, sessionUuid?: string): RawHitRow[] { 208 + function searchMessageHits(db: Db, query: string, limit: number, sessionUuid?: string): RawHitRow[] { 209 209 const normalized = query.trim(); 210 210 if (!normalized) return []; 211 211 ··· 222 222 return searchByFts(db, terms, limit, sessionUuid); 223 223 } 224 224 225 - function searchSessionHits(db: Database, query: string, limit: number): RawHitRow[] { 225 + function searchSessionHits(db: Db, query: string, limit: number): RawHitRow[] { 226 226 const normalized = query.trim(); 227 227 if (!normalized || !tableExists(db, "sessions_fts")) return []; 228 228 ··· 233 233 } 234 234 235 235 function searchByFts( 236 - db: Database, 236 + db: Db, 237 237 terms: string[], 238 238 limit: number, 239 239 sessionUuid?: string, ··· 249 249 params.push(limit); 250 250 251 251 return db 252 - .query<RawHitRow, typeof params>(` 252 + .prepare<typeof params, RawHitRow>(` 253 253 SELECT 254 254 s.session_uuid AS sessionUuid, 255 255 s.title AS title, ··· 275 275 } 276 276 277 277 function searchSessionsByFts( 278 - db: Database, 278 + db: Db, 279 279 query: string, 280 280 terms: string[], 281 281 limit: number, 282 282 ): RawHitRow[] { 283 283 const matchExpr = buildFtsMatch(terms); 284 284 const rows = db 285 - .query<RawHitRow, [string, number]>(` 285 + .prepare<[string, number], RawHitRow>(` 286 286 SELECT 287 287 s.session_uuid AS sessionUuid, 288 288 s.title AS title, ··· 311 311 })); 312 312 } 313 313 314 - function searchByLike(db: Database, query: string, limit: number, sessionUuid?: string): RawHitRow[] { 314 + function searchByLike(db: Db, query: string, limit: number, sessionUuid?: string): RawHitRow[] { 315 315 const conditions = ["lower(m.content_text) LIKE ? ESCAPE '\\'"]; 316 316 const params: SqlParams = [`%${escapeLike(query.toLowerCase())}%`]; 317 317 if (sessionUuid) { ··· 321 321 params.push(limit); 322 322 323 323 const rows = db 324 - .query<RawHitRow & { contentText: string }, typeof params>(` 324 + .prepare<typeof params, RawHitRow & { contentText: string }>(` 325 325 SELECT 326 326 s.session_uuid AS sessionUuid, 327 327 s.title AS title, ··· 352 352 })); 353 353 } 354 354 355 - function tableExists(db: Database, tableName: string): boolean { 355 + function tableExists(db: Db, tableName: string): boolean { 356 356 const row = db 357 - .query<unknown, [string]>("SELECT 1 FROM sqlite_master WHERE name = ? LIMIT 1") 357 + .prepare<[string], unknown>("SELECT 1 FROM sqlite_master WHERE name = ? LIMIT 1") 358 358 .get(tableName); 359 359 return Boolean(row); 360 360 } ··· 362 362 // Why: PRAGMA table_info doesn't accept bound parameters, so callers MUST 363 363 // pass a hard-coded identifier. Returns required columns that the table is 364 364 // missing, in input order; empty array means schema is good. 365 - function findMissingColumns(db: Database, tableName: string, required: readonly string[]): string[] { 365 + function findMissingColumns(db: Db, tableName: string, required: readonly string[]): string[] { 366 366 const rows = db 367 - .query<{ name: string }, []>(`PRAGMA table_info(${tableName})`) 367 + .prepare<[], { name: string }>(`PRAGMA table_info(${tableName})`) 368 368 .all() as Array<{ name: string }>; 369 369 const present = new Set(rows.map((row) => row.name)); 370 370 return required.filter((column) => !present.has(column));
+22
scripts/post-build.mjs
··· 1 + #!/usr/bin/env node 2 + // Adds the Node shebang to dist/cli.js and marks it executable, so that npm's 3 + // bin field can point straight at the compiled file. Cross-platform; chmod is 4 + // a no-op on Windows but npm wraps the bin with a cmd shim there anyway. 5 + 6 + import { readFileSync, writeFileSync, chmodSync } from "node:fs"; 7 + import { fileURLToPath } from "node:url"; 8 + import { dirname, resolve } from "node:path"; 9 + 10 + const here = dirname(fileURLToPath(import.meta.url)); 11 + const distCli = resolve(here, "..", "dist", "cli.js"); 12 + 13 + const SHEBANG = "#!/usr/bin/env node\n"; 14 + const original = readFileSync(distCli, "utf8"); 15 + 16 + // Source file may carry its own shebang (e.g. #!/usr/bin/env bun for dev). 17 + // Always normalize to node for the published artifact. 18 + const stripped = original.startsWith("#!") ? original.slice(original.indexOf("\n") + 1) : original; 19 + writeFileSync(distCli, SHEBANG + stripped); 20 + 21 + chmodSync(distCli, 0o755); 22 + console.log(`post-build: prepared ${distCli}`);
+1 -1
sync-lock.test.ts
··· 1 - import { afterEach, describe, expect, test } from "bun:test"; 1 + import { afterEach, describe, expect, test } from "vitest"; 2 2 import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; 3 3 import { tmpdir } from "node:os"; 4 4 import { join } from "node:path";
+2 -2
tsconfig.json
··· 8 8 "noEmit": true, 9 9 "allowImportingTsExtensions": true, 10 10 "skipLibCheck": true, 11 - "types": ["bun-types"] 11 + "resolveJsonModule": true 12 12 }, 13 13 "include": ["**/*.ts"], 14 - "exclude": ["node_modules", "data"] 14 + "exclude": ["node_modules", "dist", "data"] 15 15 }