@1agents/session-reader 0.6.2 → 0.7.0

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.
@@ -0,0 +1,20 @@
1
+ import http from 'node:http';
2
+ export declare const DEFAULT_WEB_PORT = 7780;
3
+ export interface WebOptions {
4
+ port?: number;
5
+ host?: string;
6
+ /** Workspace the UI treats as "当前工作区". Defaults to process.cwd(). */
7
+ cwd?: string;
8
+ /** Initial scope dropdown: cwd (default) or global. */
9
+ defaultScope?: 'cwd' | 'global';
10
+ /** Open the page in the default browser after listen. */
11
+ open?: boolean;
12
+ }
13
+ export interface SrUiBoot {
14
+ mode: 'standalone';
15
+ cwd: string;
16
+ title: string;
17
+ defaultScope: 'cwd' | 'global';
18
+ }
19
+ export declare function renderIndexHtml(boot: SrUiBoot): string;
20
+ export declare function serveWeb(options?: WebOptions): Promise<http.Server>;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * `1session web` — a standalone browser for the same session list, chat
3
+ * preview and file ledger the DSH plugin shows. Loopback by default; no
4
+ * DreamMate / DSH runtime required.
5
+ */
6
+ import { spawn } from 'node:child_process';
7
+ import fs from 'node:fs';
8
+ import http from 'node:http';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { handleSessionApi } from './api.js';
12
+ export const DEFAULT_WEB_PORT = 7780;
13
+ function appJsPath() {
14
+ const here = path.dirname(fileURLToPath(import.meta.url));
15
+ const sibling = path.join(here, 'app.js');
16
+ // tsx from src/web → repo dist/src/web/app.js; compiled run → sibling.
17
+ const fromSrc = path.resolve(here, '../../dist/src/web/app.js');
18
+ if (fs.existsSync(fromSrc))
19
+ return fromSrc;
20
+ return sibling;
21
+ }
22
+ export function renderIndexHtml(boot) {
23
+ const bootJson = JSON.stringify(boot).replace(/</g, '\\u003c');
24
+ return `<!DOCTYPE html>
25
+ <html lang="zh-CN">
26
+ <head>
27
+ <meta charset="utf-8" />
28
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
29
+ <title>历史会话 · 1session</title>
30
+ <script>window.__SR_UI__=${bootJson};</script>
31
+ <script src="/app.js" defer></script>
32
+ </head>
33
+ <body></body>
34
+ </html>
35
+ `;
36
+ }
37
+ function openBrowser(url) {
38
+ const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
39
+ const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
40
+ spawn(cmd, args, { stdio: 'ignore', detached: true }).unref();
41
+ }
42
+ export async function serveWeb(options = {}) {
43
+ const host = options.host ?? '127.0.0.1';
44
+ const cwd = options.cwd ?? process.cwd();
45
+ const defaultScope = options.defaultScope ?? 'cwd';
46
+ const boot = {
47
+ mode: 'standalone',
48
+ cwd,
49
+ title: path.basename(cwd) || '当前工作区',
50
+ defaultScope,
51
+ };
52
+ const appJs = appJsPath();
53
+ const server = http.createServer(async (req, res) => {
54
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
55
+ const pathname = url.pathname;
56
+ res.setHeader('Access-Control-Allow-Origin', '*');
57
+ res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
58
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
59
+ if (req.method === 'OPTIONS') {
60
+ res.statusCode = 204;
61
+ res.end();
62
+ return;
63
+ }
64
+ try {
65
+ if (pathname === '/' || pathname === '/index.html') {
66
+ const body = renderIndexHtml(boot);
67
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
68
+ res.end(body);
69
+ return;
70
+ }
71
+ if (pathname === '/app.js') {
72
+ if (!fs.existsSync(appJs)) {
73
+ res.writeHead(500, { 'content-type': 'text/plain; charset=utf-8' });
74
+ res.end('missing dist/src/web/app.js — run npm run build');
75
+ return;
76
+ }
77
+ res.writeHead(200, {
78
+ 'content-type': 'application/javascript; charset=utf-8',
79
+ 'cache-control': 'no-store',
80
+ });
81
+ res.end(fs.readFileSync(appJs));
82
+ return;
83
+ }
84
+ if (pathname.startsWith('/api/session-reader')) {
85
+ const apiPath = pathname.replace(/^\/api\/session-reader/, '') || '/';
86
+ const handled = await handleSessionApi(apiPath, url, res, { fallbackCwd: cwd });
87
+ if (handled)
88
+ return;
89
+ res.writeHead(404, { 'content-type': 'application/json; charset=utf-8' });
90
+ res.end(JSON.stringify({ error: 'Endpoint not found' }));
91
+ return;
92
+ }
93
+ res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
94
+ res.end('Not found');
95
+ }
96
+ catch (err) {
97
+ const message = err instanceof Error ? err.message : String(err);
98
+ if (!res.headersSent) {
99
+ res.writeHead(500, { 'content-type': 'application/json; charset=utf-8' });
100
+ }
101
+ res.end(JSON.stringify({ error: message }));
102
+ }
103
+ });
104
+ const wanted = options.port ?? DEFAULT_WEB_PORT;
105
+ await new Promise((resolve, reject) => {
106
+ server.once('error', (error) => {
107
+ reject(error.code === 'EADDRINUSE'
108
+ ? new Error(`端口 ${wanted} 已被占用。换一个:1session web --port <n>;或先停掉占用它的进程:` +
109
+ `lsof -nP -iTCP:${wanted} -sTCP:LISTEN`)
110
+ : error);
111
+ });
112
+ server.listen(wanted, host, resolve);
113
+ });
114
+ const { port } = server.address();
115
+ const origin = `http://${host}:${port}`;
116
+ console.log('1session web — 独立历史会话浏览器');
117
+ console.log(` ${origin}`);
118
+ console.log(` 工作区 ${cwd}`);
119
+ console.log(' 列表只拉元数据,点击会话后再加载对话与文件。Ctrl+C 退出。');
120
+ if (host !== '127.0.0.1' && host !== 'localhost' && host !== '::1') {
121
+ console.warn(` ⚠️ 绑定在 ${host}:会话原文(代码、shell 历史、密钥)将对该网络开放。`);
122
+ }
123
+ if (options.open)
124
+ openBrowser(origin);
125
+ return server;
126
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1agents/session-reader",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
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",
@@ -29,6 +29,23 @@
29
29
  ".": {
30
30
  "types": "./dist/src/index.d.ts",
31
31
  "import": "./dist/src/index.js"
32
+ },
33
+ "./client": {
34
+ "types": "./dist/src/dsh/client.d.ts",
35
+ "default": "./dist/src/dsh/client.js"
36
+ },
37
+ "./dsh": {
38
+ "types": "./dist/src/dsh/index.d.ts",
39
+ "import": "./dist/src/dsh/index.js"
40
+ },
41
+ "./cordis.patch.yml": "./cordis.patch.yml"
42
+ },
43
+ "dsh": {
44
+ "bundle": {
45
+ "patch": "./cordis.patch.yml"
46
+ },
47
+ "client": {
48
+ "platform": "web"
32
49
  }
33
50
  },
34
51
  "bin": {
@@ -37,6 +54,7 @@
37
54
  "files": [
38
55
  "dist",
39
56
  "skills",
57
+ "cordis.patch.yml",
40
58
  "README.md"
41
59
  ],
42
60
  "publishConfig": {
@@ -44,9 +62,9 @@
44
62
  "provenance": true
45
63
  },
46
64
  "scripts": {
47
- "build": "tsc -p tsconfig.build.json",
65
+ "build": "tsc -p tsconfig.build.json && node scripts/bundle-client.js",
48
66
  "typecheck": "tsc -p tsconfig.json --noEmit",
49
- "test": "node --import tsx --test test/session-reader.test.ts",
67
+ "test": "node --import tsx --test test/session-reader.test.ts test/dsh-adapter.test.ts test/web.test.ts",
50
68
  "prepack": "npm run build",
51
69
  "1session": "tsx bin/1session.ts"
52
70
  },
@@ -55,11 +73,14 @@
55
73
  },
56
74
  "devDependencies": {
57
75
  "@types/node": "^22.10.2",
76
+ "esbuild": "^0.28.2",
58
77
  "tsx": "^4.19.2",
59
78
  "typescript": "^5.7.2"
60
79
  },
61
80
  "dependencies": {
81
+ "@1agents/chat-ui": "^0.1.0",
62
82
  "@1agents/dreammate-network": "^0.3.0",
63
- "@1agents/dreammate-node": "^0.4.0"
83
+ "@1agents/dreammate-node": "^0.4.0",
84
+ "preact": "^10.19.6"
64
85
  }
65
86
  }
@@ -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. 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.
3
+ description: 通过 1session CLI 从本地原始会话文件中检索并读取用户在 Claude Code、Codex、Antigravity、Grok 和 DeepSeek Harness (dsh) 中沉淀的历史 AI 编程会话。每当用户提及之前会话中完成的工作而非当前对话内容时激活此技能——例如“上次/之前/昨天我们改了什么”、“那个报错后来怎么解决的”、“我在哪个会话里提过 X”、“这个功能是哪一轮加的”、“codex 那边做到哪了”、“grok/dsh 那边呢”、“跨项目找一下”、“整理一下最近几天的会话/写个周报”。在要求用户重复解释本机上已有明确记录的上下文之前,也应主动调用此技能——答案通常已保存在磁盘中。纯只读设计:绝不修改或恢复历史会话。本技能还包含自身的自举运行与分发逻辑:在未安装 1session CLI 时自动降级为 npx 随用随走,并支持将包全局安装以及一键同步技能至全部五款 Agent——因此当用户要求安装、升级、卸载、分享或分发 1session / session-reader,或报告未找到 1session 命令时也可使用本技能。
4
4
  ---
5
5
 
6
6
  # 1session — the cross-agent Read Plane
@@ -79,6 +79,7 @@ whole ladder by reflex:
79
79
  | "find where we talked about X / which turn was that" | `turns` |
80
80
  | "what exactly did it do at step N" | `turn <n>`, then `--event k` |
81
81
  | "what did all the agents do in this project" | `workspace` |
82
+ | "open a browser / web UI for sessions" (no DSH) | `web` |
82
83
 
83
84
  "What happened in that session" and "find where we talked about X" look like the
84
85
  same question and are not. `overview` compresses a session into statistics and
@@ -141,6 +141,22 @@ The file ledger never contains `candidate` paths — a path only enters after a
141
141
  write is proven. Here-doc bodies are stripped before command analysis, so source
142
142
  code being written to a file cannot masquerade as a redirect or an `ssh` host.
143
143
 
144
+ ## Standalone web UI
145
+
146
+ ```
147
+ 1session web [--port 7780] [--host 127.0.0.1] [--open] [--scope <path>|cwd|global]
148
+ ```
149
+
150
+ A local browser for the same session list, chat preview and file ledger the DSH
151
+ plugin shows. Does not require DSH. Default bind is loopback `127.0.0.1:7780`.
152
+
153
+ The listing is metadata-only; clicking a row loads that session's turns and
154
+ files. `--open` launches the default browser. `--scope global` starts the UI
155
+ on every workspace; a path scopes "当前工作区" to that directory.
156
+
157
+ This is not `1session serve`. `serve` is the DreamMate Network JSON service;
158
+ `web` is a human-facing page.
159
+
144
160
  ## Index and graph
145
161
 
146
162
  ```