pi-inspect 0.5.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 +58 -4
- 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 +646 -622
- 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 +94 -78
- package/public/style.css +416 -1245
- package/public/sw.js +13 -21
- package/server.js +27 -198
- package/themes/pi-dark.json +0 -21
- package/themes/pi-light.json +0 -21
package/public/sw.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
const VERSION = 'pi-inspect-
|
|
3
|
-
// Resolve relative to the SW scope so this works under any subpath (e.g. GitHub Pages).
|
|
1
|
+
// Network first, so a new server version shows on the next load; the cache only serves the shell offline (GitHub Pages share links).
|
|
2
|
+
const VERSION = 'pi-inspect-v8';
|
|
4
3
|
const BASE = new URL('./', self.registration?.scope || self.location.href).pathname;
|
|
5
|
-
const SHELL = ['', 'index.html', 'style.css', 'app.js', 'share.js', 'manifest.webmanifest', 'icon.svg'].map((p) => BASE + p);
|
|
4
|
+
const SHELL = ['', 'index.html', 'style.css', 'app.js', 'share.js', 'fonts/fonts.css', 'manifest.webmanifest', 'icon.svg'].map((p) => BASE + p);
|
|
6
5
|
|
|
7
6
|
self.addEventListener('install', (event) => {
|
|
8
7
|
event.waitUntil(caches.open(VERSION).then((c) => c.addAll(SHELL)).catch(() => {}));
|
|
@@ -20,24 +19,17 @@ self.addEventListener('fetch', (event) => {
|
|
|
20
19
|
const req = event.request;
|
|
21
20
|
if (req.method !== 'GET') return;
|
|
22
21
|
const url = new URL(req.url);
|
|
23
|
-
if (url.origin !== self.location.origin) return;
|
|
22
|
+
if (url.origin !== self.location.origin || url.pathname.startsWith('/api/')) return;
|
|
24
23
|
|
|
25
|
-
// Never cache API or SSE — always go to network.
|
|
26
|
-
if (url.pathname.startsWith('/api/')) return;
|
|
27
|
-
|
|
28
|
-
// Static shell: cache-first, fall back to network, then update cache.
|
|
29
24
|
event.respondWith(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.catch(() => cached);
|
|
40
|
-
return cached || fetchPromise;
|
|
41
|
-
}),
|
|
25
|
+
fetch(req)
|
|
26
|
+
.then((res) => {
|
|
27
|
+
if (res.ok) {
|
|
28
|
+
const copy = res.clone();
|
|
29
|
+
caches.open(VERSION).then((c) => c.put(req, copy)).catch(() => {});
|
|
30
|
+
}
|
|
31
|
+
return res;
|
|
32
|
+
})
|
|
33
|
+
.catch(() => caches.match(req, { ignoreSearch: true }).then((hit) => hit || Response.error())),
|
|
42
34
|
);
|
|
43
35
|
});
|
package/server.js
CHANGED
|
@@ -1,206 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
'use strict';
|
|
3
3
|
const path = require('node:path');
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const openSession = (() => {
|
|
17
|
-
const i = args.indexOf('--session');
|
|
18
|
-
return i >= 0 ? args[i + 1] : null;
|
|
19
|
-
})();
|
|
20
|
-
|
|
21
|
-
const BUILTIN_THEME_DIR = path.join(__dirname, 'themes');
|
|
22
|
-
const USER_THEME_DIR = process.env.INSPECT_THEME_DIR
|
|
23
|
-
|| path.join(os.homedir(), '.pi', 'agent', 'inspect', 'themes');
|
|
24
|
-
|
|
25
|
-
const app = express();
|
|
26
|
-
app.disable('x-powered-by');
|
|
27
|
-
app.use(express.json({ limit: '2mb' }));
|
|
28
|
-
|
|
29
|
-
app.get('/api/version', (_req, res) => {
|
|
30
|
-
res.json({ name: pkg.name, version: pkg.version });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
app.get('/api/sessions', async (_req, res) => {
|
|
34
|
-
try {
|
|
35
|
-
const sessions = await snapshots.readIndex();
|
|
36
|
-
const sorted = [...sessions].sort((a, b) => (b.capturedAt || 0) - (a.capturedAt || 0));
|
|
37
|
-
res.json({ sessions: sorted });
|
|
38
|
-
} catch (e) {
|
|
39
|
-
res.status(500).json({ error: e.message });
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
app.post('/api/sessions/cleanup', async (req, res) => {
|
|
44
|
-
try {
|
|
45
|
-
const keep = (req.body && req.body.keep) || req.query.keep || null;
|
|
46
|
-
const result = keep
|
|
47
|
-
? await snapshots.keepOnly(String(keep))
|
|
48
|
-
: await snapshots.cleanupIndex();
|
|
49
|
-
res.json({ ok: true, ...result });
|
|
50
|
-
} catch (e) {
|
|
51
|
-
res.status(500).json({ ok: false, error: e.message });
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const githubMemo = new Map(); // sessionId -> Record<root, {url, source}>
|
|
56
|
-
|
|
57
|
-
function collectSourceRoots(snap) {
|
|
58
|
-
const roots = new Set();
|
|
59
|
-
const add = (x) => {
|
|
60
|
-
const baseDir = x?.sourceInfo?.baseDir;
|
|
61
|
-
if (typeof baseDir === 'string' && baseDir) roots.add(baseDir);
|
|
62
|
-
};
|
|
63
|
-
for (const t of snap.tools ?? []) add(t);
|
|
64
|
-
for (const c of snap.commands ?? []) add(c);
|
|
65
|
-
return [...roots];
|
|
4
|
+
const { parseOptions } = require('./lib/args');
|
|
5
|
+
const { inspectDir } = require('./lib/agent-dir');
|
|
6
|
+
const { createApp, SESSION_PATH } = require('./lib/app');
|
|
7
|
+
const { createSnapshotStore } = require('./lib/snapshot');
|
|
8
|
+
const { watchSnapshots } = require('./lib/watch');
|
|
9
|
+
|
|
10
|
+
let options;
|
|
11
|
+
try {
|
|
12
|
+
options = parseOptions(process.argv.slice(2));
|
|
13
|
+
} catch (e) {
|
|
14
|
+
console.error(`pi-inspect: ${e.message}`);
|
|
15
|
+
process.exit(1);
|
|
66
16
|
}
|
|
67
17
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const snap = sid ? await snapshots.readSnapshot(sid) : await snapshots.readLatestSnapshot();
|
|
72
|
-
if (!snap) return res.status(404).json({ error: 'no snapshot found', sessionId: sid });
|
|
73
|
-
const memoKey = snap.sessionId;
|
|
74
|
-
let githubSources = memoKey ? githubMemo.get(memoKey) : null;
|
|
75
|
-
if (!githubSources) {
|
|
76
|
-
githubSources = await githubSource.resolveMany(collectSourceRoots(snap));
|
|
77
|
-
if (memoKey) githubMemo.set(memoKey, githubSources);
|
|
78
|
-
}
|
|
79
|
-
res.json({ ...snap, githubSources });
|
|
80
|
-
} catch (e) {
|
|
81
|
-
res.status(500).json({ error: e.message });
|
|
82
|
-
}
|
|
83
|
-
});
|
|
18
|
+
const INSPECT_DIR = inspectDir();
|
|
19
|
+
const store = createSnapshotStore(path.join(INSPECT_DIR, 'snapshots'));
|
|
20
|
+
const { app, broadcast } = createApp({ store, inspectDir: INSPECT_DIR, host: options.host });
|
|
84
21
|
|
|
85
|
-
|
|
86
|
-
async function loadThemes() {
|
|
87
|
-
for (const dir of [BUILTIN_THEME_DIR, USER_THEME_DIR]) {
|
|
88
|
-
let entries;
|
|
89
|
-
try { entries = await fsp.readdir(dir); }
|
|
90
|
-
catch (e) {
|
|
91
|
-
if (e.code !== 'ENOENT') console.warn(`themes: cannot read ${dir}: ${e.message}`);
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
for (const f of entries.filter((n) => n.toLowerCase().endsWith('.json'))) {
|
|
95
|
-
try {
|
|
96
|
-
const raw = await fsp.readFile(path.join(dir, f), 'utf8');
|
|
97
|
-
const obj = JSON.parse(raw);
|
|
98
|
-
const id = f.replace(/\.json$/i, '');
|
|
99
|
-
themes.set(id, obj);
|
|
100
|
-
} catch (e) {
|
|
101
|
-
console.warn(`themes: skip ${f}: ${e.message}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
22
|
+
watchSnapshots(store.dir, (files) => broadcast('snapshot', { files }));
|
|
106
23
|
|
|
107
|
-
app.
|
|
108
|
-
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
app.post('/api/open', (req, res) => {
|
|
112
|
-
const target = (req.body && req.body.path) || '';
|
|
113
|
-
if (!target || typeof target !== 'string') return res.status(400).json({ ok: false, error: 'missing path' });
|
|
114
|
-
const normalized = path.normalize(target);
|
|
115
|
-
if (!fs.existsSync(normalized)) return res.status(404).json({ ok: false, error: `path not found: ${normalized}` });
|
|
116
|
-
const editor = process.env.EDITOR || process.env.VISUAL || (process.platform === 'win32' ? 'code' : 'vi');
|
|
117
|
-
try {
|
|
118
|
-
const { spawn } = require('node:child_process');
|
|
119
|
-
const isWin = process.platform === 'win32';
|
|
120
|
-
const parts = editor.split(/\s+/);
|
|
121
|
-
const cmd = parts.shift();
|
|
122
|
-
const args = [...parts, normalized];
|
|
123
|
-
const quotedArgs = isWin ? args.map((a) => `"${String(a).replace(/"/g, '\\"')}"`) : args;
|
|
124
|
-
const child = spawn(cmd, quotedArgs, {
|
|
125
|
-
detached: true,
|
|
126
|
-
stdio: 'ignore',
|
|
127
|
-
shell: isWin,
|
|
128
|
-
windowsVerbatimArguments: isWin,
|
|
129
|
-
});
|
|
130
|
-
child.on('error', (err) => console.warn(`open failed: ${err.message}`));
|
|
131
|
-
child.unref();
|
|
132
|
-
res.json({ ok: true, editor, path: normalized });
|
|
133
|
-
} catch (e) {
|
|
134
|
-
res.status(500).json({ ok: false, error: e.message });
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const REQ_DIR = path.join(os.homedir(), '.pi', 'agent', 'inspect', 'requests');
|
|
139
|
-
|
|
140
|
-
app.post('/api/toggle', async (req, res) => {
|
|
141
|
-
try {
|
|
142
|
-
const { action, resourceKind, path: target, scope } = req.body || {};
|
|
143
|
-
if (action !== 'enable' && action !== 'disable') return res.status(400).json({ ok: false, error: 'action must be enable|disable' });
|
|
144
|
-
if (!['skills', 'prompts', 'extensions', 'themes'].includes(resourceKind)) return res.status(400).json({ ok: false, error: 'invalid resourceKind' });
|
|
145
|
-
if (!target || typeof target !== 'string') return res.status(400).json({ ok: false, error: 'missing path' });
|
|
146
|
-
const useScope = scope === 'project' ? 'project' : 'user';
|
|
147
|
-
await fsp.mkdir(REQ_DIR, { recursive: true });
|
|
148
|
-
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
149
|
-
const file = path.join(REQ_DIR, `${id}.json`);
|
|
150
|
-
const tmp = `${file}.tmp`;
|
|
151
|
-
const body = JSON.stringify({ id, ts: Date.now(), action, resourceKind, path: target, scope: useScope });
|
|
152
|
-
await fsp.writeFile(tmp, body, 'utf8');
|
|
153
|
-
await fsp.rename(tmp, file);
|
|
154
|
-
res.json({ ok: true, id });
|
|
155
|
-
} catch (e) {
|
|
156
|
-
res.status(500).json({ ok: false, error: e.message });
|
|
24
|
+
const server = app.listen(options.port, options.host, async (err) => {
|
|
25
|
+
if (err) {
|
|
26
|
+
console.error(`pi-inspect: cannot listen on port ${options.port}: ${err.message}`);
|
|
27
|
+
process.exit(1);
|
|
157
28
|
}
|
|
29
|
+
const base = `http://localhost:${server.address().port}`;
|
|
30
|
+
console.log(`pi-inspect listening on ${base}`);
|
|
31
|
+
if (!options.open) return;
|
|
32
|
+
const url = options.session ? base + SESSION_PATH.replace('{id}', encodeURIComponent(options.session)) : base;
|
|
33
|
+
const { default: open } = await import('open');
|
|
34
|
+
open(url).catch(() => {});
|
|
158
35
|
});
|
|
159
|
-
|
|
160
|
-
app.post('/api/focus', (req, res) => {
|
|
161
|
-
const sid = (req.body && req.body.session) || req.query.session || null;
|
|
162
|
-
const count = sseClients.size;
|
|
163
|
-
if (count > 0) broadcast('navigate', { session: sid });
|
|
164
|
-
res.json({ delivered: count });
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// SSE
|
|
168
|
-
const sseClients = new Set();
|
|
169
|
-
app.get('/api/events', (req, res) => {
|
|
170
|
-
res.set({
|
|
171
|
-
'Content-Type': 'text/event-stream',
|
|
172
|
-
'Cache-Control': 'no-cache',
|
|
173
|
-
Connection: 'keep-alive',
|
|
174
|
-
});
|
|
175
|
-
res.flushHeaders();
|
|
176
|
-
res.write(': hello\n\n');
|
|
177
|
-
sseClients.add(res);
|
|
178
|
-
req.on('close', () => sseClients.delete(res));
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
function broadcast(event, data) {
|
|
182
|
-
const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
183
|
-
for (const c of sseClients) {
|
|
184
|
-
try { c.write(payload); } catch {}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const SNAP_DIR = snapshots.snapshotDir();
|
|
189
|
-
fs.mkdirSync(SNAP_DIR, { recursive: true });
|
|
190
|
-
chokidar
|
|
191
|
-
.watch(SNAP_DIR, { ignoreInitial: true, depth: 1 })
|
|
192
|
-
.on('all', (kind, file) => {
|
|
193
|
-
if (!file.endsWith('.json')) return;
|
|
194
|
-
broadcast('snapshot', { kind, file: path.basename(file) });
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
app.use(express.static(path.join(__dirname, 'public')));
|
|
198
|
-
|
|
199
|
-
(async () => {
|
|
200
|
-
await loadThemes();
|
|
201
|
-
app.listen(PORT, () => {
|
|
202
|
-
const url = `http://localhost:${PORT}${openSession ? `/?session=${encodeURIComponent(openSession)}` : ''}`;
|
|
203
|
-
console.log(`pi-inspect listening on http://localhost:${PORT}`);
|
|
204
|
-
if (shouldOpen) open(url).catch(() => {});
|
|
205
|
-
});
|
|
206
|
-
})();
|
package/themes/pi-dark.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-dark",
|
|
3
|
-
"displayName": "pi.dev Dark",
|
|
4
|
-
"mode": "dark",
|
|
5
|
-
"colors": {
|
|
6
|
-
"bgDeep": "#1a1a1a",
|
|
7
|
-
"bgSurface": "#212121",
|
|
8
|
-
"bgElevated": "#2a2a2a",
|
|
9
|
-
"bgHover": "#333333",
|
|
10
|
-
"border": "#3a3a3a",
|
|
11
|
-
"textPrimary": "#f0ebe6",
|
|
12
|
-
"textSecondary": "#c9c2bb",
|
|
13
|
-
"textTertiary": "#8b857e",
|
|
14
|
-
"textMuted": "#5f5a55",
|
|
15
|
-
"accent": "#e8927c",
|
|
16
|
-
"accentText": "#f5b8a8",
|
|
17
|
-
"tool": "#6ea8d8",
|
|
18
|
-
"command": "#a78bfa",
|
|
19
|
-
"injected": "#f0b14d"
|
|
20
|
-
}
|
|
21
|
-
}
|
package/themes/pi-light.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-light",
|
|
3
|
-
"displayName": "pi.dev Light",
|
|
4
|
-
"mode": "light",
|
|
5
|
-
"colors": {
|
|
6
|
-
"bgDeep": "#ebe7e4",
|
|
7
|
-
"bgSurface": "#f4f1ee",
|
|
8
|
-
"bgElevated": "#e0dcd8",
|
|
9
|
-
"bgHover": "#d5d1cd",
|
|
10
|
-
"border": "#b5b0aa",
|
|
11
|
-
"textPrimary": "#0d1116",
|
|
12
|
-
"textSecondary": "#212730",
|
|
13
|
-
"textTertiary": "#495059",
|
|
14
|
-
"textMuted": "#757d89",
|
|
15
|
-
"accent": "#2f5f8a",
|
|
16
|
-
"accentText": "#1d4870",
|
|
17
|
-
"tool": "#2f5f8a",
|
|
18
|
-
"command": "#7c3aed",
|
|
19
|
-
"injected": "#b45309"
|
|
20
|
-
}
|
|
21
|
-
}
|