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.

docs(skill): 完善安装路径与 agent 指引

Entire-Checkpoint: 809cef794e56

cat 4a365cf5 51bbe46b

+93 -8
+13 -1
.agents/skills/cxs/SKILL.md
··· 1 1 --- 2 2 name: cxs 3 - description: "用 cxs 对 Codex session 日志做渐进式检索:find -> read-range -> read-page。用于用户说之前、上次、前几天、昨天、我记得我配过、我试过、我们讨论过、翻一下之前的 session、找那次我跑通的命令、之前那个 codex 对话、以前 debug 过的问题、历史对话、codex 历史、session 历史,或者需要在 ~/.codex/sessions 里定位一段对话、但又不想把整批 JSONL 拉进来读。不是用来做当前仓库代码搜索、读当前文件、查外部文档、总结今天提交或收尾当前会话。Also triggers on English: 'last time I', 'earlier session', 'did we already', 'I remember configuring', 'previous codex chat', 'search my codex history'." 3 + description: "用于用户要找本机 Codex 历史会话或 ~/.codex/sessions 上下文:之前、上次、前几天、我记得、我们讨论过、翻旧 session、找那次命令、历史对话、Codex history、previous/earlier/last Codex chat。不要用于当前仓库代码搜索、外部文档、今日提交/日报或当前会话收尾。" 4 4 --- 5 5 6 6 # cxs ··· 14 14 ``` 15 15 16 16 CLI install guide: https://github.com/catoncat/cxs#cli-install-guide 17 + 18 + 这个 skill 只提供 agent 工作流,不安装 `cxs` CLI 本体。若用户询问安装方式,指向 README 的 CLI install guide,并提醒安装或更新 skill 后需要重启 Codex / 开新 session。 17 19 18 20 ## 路径前提 19 21 ··· 30 32 "${CXS_BIN:-cxs}" <subcommand> ... 31 33 ``` 32 34 35 + 使用前先验证命令面确实是 cxs: 36 + 37 + ```bash 38 + "${CXS_BIN:-cxs}" --version 39 + "${CXS_BIN:-cxs}" --help 40 + ``` 41 + 42 + 如果 `--version` 没有输出 cxs 版本,或 `--help` 没有列出 `sync/find/read-range/read-page/list/stats/current`,不要继续猜;改用 `CXS_BIN=/absolute/path/to/bin/cxs`,或先让用户完成 CLI install guide。 43 + 33 44 这样可以同时兼容: 34 45 35 46 - 你自己安装到 `PATH` 的 `cxs` ··· 61 72 ## 前置 62 73 63 74 - 先用 `stats --json` 看 `dbPath`、`lastSyncAt`、`sessionCount` 75 + - 如果 `stats --json` 提示索引不存在,先跑 `sync` 64 76 - 用户明确说“最近那次”“我刚做过”,但 `lastSyncAt` 很旧或 `find`/`list` 零结果时,先跑 `sync` 65 77 - `sync` 默认严格模式;任一文件失败都会非零退出且不提交半截索引 66 78 - 只有用户接受部分成功时才加 `--best-effort`
+25 -1
README.md
··· 38 38 39 39 ## CLI Install Guide 40 40 41 + `cxs` 目前推荐从源码安装,适合本机工具或 agent sidecar 使用: 42 + 41 43 ```bash 42 44 git clone https://github.com/catoncat/cxs.git 43 45 cd cxs 44 46 bun install 45 47 export CXS_BIN="$PWD/bin/cxs" 48 + "$CXS_BIN" --version 49 + "$CXS_BIN" --help 50 + ``` 51 + 52 + `--version` 应输出当前 `package.json` 里的版本号;`--help` 应显示 `sync`、`find`、`read-range`、`read-page`、`list`、`stats`、`current`。 53 + 54 + 首次使用前建立索引: 55 + 56 + ```bash 57 + "$CXS_BIN" sync 58 + "$CXS_BIN" stats --json 59 + ``` 60 + 61 + 如果希望直接使用 `cxs` 命令,可以把仓库里的启动脚本链接到 PATH: 62 + 63 + ```bash 64 + mkdir -p ~/bin 65 + ln -sf "$PWD/bin/cxs" ~/bin/cxs 66 + export PATH="$HOME/bin:$PATH" 67 + cxs --version 46 68 ``` 47 69 48 70 要求: ··· 217 239 218 240 注意: 219 241 220 - - 最稳的做法是先让 `cxs` 命令可执行,或设置 `CXS_BIN=/absolute/path/to/bin/cxs` 242 + - `npx skills add` 只安装 agent skill,不安装 CLI 本体 243 + - 安装或更新 skill 后,需要重启 Codex / 开新 session 才会被 agent 发现 244 + - 推荐先按 CLI install guide 让 `cxs` 可执行,或设置 `CXS_BIN=/absolute/path/to/bin/cxs`
+12 -1
bin/cxs
··· 1 1 #!/usr/bin/env bash 2 2 set -euo pipefail 3 3 4 - ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 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)" 5 16 exec bun run "$ROOT/cli.ts" "$@"
+32 -3
cli.test.ts
··· 1 1 import { afterEach, describe, expect, test } from "bun:test"; 2 2 import { Database } from "bun:sqlite"; 3 - import { chmodSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; 3 + import { chmodSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; 4 4 import { tmpdir } from "node:os"; 5 5 import { join } from "node:path"; 6 6 import { INDEX_VERSION } from "./env"; ··· 23 23 }); 24 24 25 25 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 + 26 40 test("help only shows current/sync/find/read-range/read-page/list/stats", async () => { 27 41 const result = await runCli(["--help"]); 28 42 expect(result.exitCode).toBe(0); ··· 337 351 }); 338 352 } 339 353 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 + 340 361 async function runCli(args: string[]): Promise<{ exitCode: number; stdout: string; stderr: string }> { 341 - const proc = Bun.spawn([process.execPath, "cli.ts", ...args], { 342 - cwd: import.meta.dir, 362 + return runExecutable(process.execPath, ["cli.ts", ...args], import.meta.dir); 363 + } 364 + 365 + async function runExecutable( 366 + executable: string, 367 + args: string[], 368 + cwd = import.meta.dir, 369 + ): Promise<{ exitCode: number; stdout: string; stderr: string }> { 370 + const proc = Bun.spawn([executable, ...args], { 371 + cwd, 343 372 stdout: "pipe", 344 373 stderr: "pipe", 345 374 });
+11 -2
cli.ts
··· 1 1 #!/usr/bin/env bun 2 2 3 - import { existsSync } from "node:fs"; 3 + import { existsSync, readFileSync } from "node:fs"; 4 4 import { Command } from "commander"; 5 5 import { DEFAULT_CODEX_STATE_DB_PATH, DEFAULT_DB_PATH, resolveCodexDir } from "./env"; 6 6 import { ··· 30 30 program 31 31 .name("cxs") 32 32 .description("Codex sessions 渐进式检索 CLI") 33 - .version("0.1.0"); 33 + .version(readPackageVersion()); 34 34 35 35 program 36 36 .command("current") ··· 234 234 function normalizeListSort(value: string | undefined): SessionListSort { 235 235 if (value === "started" || value === "messages") return value; 236 236 return "ended"; 237 + } 238 + 239 + function readPackageVersion(): string { 240 + const raw = readFileSync(new URL("./package.json", import.meta.url), "utf8"); 241 + const parsed = JSON.parse(raw) as { version?: unknown }; 242 + if (typeof parsed.version !== "string" || !parsed.version) { 243 + throw new Error("package.json version is missing"); 244 + } 245 + return parsed.version; 237 246 } 238 247 239 248 function emitCurrentError(error: CurrentStateDbError, jsonMode: boolean): void {