@1agents/session-reader 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/src/serve/http.js +13 -1
- package/dist/src/store/nul.d.ts +12 -0
- package/dist/src/store/nul.js +52 -0
- package/dist/src/store/read.js +4 -3
- package/dist/src/store/schema.d.ts +1 -1
- package/dist/src/store/schema.js +1 -1
- package/dist/src/store/write.js +3 -2
- package/package.json +2 -2
- package/skills/1session/SKILL.md +45 -8
- package/skills/1session/evals/evals.json +43 -1
- package/skills/1session/references/install.md +195 -0
package/README.md
CHANGED
|
@@ -71,6 +71,21 @@ npx @1agents/session-reader list # 不安装直接用
|
|
|
71
71
|
其他开关:`--agent claude,codex`(只装指定的几家,即使该智能体尚未安装也会建目录,
|
|
72
72
|
方便先装 skill 后装智能体)、`--dry-run`(只说会做什么)、`--force`(覆盖同名条目)。
|
|
73
73
|
|
|
74
|
+
**别用 `npx` 跑 `skill install`。** 默认的链接模式会指回包所在目录,而 npx 装的那份在
|
|
75
|
+
`~/.npm/_npx/<hash>/` 的缓存里,npm 自己会清。清掉那天五家的 skill 一起静悄悄消失。
|
|
76
|
+
先 `npm i -g` 再 `skill install`;实在要从 npx 引导就加 `--copy`,把字节交给各家自己拿着。
|
|
77
|
+
|
|
78
|
+
传播给别人时,两行就够——第二行是大家会忘的那行:只装 CLI 的人得自己记着它存在,
|
|
79
|
+
两行都跑了的人是智能体替他记着。
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm i -g @1agents/session-reader
|
|
83
|
+
1session skill install
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
skill 自己也带着这套安装说明(`skills/1session/references/install.md`):即便对方只拿到
|
|
87
|
+
一份 SKILL.md、机器上没有 CLI,它也会先用 `npx` 把问题回答掉,再回头提一次永久安装。
|
|
88
|
+
|
|
74
89
|
## CLI
|
|
75
90
|
|
|
76
91
|
```bash
|
|
@@ -538,6 +553,21 @@ base_url http://scott-mac.tailfb4720.ts.net:7777/v1 ← MagicDNS,跨机可
|
|
|
538
553
|
> 而共用同一份定义才谈得上「公共语言」。**schema 是唯一事实源,改类型先去改那个包。**
|
|
539
554
|
> `/manifest` 的 `metadata.protocol_version` 报告本服务遵循的协议版本。
|
|
540
555
|
|
|
556
|
+
### SQLite 的 TEXT 列会在 NUL 处截断
|
|
557
|
+
|
|
558
|
+
存进去 `A<NUL>B`,读出来只剩 `A`——**不报错**,安静地丢掉后面全部内容。
|
|
559
|
+
实测一个 antigravity 会话里 `wsl -l -v` 的 UTF-16 输出被当 UTF-8 读,产生交错
|
|
560
|
+
的 NUL,502 字符的 `tool_result` 存完只剩 342,后面 160 个字符凭空消失。
|
|
561
|
+
|
|
562
|
+
改存 BLOB 能保真,但 `text` / `tool_result` 上有 SQL 搜索(`LIKE` 对 BLOB 不
|
|
563
|
+
工作),所以走**写入转义、读取还原**(`src/store/nul.ts`)。引导符用 U+FFFF:
|
|
564
|
+
Unicode 明确规定的 noncharacter,不会出现在有效文本里;万一真出现也会被双写,
|
|
565
|
+
还原无歧义。绝大多数内容两个字符都不含,直接原样返回,常态零开销。
|
|
566
|
+
|
|
567
|
+
这类 bug 的可怕之处在于**没有任何报错**——只有 round-trip 测试
|
|
568
|
+
(`readSession(db,row)` 深度等于 `adapter.parse(candidate)`)能抓到它。
|
|
569
|
+
那条断言就是整个索引层的安全网:一旦索引在改写事实而不是缓存事实,它就会红。
|
|
570
|
+
|
|
541
571
|
## 测试
|
|
542
572
|
|
|
543
573
|
```bash
|
package/dist/src/serve/http.js
CHANGED
|
@@ -172,7 +172,19 @@ export function createServer(options = {}) {
|
|
|
172
172
|
export async function serve(options = {}) {
|
|
173
173
|
const host = options.host ?? '127.0.0.1';
|
|
174
174
|
const server = createServer(options);
|
|
175
|
-
|
|
175
|
+
// 请求的端口。实际绑定到哪个从 server.address() 读——传 0 时它是随机的。
|
|
176
|
+
const wanted = options.port ?? DEFAULT_PORT;
|
|
177
|
+
// 端口占用是最常见的启动失败,默认会抛一整屏 Node 栈——对着栈猜"是不是
|
|
178
|
+
// 已经起了一个"没意义,直接说清楚怎么办。
|
|
179
|
+
await new Promise((resolve, reject) => {
|
|
180
|
+
server.once('error', (error) => {
|
|
181
|
+
reject(error.code === 'EADDRINUSE'
|
|
182
|
+
? new Error(`端口 ${wanted} 已被占用。换一个:--port <n>;或先停掉占用它的进程:` +
|
|
183
|
+
`lsof -nP -iTCP:${wanted} -sTCP:LISTEN`)
|
|
184
|
+
: error);
|
|
185
|
+
});
|
|
186
|
+
server.listen(wanted, host, resolve);
|
|
187
|
+
});
|
|
176
188
|
const { port } = server.address();
|
|
177
189
|
const identity = await nodeIdentity();
|
|
178
190
|
// 只听回环的服务,外部发现得了却连不上——如实说,别让调用方白跑一趟。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 写进 SQLite 的 TEXT 列之前调用。
|
|
3
|
+
*
|
|
4
|
+
* 绝大多数内容两个字符都不含,直接原样返回——所以常态是零拷贝。
|
|
5
|
+
*/
|
|
6
|
+
export declare function encodeText(value: string): string;
|
|
7
|
+
export declare function encodeText(value: string | null | undefined): string | null;
|
|
8
|
+
/** 从 SQLite 的 TEXT 列读出来之后调用。 */
|
|
9
|
+
export declare function decodeText(value: string): string;
|
|
10
|
+
export declare function decodeText(value: string | null | undefined): string | null;
|
|
11
|
+
/** 测试与诊断用:这段文本经过 SQLite 的 TEXT 列会不会被截断。 */
|
|
12
|
+
export declare function wouldTruncate(value: string): boolean;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite 的 TEXT 列在第一个 NUL 处截断——不报错,安静地丢掉后面全部内容。
|
|
3
|
+
*
|
|
4
|
+
* 实测:一个 antigravity 会话里 `wsl -l -v` 的 UTF-16 输出被当 UTF-8 读,产生
|
|
5
|
+
* 交错的 NUL;502 字符的 tool_result 存进索引再读出来只剩 342,后面 160 个
|
|
6
|
+
* 字符凭空消失。round-trip 测试因此长期飘红,而失败信息指向的是内容本身,
|
|
7
|
+
* 很难看出是存储层干的。
|
|
8
|
+
*
|
|
9
|
+
* 改存 BLOB 能保真,但 `text` / `tool_result` 上有 SQL 搜索(`LIKE` 对 BLOB
|
|
10
|
+
* 不工作),所以改成**写入时转义、读取时还原**。
|
|
11
|
+
*
|
|
12
|
+
* 引导符用 U+FFFF:Unicode 明确规定的 noncharacter,不会出现在有效文本里;
|
|
13
|
+
* 万一真出现也会被双写,所以还原无歧义。
|
|
14
|
+
*
|
|
15
|
+
* 两个常量用 `String.fromCharCode` 而不是字面量,免得源文件里真带上这些
|
|
16
|
+
* 字符——它们在编辑器、diff、终端里都是隐形的。
|
|
17
|
+
*/
|
|
18
|
+
const NUL = String.fromCharCode(0x00);
|
|
19
|
+
const LEAD = String.fromCharCode(0xffff);
|
|
20
|
+
const ESCAPED_NUL = `${LEAD}0`;
|
|
21
|
+
const ESCAPED_LEAD = `${LEAD}${LEAD}`;
|
|
22
|
+
export function encodeText(value) {
|
|
23
|
+
if (value === null || value === undefined)
|
|
24
|
+
return null;
|
|
25
|
+
if (!value.includes(NUL) && !value.includes(LEAD))
|
|
26
|
+
return value;
|
|
27
|
+
// 顺序要紧:先把引导符自己双写,再拿它去转义 NUL。反过来会把刚写出的
|
|
28
|
+
// 转义序列又转义一遍。
|
|
29
|
+
return value.split(LEAD).join(ESCAPED_LEAD).split(NUL).join(ESCAPED_NUL);
|
|
30
|
+
}
|
|
31
|
+
export function decodeText(value) {
|
|
32
|
+
if (value === null || value === undefined)
|
|
33
|
+
return null;
|
|
34
|
+
if (!value.includes(LEAD))
|
|
35
|
+
return value;
|
|
36
|
+
let out = '';
|
|
37
|
+
for (let i = 0; i < value.length; i++) {
|
|
38
|
+
if (value[i] !== LEAD) {
|
|
39
|
+
out += value[i];
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const next = value[++i];
|
|
43
|
+
// 双写还原成引导符本身;LEAD+'0' 还原成 NUL;落单的引导符原样留着,
|
|
44
|
+
// 宁可多留一个字符,也不要把不认识的序列吞掉。
|
|
45
|
+
out += next === LEAD ? LEAD : next === '0' ? NUL : LEAD + (next ?? '');
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
/** 测试与诊断用:这段文本经过 SQLite 的 TEXT 列会不会被截断。 */
|
|
50
|
+
export function wouldTruncate(value) {
|
|
51
|
+
return value.includes(NUL);
|
|
52
|
+
}
|
package/dist/src/store/read.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decodeText } from './nul.js';
|
|
1
2
|
import { turnStartsFrom } from '../turns.js';
|
|
2
3
|
import { emptyProviderStats, } from '../types.js';
|
|
3
4
|
export function sessionRow(db, id) {
|
|
@@ -70,7 +71,7 @@ export function refOf(row) {
|
|
|
70
71
|
id: row.native_id,
|
|
71
72
|
provider: row.provider,
|
|
72
73
|
path: row.source_path,
|
|
73
|
-
...(row.title === null ? {} : { title: row.title }),
|
|
74
|
+
...(row.title === null ? {} : { title: decodeText(row.title) }),
|
|
74
75
|
...(row.workspace === null ? {} : { workspace: row.workspace }),
|
|
75
76
|
...(row.started_at === null ? {} : { createdAt: row.started_at }),
|
|
76
77
|
...(row.ended_at === null ? {} : { updatedAt: row.ended_at }),
|
|
@@ -89,12 +90,12 @@ function eventsOf(db, id, nativeId) {
|
|
|
89
90
|
id: `${nativeId}#${row.idx}`,
|
|
90
91
|
index: row.idx,
|
|
91
92
|
kind: row.kind,
|
|
92
|
-
...(row.text === null ? {} : { text: row.text }),
|
|
93
|
+
...(row.text === null ? {} : { text: decodeText(row.text) }),
|
|
93
94
|
...(row.tool_name === null ? {} : { toolName: row.tool_name }),
|
|
94
95
|
...(row.tool_args_json === null
|
|
95
96
|
? {}
|
|
96
97
|
: { toolArgs: JSON.parse(row.tool_args_json) }),
|
|
97
|
-
...(row.tool_result === null ? {} : { toolResult: row.tool_result }),
|
|
98
|
+
...(row.tool_result === null ? {} : { toolResult: decodeText(row.tool_result) }),
|
|
98
99
|
...(row.is_error === null ? {} : { isError: row.is_error === 1 }),
|
|
99
100
|
...(row.ts === null ? {} : { timestamp: row.ts }),
|
|
100
101
|
...(row.source_index === null ? {} : { sourceIndex: row.source_index }),
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* the behaviour — never derive them from a git hash, `dist` ships without git.
|
|
8
8
|
*/
|
|
9
9
|
/** DDL layout. A bump drops and rebuilds the whole database. */
|
|
10
|
-
export declare const SCHEMA_VERSION =
|
|
10
|
+
export declare const SCHEMA_VERSION = 2;
|
|
11
11
|
/** L1 semantics — anything in `parsers/` that changes normalized events. */
|
|
12
12
|
export declare const PARSER_VERSION = 3;
|
|
13
13
|
/** L2 rules — `writes.ts` / `ledger.ts`. Re-derives facts from stored events. */
|
package/dist/src/store/schema.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* the behaviour — never derive them from a git hash, `dist` ships without git.
|
|
8
8
|
*/
|
|
9
9
|
/** DDL layout. A bump drops and rebuilds the whole database. */
|
|
10
|
-
export const SCHEMA_VERSION =
|
|
10
|
+
export const SCHEMA_VERSION = 2;
|
|
11
11
|
/** L1 semantics — anything in `parsers/` that changes normalized events. */
|
|
12
12
|
export const PARSER_VERSION = 3;
|
|
13
13
|
/** L2 rules — `writes.ts` / `ledger.ts`. Re-derives facts from stored events. */
|
package/dist/src/store/write.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { encodeText } from './nul.js';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import crypto from 'node:crypto';
|
|
3
4
|
import { PARSER_VERSION } from './schema.js';
|
|
@@ -51,7 +52,7 @@ export function writeSession(db, session, fingerprint, turnCount) {
|
|
|
51
52
|
id, provider, native_id, source_path, workspace, title, started_at, ended_at,
|
|
52
53
|
event_count, turn_count, source_size, source_mtime_ms, head_hash, aux_fingerprint,
|
|
53
54
|
parser_version, extractor_version, edge_version, indexed_at, artifacts_json, stats_json
|
|
54
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, ?)`).run(id, session.ref.provider, session.ref.id, session.ref.path, session.ref.workspace ?? null, session.ref.title
|
|
55
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, ?)`).run(id, session.ref.provider, session.ref.id, session.ref.path, session.ref.workspace ?? null, encodeText(session.ref.title), session.ref.createdAt ?? null, session.ref.updatedAt ?? null, session.turns.length, turnCount, fingerprint.sourceSize, fingerprint.sourceMtimeMs, fingerprint.headHash, fingerprint.auxFingerprint ?? null, PARSER_VERSION, new Date().toISOString(), JSON.stringify(session.artifacts), JSON.stringify(session.stats));
|
|
55
56
|
const insert = db.prepare(`INSERT INTO events (
|
|
56
57
|
session_id, idx, kind, text, tool_name, tool_args_json, tool_result, is_error,
|
|
57
58
|
ts, source_index, exit_code, pid, duration_ms, provider_truncated
|
|
@@ -60,7 +61,7 @@ export function writeSession(db, session, fingerprint, turnCount) {
|
|
|
60
61
|
// 0.08% and silently dropped search hits from long build logs — exactly
|
|
61
62
|
// the kind of "looks complete, isn't" answer this store exists to remove.
|
|
62
63
|
for (const event of session.turns) {
|
|
63
|
-
insert.run(id, event.index, event.kind, event.text
|
|
64
|
+
insert.run(id, event.index, event.kind, encodeText(event.text), event.toolName ?? null, event.toolArgs ? JSON.stringify(event.toolArgs) : null, encodeText(event.toolResult), event.isError === undefined ? null : event.isError ? 1 : 0, event.timestamp ?? null, event.sourceIndex ?? null, event.exitCode ?? null, event.processId ?? null, event.durationMs ?? null, event.truncated === undefined ? null : event.truncated ? 1 : 0);
|
|
64
65
|
}
|
|
65
66
|
db.exec('COMMIT');
|
|
66
67
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1agents/session-reader",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Read Plane: cross-agent session discovery, turn inspection, workspace aggregation and distillation from raw local session files.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -60,6 +60,6 @@
|
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@1agents/dreammate-network": "^0.3.0",
|
|
63
|
-
"@1agents/dreammate-node": "^0.
|
|
63
|
+
"@1agents/dreammate-node": "^0.4.0"
|
|
64
64
|
}
|
|
65
65
|
}
|
package/skills/1session/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: 1session
|
|
3
|
-
description: Search and read the user's past AI coding sessions across Claude Code, Codex, Antigravity, Grok and DeepSeek Harness (dsh) from their raw local session files, using the `1session` CLI. Use this whenever the user refers to work they did in an earlier session rather than in this conversation — "上次/之前/昨天我们改了什么", "那个报错后来怎么解决的", "我在哪个会话里提过 X", "这个功能是哪一轮加的", "codex 那边做到哪了", "grok/dsh 那边呢", "跨项目找一下", "整理一下最近几天的会话/写个周报". Also reach for it proactively, before asking the user to re-explain context they have obviously already established with some agent on this machine — the answer is usually already on disk. Read-only: it never modifies or resumes a session.
|
|
3
|
+
description: Search and read the user's past AI coding sessions across Claude Code, Codex, Antigravity, Grok and DeepSeek Harness (dsh) from their raw local session files, using the `1session` CLI. Use this whenever the user refers to work they did in an earlier session rather than in this conversation — "上次/之前/昨天我们改了什么", "那个报错后来怎么解决的", "我在哪个会话里提过 X", "这个功能是哪一轮加的", "codex 那边做到哪了", "grok/dsh 那边呢", "跨项目找一下", "整理一下最近几天的会话/写个周报". Also reach for it proactively, before asking the user to re-explain context they have obviously already established with some agent on this machine — the answer is usually already on disk. Read-only: it never modifies or resumes a session. This skill also covers getting itself running and spreading to other agents: when the `1session` CLI is missing it falls back to `npx` on the spot, and it knows how to install the package globally and push the skill into all five agents at once — so use it too when the user asks to install, upgrade, uninstall, share or distribute 1session / session-reader, or reports that the `1session` command is not found.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# 1session — the cross-agent Read Plane
|
|
@@ -19,15 +19,52 @@ normalizes all of them and answers questions about what actually happened.
|
|
|
19
19
|
Everything is derived from the raw files at read time. Nothing is written back to
|
|
20
20
|
them, no daemon is involved, and no session is ever resumed or modified.
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Bootstrap: get the CLI, then answer the question
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
This skill travels on its own. Someone may have dropped `SKILL.md` into an agent
|
|
25
|
+
on a machine where the `1session` CLI does not exist, so the first call is a
|
|
26
|
+
probe rather than an assumption:
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
```bash
|
|
29
|
+
1session help
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If that fails, **do not stop to install before answering.** `npx` runs the same
|
|
33
|
+
CLI with nothing installed:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx -y @1agents/session-reader@latest list --global --limit 10
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Substitute `npx -y @1agents/session-reader@latest` for `1session` everywhere
|
|
40
|
+
below, answer the question the user actually asked, and offer the permanent
|
|
41
|
+
install once, afterwards. Someone asking where last week's bug got fixed wants
|
|
42
|
+
the bug, not a setup errand.
|
|
43
|
+
|
|
44
|
+
The permanent install, when they want it:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm i -g @1agents/session-reader # requires Node >= 22.15
|
|
48
|
+
1session skill install # put this skill into every agent on the machine
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Node below 22.15 is a hard stop rather than a warning — the reader needs
|
|
52
|
+
`node:sqlite` for the index and `node:zlib`'s zstd to read dsh's compressed
|
|
53
|
+
sessions, and `npx` does not rescue an old runtime either. Check `node -v`, say
|
|
54
|
+
plainly that Node needs upgrading, and don't improvise around it.
|
|
55
|
+
|
|
56
|
+
That second command is what makes this spread: one run installs the skill into
|
|
57
|
+
Claude Code, Codex, Antigravity, Grok and dsh at once, so whichever agent the
|
|
58
|
+
user opens next already knows their history is readable. Run it after a global
|
|
59
|
+
install, **not** through `npx` — npx installs the package into a cache directory
|
|
60
|
+
that npm later garbage-collects, and the skill links would dangle with it.
|
|
61
|
+
|
|
62
|
+
`references/install.md` has the rest: PATH and permission failures, link vs copy,
|
|
63
|
+
upgrading, uninstalling, and what to hand someone who wants this on their own
|
|
64
|
+
machine. Read it when an install misbehaves or the user asks how to share this.
|
|
65
|
+
|
|
66
|
+
The first real run parses every session (~10s for a few hundred); an index makes
|
|
67
|
+
each call after that sub-second. A slow first call is that build, not a hang.
|
|
31
68
|
|
|
32
69
|
## Pick the command from the question
|
|
33
70
|
|
|
@@ -42,6 +42,48 @@
|
|
|
42
42
|
"Gives a drill-down handle — a turn/event number or an exact command the user can run to see the evidence",
|
|
43
43
|
"Reports how it was verified (tests green / the commit) rather than only describing the change"
|
|
44
44
|
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": 3,
|
|
48
|
+
"name": "missing-cli-still-answers",
|
|
49
|
+
"prompt": "上周我在这台机器上调一个超时的问题,后来是怎么解决的?(顺便说一句,我这台机器上好像没装过什么 1session,`1session` 这个命令敲下去是 command not found)",
|
|
50
|
+
"expected_output": "Recognizes the CLI is absent, immediately falls back to `npx -y @1agents/session-reader@latest ...` to actually answer the timeout question, and only then mentions the permanent install as a one-time follow-up — rather than stopping to install first or declaring it cannot help.",
|
|
51
|
+
"files": [],
|
|
52
|
+
"assertions": [
|
|
53
|
+
"Falls back to npx (`npx -y @1agents/session-reader@latest ...`) instead of stopping at the missing command",
|
|
54
|
+
"Actually attempts to answer the user's timeout question rather than turning the turn into a setup errand",
|
|
55
|
+
"Mentions the permanent install (`npm i -g @1agents/session-reader`) as an optional follow-up, not as a precondition",
|
|
56
|
+
"Does not run a global npm install without the user agreeing to it first",
|
|
57
|
+
"Does not claim the question is unanswerable because the CLI is missing"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": 4,
|
|
62
|
+
"name": "distribute-to-a-colleague",
|
|
63
|
+
"prompt": "同事看我能翻出以前会话的记录,也想在他 mac 上用。我要发给他什么?他那边只装了 claude code 和 codex,另外他 node 好像还是 20。",
|
|
64
|
+
"expected_output": "Gives the two-line install (npm i -g, then `1session skill install`), explains that the second line is what puts the skill into Claude Code and Codex, and flags that Node 20 is below the 22.15 floor so he must upgrade Node first — npx will not work around it either.",
|
|
65
|
+
"files": [],
|
|
66
|
+
"assertions": [
|
|
67
|
+
"Gives `npm i -g @1agents/session-reader` as the install command",
|
|
68
|
+
"Includes `1session skill install` and explains it is what puts the skill into the colleague's agents, not just the CLI",
|
|
69
|
+
"Flags Node 20 as below the >= 22.15 requirement and says it must be upgraded",
|
|
70
|
+
"Does not suggest npx as a way around the old Node version (it runs on the same runtime and fails identically)",
|
|
71
|
+
"Does not invent an install path that does not exist (no curl|sh script, no brew formula, no manual git clone as the primary route)"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": 5,
|
|
76
|
+
"name": "npx-skill-install-trap",
|
|
77
|
+
"prompt": "我不想全局装东西,能不能直接用 npx 把这个 skill 装到我的 claude 和 codex 里就好?",
|
|
78
|
+
"expected_output": "Warns that `skill install` in its default link mode would point at the npx cache directory, which npm garbage-collects — the skill would silently vanish later. Offers `--copy` as the npx-compatible route, or a global install as the robust one.",
|
|
79
|
+
"files": [],
|
|
80
|
+
"assertions": [
|
|
81
|
+
"Warns that a link-mode install via npx points into the npx cache (~/.npm/_npx/...) which npm later prunes",
|
|
82
|
+
"Names the consequence concretely: the skill silently disappears from the agents afterwards",
|
|
83
|
+
"Offers `--copy` as the way to make an npx-based install survive, and/or a global install as the robust alternative",
|
|
84
|
+
"Mentions `--agent claude,codex` or otherwise respects that the user only wants those two",
|
|
85
|
+
"Does not simply tell the user to run `npx ... skill install` with no caveat"
|
|
86
|
+
]
|
|
45
87
|
}
|
|
46
88
|
]
|
|
47
|
-
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Installing and spreading 1session
|
|
2
|
+
|
|
3
|
+
Read this when the probe in SKILL.md fails in a way the two-line fallback does
|
|
4
|
+
not cover, or when the user asks how to get this onto another machine or into
|
|
5
|
+
another agent.
|
|
6
|
+
|
|
7
|
+
- [What it needs](#what-it-needs)
|
|
8
|
+
- [Three ways to run it](#three-ways-to-run-it)
|
|
9
|
+
- [Spreading the skill to every agent](#spreading-the-skill-to-every-agent)
|
|
10
|
+
- [Upgrading](#upgrading)
|
|
11
|
+
- [Uninstalling](#uninstalling)
|
|
12
|
+
- [Handing it to someone else](#handing-it-to-someone-else)
|
|
13
|
+
- [Troubleshooting](#troubleshooting)
|
|
14
|
+
|
|
15
|
+
## What it needs
|
|
16
|
+
|
|
17
|
+
**Node.js >= 22.15**, and nothing else. The version floor is real, not
|
|
18
|
+
defensive: the index is `node:sqlite` and dsh's sessions are zstd-compressed,
|
|
19
|
+
which only landed in `node:zlib` in 22.15. On an older runtime the package
|
|
20
|
+
installs and then fails at the first call, so check first:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node -v
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If it is below 22.15, say so and stop. `npx` runs the same code on the same
|
|
27
|
+
runtime and will fail identically — there is no way around it except upgrading
|
|
28
|
+
Node (`nvm install 22`, `brew upgrade node`, or whatever that machine uses).
|
|
29
|
+
|
|
30
|
+
Everything else is already on the machine. The only runtime dependency is
|
|
31
|
+
`@1agents/dreammate-network` (12 kB, zero dependencies of its own), which npm
|
|
32
|
+
pulls in automatically. There is no daemon, no service to start, no config file,
|
|
33
|
+
and no account. The reader opens session files the agents already wrote and
|
|
34
|
+
never writes back to them.
|
|
35
|
+
|
|
36
|
+
macOS, Linux and WSL all work. On native Windows the paths it reads
|
|
37
|
+
(`~/.claude`, `~/.codex`, …) resolve through `os.homedir()`, but it is the least
|
|
38
|
+
exercised platform — if something looks wrong there, check that the agent in
|
|
39
|
+
question actually stores sessions under the Windows home directory before
|
|
40
|
+
assuming the reader is broken.
|
|
41
|
+
|
|
42
|
+
## Three ways to run it
|
|
43
|
+
|
|
44
|
+
**Zero install (`npx`).** Correct for a one-off answer, for a machine you are
|
|
45
|
+
only visiting, and for the first call before anyone has agreed to install
|
|
46
|
+
anything:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx -y @1agents/session-reader@latest list --global --limit 10
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Every command in SKILL.md works with this prefix substituted for `1session`.
|
|
53
|
+
The cost is a few seconds of download per invocation and a cache directory npm
|
|
54
|
+
cleans up on its own schedule — fine for answering, wrong as a permanent setup
|
|
55
|
+
(see the warning under [spreading](#spreading-the-skill-to-every-agent)).
|
|
56
|
+
|
|
57
|
+
**Global install.** The normal choice for a machine the user works on daily:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm i -g @1agents/session-reader
|
|
61
|
+
1session help # verify: prints the command list
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**As a library.** The parsers and ledgers are exported, so a script can consume
|
|
65
|
+
sessions without shelling out:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm i @1agents/session-reader
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import { listSessions } from '@1agents/session-reader';
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Reach for this only when the user is building something on top of session data.
|
|
76
|
+
For answering questions about the past, the CLI is both cheaper and denser.
|
|
77
|
+
|
|
78
|
+
## Spreading the skill to every agent
|
|
79
|
+
|
|
80
|
+
The package ships this skill inside it, and the CLI installs it into every
|
|
81
|
+
agent's skills directory in one call:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
1session skill install # all agents found on this machine
|
|
85
|
+
1session skill status # what is installed where, and whether it is current
|
|
86
|
+
1session skill uninstall # remove it again
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| Agent | Where it lands |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| claude | `~/.claude/skills/1session` |
|
|
92
|
+
| codex | `~/.codex/skills/1session` |
|
|
93
|
+
| antigravity | `~/.gemini/antigravity/skills/1session` (**not** `~/.gemini/skills`, which is gemini-cli's) |
|
|
94
|
+
| grok | `~/.grok/skills/1session` |
|
|
95
|
+
| dsh | `~/.dsh/skills/1session` |
|
|
96
|
+
|
|
97
|
+
All five load `<dir>/<name>/SKILL.md` with the same YAML frontmatter, so it is
|
|
98
|
+
genuinely one file serving five agents, not five ports of it.
|
|
99
|
+
|
|
100
|
+
**Do not run `skill install` through `npx`.** In link mode — the default — the
|
|
101
|
+
installed entry is a symlink back to wherever the package lives, and under npx
|
|
102
|
+
that is a cache directory like `~/.npm/_npx/<hash>/node_modules/…` which npm
|
|
103
|
+
garbage-collects. The skill works right up until the day the cache is pruned and
|
|
104
|
+
then silently disappears from all five agents. Install the package globally
|
|
105
|
+
first and then run `skill install`; if you truly must bootstrap through npx, use
|
|
106
|
+
`--copy` so the bytes are owned by the agent rather than borrowed from a cache.
|
|
107
|
+
|
|
108
|
+
Link mode is otherwise the better default: after `npm i -g …@latest`, every
|
|
109
|
+
agent is already looking at the new skill with nothing to re-run. Use `--copy`
|
|
110
|
+
when an agent's loader does not follow symlinks — the symptom is `skill status`
|
|
111
|
+
reporting a healthy link while the agent itself never mentions the skill. The
|
|
112
|
+
cost of a copy is that upgrades no longer propagate; `skill status` marks a copy
|
|
113
|
+
that has drifted from the installed package, so the drift is visible rather than
|
|
114
|
+
silent.
|
|
115
|
+
|
|
116
|
+
Other flags:
|
|
117
|
+
|
|
118
|
+
| Flag | Why |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| `--agent claude,codex` | Only these. Named agents get their skills directory created even if the agent is not installed yet, which is how you set a machine up before installing the agent. |
|
|
121
|
+
| `--copy` | Copy instead of symlink. |
|
|
122
|
+
| `--force` | Overwrite a same-named entry that this installer did not create, or convert an existing copy into a link. Without it, both cases are refused rather than clobbered. |
|
|
123
|
+
| `--dry-run` | Print the plan and touch nothing. Worth doing first on someone else's machine. |
|
|
124
|
+
| `--json` | Machine-readable result. |
|
|
125
|
+
|
|
126
|
+
Agents that are not installed are skipped rather than failed, so a machine with
|
|
127
|
+
only Claude Code reports three skips and one install, and that is success.
|
|
128
|
+
|
|
129
|
+
## Upgrading
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npm i -g @1agents/session-reader@latest
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Linked skills are current the moment that finishes. Copied ones need
|
|
136
|
+
`1session skill install --copy --force` afterwards; `1session skill status` is
|
|
137
|
+
what tells you which case you are in.
|
|
138
|
+
|
|
139
|
+
The index rebuilds itself when the parser version moves, so an upgrade that
|
|
140
|
+
changes how sessions are read costs one slower call and needs no manual
|
|
141
|
+
clearing. To force it: `1session index --all --global`.
|
|
142
|
+
|
|
143
|
+
## Uninstalling
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
1session skill uninstall # remove the skill from the agents
|
|
147
|
+
npm rm -g @1agents/session-reader
|
|
148
|
+
rm -rf ~/.1agents/session-reader # the index; sessions themselves are untouched
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`skill uninstall` only removes entries it could have created (its own links and
|
|
152
|
+
copies); anything else needs `--force`, which exists so a hand-written skill of
|
|
153
|
+
the same name is never deleted by accident. Nothing here touches the agents'
|
|
154
|
+
session files — the reader has never written to them.
|
|
155
|
+
|
|
156
|
+
## Handing it to someone else
|
|
157
|
+
|
|
158
|
+
The whole thing is one npm package, so the shortest correct instruction is two
|
|
159
|
+
lines:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm i -g @1agents/session-reader
|
|
163
|
+
1session skill install
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
That is the version to paste into a chat or a README. It gets them the CLI and
|
|
167
|
+
puts the skill into every agent they have, which is the part people forget: a
|
|
168
|
+
colleague who installs only the CLI has to remember it exists, while one who ran
|
|
169
|
+
both lines has their agent remember for them.
|
|
170
|
+
|
|
171
|
+
If they cannot install globally (locked-down machine, shared box), the npx form
|
|
172
|
+
plus `skill install --copy` gets them to the same place with the package living
|
|
173
|
+
in a cache instead of `/usr/local`.
|
|
174
|
+
|
|
175
|
+
If they want to read the source or file a bug:
|
|
176
|
+
<https://github.com/scottzx/session-reader>.
|
|
177
|
+
|
|
178
|
+
When you are the one setting this up on a user's machine, prefer offering these
|
|
179
|
+
commands over running them unasked — a global npm install changes their system.
|
|
180
|
+
Running the read-only CLI to answer a question is not the same kind of act as
|
|
181
|
+
installing software, and the difference is worth respecting.
|
|
182
|
+
|
|
183
|
+
## Troubleshooting
|
|
184
|
+
|
|
185
|
+
| Symptom | What is actually wrong |
|
|
186
|
+
| --- | --- |
|
|
187
|
+
| `1session: command not found` right after `npm i -g` | npm's global bin directory is not on `PATH`. It is `$(npm prefix -g)/bin` — `npm bin -g` was removed in npm 9, so use the prefix form — and it goes in the shell profile. Meanwhile `npx -y @1agents/session-reader@latest …` works unchanged. |
|
|
188
|
+
| `EACCES` / permission denied during `npm i -g` | The global prefix is root-owned. Do not reach for `sudo npm` — repoint the prefix (`npm config set prefix ~/.npm-global`, then put `~/.npm-global/bin` on `PATH`) or use a Node version manager, both of which leave the system directories alone. |
|
|
189
|
+
| Installs fine, then throws on the first real command | Almost always Node < 22.15 — `node:sqlite` or zstd missing. Check `node -v`. |
|
|
190
|
+
| `skill status` shows a healthy link, but the agent never uses the skill | That agent's loader does not follow symlinks. `1session skill install --copy --force`. |
|
|
191
|
+
| The skill vanished from every agent at once | It was installed in link mode from an npx cache that npm has since pruned. Install the package globally, then `1session skill install --force`. |
|
|
192
|
+
| `skill install` reports `blocked` | Something else already owns that name — a hand-written skill, or a copy where a link is wanted. Look at it before passing `--force`. |
|
|
193
|
+
| The first call takes ~10s | That is the initial index build over every session on the machine, not a hang. Subsequent calls are sub-second. |
|
|
194
|
+
| Results look stale or wrong | `1session index --all --global` rebuilds, and `--no-index` on any command reads the raw files directly — if those two disagree, that is a real bug worth reporting. |
|
|
195
|
+
| `list` returns nothing in a directory that definitely had sessions | Scope, not installation. `list` defaults to the pwd subtree; add `--global`. |
|