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.
Files changed (40) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +65 -3
  3. package/extensions/inspect.ts +54 -52
  4. package/lib/agent-dir.js +18 -0
  5. package/lib/app.js +144 -0
  6. package/lib/args.js +30 -0
  7. package/lib/editor.js +26 -0
  8. package/lib/snapshot.js +57 -79
  9. package/lib/watch.js +29 -0
  10. package/package.json +9 -8
  11. package/public/app.js +676 -591
  12. package/public/fonts/OFL-IBM-Plex.txt +93 -0
  13. package/public/fonts/OFL-Playfair-Display.txt +93 -0
  14. package/public/fonts/fonts.css +244 -0
  15. package/public/fonts/ibm-plex-mono-cyrillic-400.woff2 +0 -0
  16. package/public/fonts/ibm-plex-mono-cyrillic-500.woff2 +0 -0
  17. package/public/fonts/ibm-plex-mono-cyrillic-600.woff2 +0 -0
  18. package/public/fonts/ibm-plex-mono-cyrillic-ext-400.woff2 +0 -0
  19. package/public/fonts/ibm-plex-mono-cyrillic-ext-500.woff2 +0 -0
  20. package/public/fonts/ibm-plex-mono-cyrillic-ext-600.woff2 +0 -0
  21. package/public/fonts/ibm-plex-mono-latin-400.woff2 +0 -0
  22. package/public/fonts/ibm-plex-mono-latin-500.woff2 +0 -0
  23. package/public/fonts/ibm-plex-mono-latin-600.woff2 +0 -0
  24. package/public/fonts/ibm-plex-mono-latin-ext-400.woff2 +0 -0
  25. package/public/fonts/ibm-plex-mono-latin-ext-500.woff2 +0 -0
  26. package/public/fonts/ibm-plex-mono-latin-ext-600.woff2 +0 -0
  27. package/public/fonts/ibm-plex-mono-vietnamese-400.woff2 +0 -0
  28. package/public/fonts/ibm-plex-mono-vietnamese-500.woff2 +0 -0
  29. package/public/fonts/ibm-plex-mono-vietnamese-600.woff2 +0 -0
  30. package/public/fonts/playfair-display-cyrillic-400.woff2 +0 -0
  31. package/public/fonts/playfair-display-latin-400.woff2 +0 -0
  32. package/public/fonts/playfair-display-latin-ext-400.woff2 +0 -0
  33. package/public/fonts/playfair-display-vietnamese-400.woff2 +0 -0
  34. package/public/index.html +96 -75
  35. package/public/share.js +119 -0
  36. package/public/style.css +416 -1245
  37. package/public/sw.js +14 -20
  38. package/server.js +27 -198
  39. package/themes/pi-dark.json +0 -21
  40. package/themes/pi-light.json +0 -21
package/public/index.html CHANGED
@@ -4,6 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>pi-inspect</title>
7
+ <link rel="stylesheet" href="fonts/fonts.css">
7
8
  <link rel="stylesheet" href="style.css">
8
9
  <link rel="manifest" href="manifest.webmanifest">
9
10
  <link rel="icon" type="image/svg+xml" href="icon.svg">
@@ -14,108 +15,128 @@
14
15
  <meta name="theme-color" content="#e8e6e3" media="(prefers-color-scheme: light)">
15
16
  </head>
16
17
  <body>
17
- <div class="topbar">
18
+ <header class="topbar">
18
19
  <div class="topbar-logo">
19
20
  <div class="logo-mark">
20
21
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.35-4.35"/></svg>
21
22
  </div>
22
23
  <span class="logo-text">pi <span class="accent">inspect</span></span>
23
24
  </div>
24
- <div class="spacer"></div>
25
- <div class="topbar-search">
25
+ <label class="topbar-search">
26
26
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
27
- <input type="text" id="searchInput" placeholder="Filter tools / commands..." autocomplete="off">
28
- </div>
29
- <select class="topbar-select" id="kindFilter">
30
- <option value="all" selected>All</option>
31
- <option value="context">Context</option>
32
- <option value="tool">Tools</option>
33
- <option value="command">Commands</option>
34
- <option value="prompt">Prompts</option>
35
- <option value="skill">Skills</option>
36
- </select>
37
- <select class="topbar-select" id="sessionSelect" title="Switch session"></select>
38
- <button class="topbar-btn" id="cleanupSessionsBtn" title="Delete all snapshots except the selected session">
39
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-2 14a2 2 0 01-2 2H9a2 2 0 01-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a2 2 0 012-2h2a2 2 0 012 2v2"/></svg>
40
- </button>
41
- <div class="topbar-project" id="projectBtn" title="Current cwd">
27
+ <input type="text" id="searchInput" placeholder="Filter by name or source" autocomplete="off" aria-label="Filter by name or source">
28
+ <kbd>/</kbd>
29
+ </label>
30
+ <select class="topbar-select" id="sessionSelect" aria-label="Session" title="Session (s)"></select>
31
+ <div class="topbar-project" title="Working directory">
42
32
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
43
33
  <span id="projectPath">—</span>
44
34
  </div>
45
- <button class="topbar-btn" id="refreshBtn" title="Refresh data">
46
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>
47
- Refresh
48
- </button>
49
- <button class="topbar-btn" id="themeBtn" title="Toggle theme">
50
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
51
- </button>
52
- <button class="topbar-btn" id="shortcutsBtn" title="Keyboard shortcuts (?)">
53
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><circle cx="12" cy="17" r="0.5" fill="currentColor"/></svg>
54
- <kbd style="font-size:10px">?</kbd>
55
- </button>
56
- <a class="topbar-btn" href="https://github.com/NikiforovAll/pi-inspect" target="_blank" rel="noopener" title="GitHub">
57
- <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
58
- </a>
59
- </div>
60
-
61
- <div class="main-layout">
62
- <div class="tree-panel" id="treePanel">
63
- <div class="tree-expand-toggle" id="expandToggle">Expand all</div>
64
- <div class="tree-container" id="treeContainer">
65
- <div class="loading">Loading snapshot...</div>
66
- </div>
35
+ <div class="spacer"></div>
36
+ <div class="topbar-actions">
37
+ <button class="icon-btn" id="cleanupSessionsBtn" title="Delete the other snapshots" aria-label="Delete the other snapshots">
38
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-2 14a2 2 0 01-2 2H9a2 2 0 01-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a2 2 0 012-2h2a2 2 0 012 2v2"/></svg>
39
+ </button>
40
+ <button class="icon-btn" id="shareBtn" title="Copy a share link for this snapshot" aria-label="Copy a share link">
41
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
42
+ </button>
43
+ <button class="icon-btn" id="refreshBtn" title="Refresh (r)" aria-label="Refresh">
44
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>
45
+ </button>
46
+ <button class="icon-btn" id="themeBtn" title="Toggle theme (t)" aria-label="Toggle theme">
47
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
48
+ </button>
49
+ <button class="icon-btn" id="shortcutsBtn" title="Keyboard shortcuts (?)" aria-label="Keyboard shortcuts">
50
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 10h.01M10 10h.01M14 10h.01M18 10h.01M8 14h8"/></svg>
51
+ </button>
52
+ <a class="icon-btn" id="githubLink" href="https://github.com/NikiforovAll/pi-inspect" target="_blank" rel="noopener" title="GitHub" aria-label="GitHub">
53
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
54
+ </a>
67
55
  </div>
68
- <div class="resize-handle" id="resizeHandle"></div>
69
- <div class="detail-panel" id="detailPanel">
70
- <div class="detail-empty">
71
- <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" opacity="0.3"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.35-4.35"/></svg>
72
- <span>Select an item to view details</span>
56
+ </header>
57
+
58
+ <nav class="chipbar" aria-label="Filter by kind">
59
+ <div class="chips" id="kindChips" role="radiogroup" aria-label="Kind"></div>
60
+ <div class="spacer"></div>
61
+ <button class="text-btn" id="expandToggle" title="Expand or collapse all (e)">Collapse all</button>
62
+ </nav>
63
+
64
+ <main class="main-layout">
65
+ <section class="tree-panel" id="treePanel">
66
+ <div class="tree-container" id="treeContainer" role="tree" aria-label="Snapshot items" tabindex="0">
67
+ <div class="empty-state">Loading snapshot…</div>
73
68
  </div>
74
- </div>
75
- </div>
69
+ </section>
70
+ <div class="resize-handle" id="resizeHandle" aria-hidden="true"></div>
71
+ <section class="detail-panel" id="detailPanel" aria-live="polite"></section>
72
+ </main>
76
73
 
77
- <div class="toast-container" id="toast"></div>
74
+ <div class="toast-container" id="toast" role="status"></div>
78
75
 
79
76
  <div class="modal-overlay" id="helpModal" aria-hidden="true">
80
- <div class="modal help-modal" role="dialog" aria-label="Keyboard shortcuts">
77
+ <div class="modal help-modal" role="dialog" aria-modal="true" aria-labelledby="helpTitle">
81
78
  <div class="modal-header">
82
- <h3>Keyboard Shortcuts</h3>
83
- <button class="modal-close" id="helpCloseBtn" title="Close (Esc)">&#10005;</button>
79
+ <h3 id="helpTitle">Keyboard shortcuts</h3>
80
+ <button class="icon-btn" id="helpCloseBtn" title="Close (Esc)" aria-label="Close">&#10005;</button>
81
+ </div>
82
+ <div class="modal-body help-sections">
83
+ <section class="help-section">
84
+ <h4>Navigate</h4>
85
+ <table>
86
+ <tr><td><kbd>j</kbd> <kbd>k</kbd></td><td>Next / previous row</td></tr>
87
+ <tr><td><kbd>g</kbd> <kbd>G</kbd></td><td>First / last row</td></tr>
88
+ <tr><td><kbd>l</kbd> <kbd>h</kbd></td><td>Expand group / go to parent</td></tr>
89
+ <tr><td><kbd>Enter</kbd></td><td>Open row</td></tr>
90
+ <tr><td><kbd>e</kbd></td><td>Expand or collapse all</td></tr>
91
+ </table>
92
+ </section>
93
+ <section class="help-section">
94
+ <h4>Filter</h4>
95
+ <table>
96
+ <tr><td><kbd>/</kbd></td><td>Search</td></tr>
97
+ <tr><td><kbd>0</kbd>–<kbd>5</kbd></td><td>All, context, tools, commands, prompts, skills</td></tr>
98
+ <tr><td><kbd>s</kbd></td><td>Pick a session</td></tr>
99
+ </table>
100
+ </section>
101
+ <section class="help-section">
102
+ <h4>Item</h4>
103
+ <table>
104
+ <tr><td><kbd>o</kbd></td><td>Open in editor</td></tr>
105
+ <tr><td><kbd>c</kbd></td><td>Copy path</td></tr>
106
+ <tr><td><kbd>Esc</kbd></td><td>Close item / leave input</td></tr>
107
+ </table>
108
+ </section>
109
+ <section class="help-section">
110
+ <h4>App</h4>
111
+ <table>
112
+ <tr><td><kbd>r</kbd></td><td>Refresh</td></tr>
113
+ <tr class="standalone-only"><td><kbd>t</kbd></td><td>Toggle theme</td></tr>
114
+ <tr><td><kbd>?</kbd></td><td>This help</td></tr>
115
+ </table>
116
+ </section>
84
117
  </div>
118
+ </div>
119
+ </div>
120
+
121
+ <div class="modal-overlay" id="confirmModal" aria-hidden="true">
122
+ <div class="modal confirm-modal" role="alertdialog" aria-modal="true" aria-labelledby="confirmTitle" aria-describedby="confirmText">
123
+ <div class="modal-header"><h3 id="confirmTitle">Delete snapshots</h3></div>
85
124
  <div class="modal-body">
86
- <div class="help-sections">
87
- <div class="help-section">
88
- <h4>Global</h4>
89
- <table>
90
- <tr><td><kbd>?</kbd></td><td>Show this help</td></tr>
91
- <tr><td><kbd>/</kbd></td><td>Focus search</td></tr>
92
- <tr><td><kbd>f</kbd></td><td>Focus kind filter</td></tr>
93
- <tr><td><kbd>r</kbd></td><td>Refresh</td></tr>
94
- <tr><td><kbd>t</kbd></td><td>Toggle theme</td></tr>
95
- <tr><td><kbd>e</kbd></td><td>Expand / collapse all</td></tr>
96
- <tr><td><kbd>Esc</kbd></td><td>Close modal / clear selection / blur input</td></tr>
97
- </table>
98
- </div>
99
- <div class="help-section">
100
- <h4>Navigation</h4>
101
- <table>
102
- <tr><td><kbd>j</kbd> / <kbd>↓</kbd></td><td>Next row</td></tr>
103
- <tr><td><kbd>k</kbd> / <kbd>↑</kbd></td><td>Previous row</td></tr>
104
- <tr><td><kbd>l</kbd> / <kbd>→</kbd></td><td>Expand group</td></tr>
105
- <tr><td><kbd>h</kbd> / <kbd>←</kbd></td><td>Collapse group / go to parent</td></tr>
106
- <tr><td><kbd>Enter</kbd> / <kbd>Space</kbd></td><td>Select / toggle</td></tr>
107
- </table>
108
- </div>
125
+ <p id="confirmText"></p>
126
+ <div class="modal-actions">
127
+ <button class="btn" id="confirmCancel">Cancel</button>
128
+ <button class="btn btn-danger" id="confirmOk">Delete</button>
109
129
  </div>
110
130
  </div>
111
131
  </div>
112
132
  </div>
113
133
 
134
+ <script src="share.js"></script>
114
135
  <script src="app.js"></script>
115
136
  <script>
116
137
  if ('serviceWorker' in navigator) {
117
138
  window.addEventListener('load', () => {
118
- navigator.serviceWorker.register('/sw.js').catch((e) => console.warn('sw register failed:', e));
139
+ navigator.serviceWorker.register('./sw.js').catch((e) => console.warn('sw register failed:', e));
119
140
  });
120
141
  }
121
142
  </script>
@@ -0,0 +1,119 @@
1
+ // Shareable snapshot encoding: JSON → deflate-raw → base64url.
2
+ // URL shape mirrors plannotator.ai: `#s=<base64url>`.
3
+
4
+ const HASH_PREFIX = '#s=';
5
+
6
+ function bytesToBase64Url(bytes) {
7
+ let bin = '';
8
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
9
+ return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
10
+ }
11
+
12
+ function base64UrlToBytes(str) {
13
+ const pad = str.length % 4 === 0 ? '' : '='.repeat(4 - (str.length % 4));
14
+ const b64 = str.replace(/-/g, '+').replace(/_/g, '/') + pad;
15
+ const bin = atob(b64);
16
+ const out = new Uint8Array(bin.length);
17
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
18
+ return out;
19
+ }
20
+
21
+ async function streamThrough(transformer, bytes) {
22
+ const stream = new Blob([bytes]).stream().pipeThrough(transformer);
23
+ const buf = await new Response(stream).arrayBuffer();
24
+ return new Uint8Array(buf);
25
+ }
26
+
27
+ // Path-redaction: strip the sender's HOME prefix from every string and key,
28
+ // in both slash flavors. Preserves project basename + relative tail so the
29
+ // shared view still has useful structure ("<home>/dev/pi-inspect").
30
+ function escapeRegex(s) {
31
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
32
+ }
33
+
34
+ function deriveHome(cwd) {
35
+ if (!cwd || typeof cwd !== 'string') return null;
36
+ const patterns = [
37
+ /^([a-zA-Z]:[\\/]Users[\\/][^\\/]+)/, // Windows: C:\Users\<user>
38
+ /^(\/Users\/[^/]+)/, // macOS: /Users/<user>
39
+ /^(\/home\/[^/]+)/, // Linux: /home/<user>
40
+ /^(\/[a-zA-Z]\/Users\/[^/]+)/, // Git Bash: /c/Users/<user>
41
+ ];
42
+ for (const re of patterns) {
43
+ const m = cwd.match(re);
44
+ if (m) return m[1];
45
+ }
46
+ return null;
47
+ }
48
+
49
+ function buildHomeReplacers(cwd) {
50
+ const home = deriveHome(cwd);
51
+ if (!home) return [];
52
+ const alt = home.includes('\\') ? home.replace(/\\/g, '/') : home.replace(/\//g, '\\');
53
+ const variants = new Set([home, alt]);
54
+ return [...variants].map((v) => [new RegExp(escapeRegex(v), 'g'), '<home>']);
55
+ }
56
+
57
+ function redactString(s, reps) {
58
+ if (typeof s !== 'string') return s;
59
+ let out = s;
60
+ for (const [re, rep] of reps) out = out.replace(re, rep);
61
+ return out;
62
+ }
63
+
64
+ // Keys whose values are home-path-keyed maps — redact keys here too.
65
+ const PATH_KEYED_MAPS = new Set(['githubSources']);
66
+
67
+ function redactDeep(value, reps, redactKeys) {
68
+ if (value == null) return value;
69
+ if (typeof value === 'string') return redactString(value, reps);
70
+ if (Array.isArray(value)) return value.map((v) => redactDeep(v, reps, false));
71
+ if (typeof value === 'object') {
72
+ const out = {};
73
+ for (const [k, v] of Object.entries(value)) {
74
+ const newKey = redactKeys ? redactString(k, reps) : k;
75
+ out[newKey] = redactDeep(v, reps, PATH_KEYED_MAPS.has(k));
76
+ }
77
+ return out;
78
+ }
79
+ return value;
80
+ }
81
+
82
+ function redactSnapshot(snapshot) {
83
+ if (!snapshot) return snapshot;
84
+ const reps = buildHomeReplacers(snapshot.cwd || '');
85
+ if (!reps.length) return snapshot;
86
+ return redactDeep(snapshot, reps, false);
87
+ }
88
+
89
+ async function encodeSnapshot(snapshot) {
90
+ const json = JSON.stringify(redactSnapshot(snapshot));
91
+ const raw = new TextEncoder().encode(json);
92
+ const compressed = await streamThrough(new CompressionStream('deflate-raw'), raw);
93
+ return bytesToBase64Url(compressed);
94
+ }
95
+
96
+ async function decodeSnapshot(encoded) {
97
+ const compressed = base64UrlToBytes(encoded);
98
+ const raw = await streamThrough(new DecompressionStream('deflate-raw'), compressed);
99
+ return JSON.parse(new TextDecoder().decode(raw));
100
+ }
101
+
102
+ function getSharedSnapshotParam() {
103
+ const h = location.hash || '';
104
+ return h.startsWith(HASH_PREFIX) ? h.slice(HASH_PREFIX.length) : null;
105
+ }
106
+
107
+ const PUBLIC_BASE_URL = 'https://nikiforovall.blog/pi-inspect/';
108
+
109
+ function buildShareUrl(encoded) {
110
+ // Local pi-inspect runs on localhost — recipients can't open that. Always
111
+ // anchor share links on the hosted static dashboard. When the current page
112
+ // is already a non-localhost origin (e.g. the hosted site itself), reuse it.
113
+ const host = location.hostname;
114
+ const isLocal = !host || host === 'localhost' || host === '127.0.0.1' || host === '::1';
115
+ const base = isLocal ? PUBLIC_BASE_URL : `${location.origin}${location.pathname}`;
116
+ return `${base}${HASH_PREFIX}${encoded}`;
117
+ }
118
+
119
+ window.piShare = { encodeSnapshot, decodeSnapshot, getSharedSnapshotParam, buildShareUrl, redactSnapshot };