pi-inspect 0.4.0 → 0.6.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.
- package/CHANGELOG.md +28 -0
- package/README.md +65 -3
- package/extensions/inspect.ts +54 -52
- package/lib/agent-dir.js +18 -0
- package/lib/app.js +144 -0
- package/lib/args.js +30 -0
- package/lib/editor.js +26 -0
- package/lib/snapshot.js +57 -79
- package/lib/watch.js +29 -0
- package/package.json +9 -8
- package/public/app.js +676 -591
- package/public/fonts/OFL-IBM-Plex.txt +93 -0
- package/public/fonts/OFL-Playfair-Display.txt +93 -0
- package/public/fonts/fonts.css +244 -0
- package/public/fonts/ibm-plex-mono-cyrillic-400.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-cyrillic-500.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-cyrillic-600.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-cyrillic-ext-400.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-cyrillic-ext-500.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-cyrillic-ext-600.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-400.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-500.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-600.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-ext-400.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-ext-500.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-ext-600.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-vietnamese-400.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-vietnamese-500.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-vietnamese-600.woff2 +0 -0
- package/public/fonts/playfair-display-cyrillic-400.woff2 +0 -0
- package/public/fonts/playfair-display-latin-400.woff2 +0 -0
- package/public/fonts/playfair-display-latin-ext-400.woff2 +0 -0
- package/public/fonts/playfair-display-vietnamese-400.woff2 +0 -0
- package/public/index.html +96 -75
- package/public/share.js +119 -0
- package/public/style.css +416 -1245
- package/public/sw.js +14 -20
- package/server.js +27 -198
- package/themes/pi-dark.json +0 -21
- package/themes/pi-light.json +0 -21
package/lib/snapshot.js
CHANGED
|
@@ -1,102 +1,80 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
const fsp = require('node:fs/promises');
|
|
3
3
|
const path = require('node:path');
|
|
4
|
-
const os = require('node:os');
|
|
5
4
|
|
|
6
|
-
const
|
|
7
|
-
|| path.join(os.homedir(), '.pi', 'agent', 'inspect', 'snapshots');
|
|
8
|
-
const INDEX_PATH = path.join(SNAPSHOT_DIR, 'index.json');
|
|
9
|
-
|
|
10
|
-
function snapshotDir() {
|
|
11
|
-
return SNAPSHOT_DIR;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function snapshotPath(sessionId) {
|
|
15
|
-
return path.join(SNAPSHOT_DIR, `${sanitize(sessionId)}.json`);
|
|
16
|
-
}
|
|
5
|
+
const INDEX_FILE = 'index.json';
|
|
17
6
|
|
|
7
|
+
// Same rule as the extension's file names, so an id maps to the file it wrote.
|
|
18
8
|
function sanitize(id) {
|
|
19
9
|
return String(id).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
20
10
|
}
|
|
21
11
|
|
|
22
|
-
async function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} catch (e) {
|
|
28
|
-
if (e.code !== 'ENOENT') console.warn(`pi-inspect: read index: ${e.message}`);
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
12
|
+
async function writeJsonAtomic(file, value) {
|
|
13
|
+
await fsp.mkdir(path.dirname(file), { recursive: true });
|
|
14
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
15
|
+
await fsp.writeFile(tmp, JSON.stringify(value, null, 2), 'utf8');
|
|
16
|
+
await fsp.rename(tmp, file);
|
|
31
17
|
}
|
|
32
18
|
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const raw = await fsp.readFile(snapshotPath(sessionId), 'utf8');
|
|
36
|
-
return JSON.parse(raw);
|
|
37
|
-
} catch (e) {
|
|
38
|
-
if (e.code === 'ENOENT') return null;
|
|
39
|
-
throw e;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
19
|
+
const byCapturedDesc = (a, b) => (b.capturedAt || 0) - (a.capturedAt || 0);
|
|
42
20
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
21
|
+
function createSnapshotStore(dir) {
|
|
22
|
+
const indexPath = path.join(dir, INDEX_FILE);
|
|
23
|
+
const snapshotPath = (id) => path.join(dir, `${sanitize(id)}.json`);
|
|
47
24
|
|
|
48
|
-
async function
|
|
49
|
-
const sessions = await readIndex();
|
|
50
|
-
const kept = [];
|
|
51
|
-
const removed = [];
|
|
52
|
-
for (const s of sessions) {
|
|
25
|
+
async function readIndex() {
|
|
53
26
|
try {
|
|
54
|
-
await fsp.
|
|
55
|
-
|
|
56
|
-
} catch {
|
|
57
|
-
|
|
27
|
+
const parsed = JSON.parse(await fsp.readFile(indexPath, 'utf8'));
|
|
28
|
+
return Array.isArray(parsed?.sessions) ? parsed.sessions : [];
|
|
29
|
+
} catch (e) {
|
|
30
|
+
if (e.code !== 'ENOENT') console.warn(`pi-inspect: read index: ${e.message}`);
|
|
31
|
+
return [];
|
|
58
32
|
}
|
|
59
33
|
}
|
|
60
|
-
if (removed.length) await writeIndex(kept);
|
|
61
|
-
return { kept: kept.length, removed };
|
|
62
|
-
}
|
|
63
34
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
for (const f of files) {
|
|
74
|
-
if (!f.endsWith('.json') || f === 'index.json' || f === keepPath) continue;
|
|
35
|
+
function writeIndex(sessions) {
|
|
36
|
+
return writeJsonAtomic(indexPath, { sessions });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function listSessions() {
|
|
40
|
+
return (await readIndex()).sort(byCapturedDesc);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function readSnapshot(id) {
|
|
75
44
|
try {
|
|
76
|
-
await fsp.
|
|
77
|
-
removed.push(f.replace(/\.json$/, ''));
|
|
45
|
+
return JSON.parse(await fsp.readFile(snapshotPath(id), 'utf8'));
|
|
78
46
|
} catch (e) {
|
|
79
|
-
|
|
47
|
+
if (e.code === 'ENOENT') return null;
|
|
48
|
+
throw e;
|
|
80
49
|
}
|
|
81
50
|
}
|
|
82
|
-
await writeIndex(kept);
|
|
83
|
-
return { kept: kept.length, removed };
|
|
84
|
-
}
|
|
85
51
|
|
|
86
|
-
async function readLatestSnapshot() {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
52
|
+
async function readLatestSnapshot() {
|
|
53
|
+
const [latest] = await listSessions();
|
|
54
|
+
return latest ? readSnapshot(latest.id) : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function keepOnly(id) {
|
|
58
|
+
if (!id) throw new Error('session id required');
|
|
59
|
+
const keepFile = path.basename(snapshotPath(id));
|
|
60
|
+
let files;
|
|
61
|
+
try { files = await fsp.readdir(dir); } catch { files = []; }
|
|
62
|
+
const removed = [];
|
|
63
|
+
for (const f of files) {
|
|
64
|
+
if (!f.endsWith('.json') || f === INDEX_FILE || f === keepFile) continue;
|
|
65
|
+
try {
|
|
66
|
+
await fsp.unlink(path.join(dir, f));
|
|
67
|
+
removed.push(f.slice(0, -'.json'.length));
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.warn(`pi-inspect: unlink ${f}: ${e.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const kept = (await readIndex()).filter((s) => s.id === id);
|
|
73
|
+
await writeIndex(kept);
|
|
74
|
+
return { kept: kept.length, removed };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { dir, snapshotPath, writeIndex, listSessions, readSnapshot, readLatestSnapshot, keepOnly };
|
|
91
78
|
}
|
|
92
79
|
|
|
93
|
-
module.exports = {
|
|
94
|
-
snapshotDir,
|
|
95
|
-
snapshotPath,
|
|
96
|
-
readIndex,
|
|
97
|
-
writeIndex,
|
|
98
|
-
cleanupIndex,
|
|
99
|
-
keepOnly,
|
|
100
|
-
readSnapshot,
|
|
101
|
-
readLatestSnapshot,
|
|
102
|
-
};
|
|
80
|
+
module.exports = { createSnapshotStore, sanitize, writeJsonAtomic };
|
package/lib/watch.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
|
|
4
|
+
// fs.watch fires several events per write, so changes are batched into one call per quiet period.
|
|
5
|
+
function watchSnapshots(dir, onChange, { delayMs = 150 } = {}) {
|
|
6
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
7
|
+
let timer = null;
|
|
8
|
+
const changed = new Set();
|
|
9
|
+
// libuv aborts when it watches a Windows 8.3 short path (C:\Users\NAME~1\...), so watch the long form.
|
|
10
|
+
const watcher = fs.watch(fs.realpathSync.native(dir), (_event, filename) => {
|
|
11
|
+
const name = filename && String(filename);
|
|
12
|
+
if (!name || !name.endsWith('.json')) return;
|
|
13
|
+
changed.add(name);
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
timer = setTimeout(() => {
|
|
16
|
+
const files = [...changed];
|
|
17
|
+
changed.clear();
|
|
18
|
+
onChange(files);
|
|
19
|
+
}, delayMs);
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
close() {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
watcher.close();
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { watchSnapshots };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-inspect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Introspection dashboard for the pi coding agent — tools, slash commands, skills, and the system prompt injected on init.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -29,22 +29,23 @@
|
|
|
29
29
|
"lib/",
|
|
30
30
|
"public/",
|
|
31
31
|
"extensions/",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
32
|
+
"README.md",
|
|
33
|
+
"CHANGELOG.md"
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
36
|
"start": "node server.js",
|
|
37
37
|
"dev": "node server.js --open",
|
|
38
|
+
"test": "node --test test/*.test.js",
|
|
38
39
|
"typecheck:extensions": "tsc -p extensions/tsconfig.json --noEmit"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"open": "^10.1.0"
|
|
42
|
+
"express": "^5.3.0",
|
|
43
|
+
"open": "^11.0.4"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@
|
|
47
|
-
"
|
|
46
|
+
"@earendil-works/pi-coding-agent": "^1.4.0",
|
|
47
|
+
"@types/node": "^25.9.9",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
51
|
"@earendil-works/pi-coding-agent": "*"
|