ccsniff 1.0.28 → 1.0.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccsniff",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "Watch Claude Code JSONL output files and emit structured events as a Node.js EventEmitter",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { JsonlReplayer, rollup } from './index.js';
2
+ import { JsonlReplayer, rollup, vault } from './index.js';
3
3
  import path from 'path';
4
4
 
5
5
  if (process.argv[2] === 'gui') {
@@ -22,6 +22,8 @@ if (process.argv[2] === 'gui') {
22
22
  process.stdin.resume();
23
23
  } else {
24
24
 
25
+ { const r = vault(); if (r.copied > 0) process.stderr.write(`# vault: ${r.copied} copied → ~/.claude/history-backup\n`); }
26
+
25
27
  const FLAGS = {
26
28
  string: ['since', 'until', 'before', 'after', 'grep', 'igrep', 'cwd', 'project', 'role', 'type', 'tool', 'session', 'sid', 'parent', 'rollup', 'format', 'sort'],
27
29
  multi: ['grep', 'igrep', 'role', 'type', 'tool', 'session', 'sid', 'project', 'cwd'],
package/src/index.cjs CHANGED
@@ -228,6 +228,37 @@ function replay(projectsDir, opts) {
228
228
  return new JsonlReplayer(projectsDir);
229
229
  }
230
230
 
231
+ function vault({ projectsDir = DEFAULT_DIR, destDir = path.join(os.homedir(), '.claude', 'history-backup') } = {}) {
232
+ if (!fs.existsSync(projectsDir)) return { copied: 0, skipped: 0 };
233
+ let copied = 0, skipped = 0;
234
+ const walk = (src, depth) => {
235
+ if (depth > 5) return;
236
+ let entries;
237
+ try { entries = fs.readdirSync(src, { withFileTypes: true }); } catch { return; }
238
+ for (const d of entries) {
239
+ const srcPath = path.join(src, d.name);
240
+ const rel = path.relative(projectsDir, srcPath);
241
+ const dstPath = path.join(destDir, rel);
242
+ if (d.isDirectory()) { walk(srcPath, depth + 1); continue; }
243
+ if (!d.name.endsWith('.jsonl')) continue;
244
+ let srcStat;
245
+ try { srcStat = fs.statSync(srcPath); } catch { continue; }
246
+ try {
247
+ const dstStat = fs.statSync(dstPath);
248
+ if (dstStat.size === srcStat.size && dstStat.mtimeMs >= srcStat.mtimeMs) { skipped++; continue; }
249
+ } catch {}
250
+ try {
251
+ fs.mkdirSync(path.dirname(dstPath), { recursive: true });
252
+ fs.copyFileSync(srcPath, dstPath);
253
+ fs.utimesSync(dstPath, srcStat.atime, srcStat.mtime);
254
+ copied++;
255
+ } catch {}
256
+ }
257
+ };
258
+ walk(projectsDir, 0);
259
+ return { copied, skipped };
260
+ }
261
+
231
262
  async function rollup({ projectsDir, since = 0, out, format = 'ndjson' } = {}) {
232
263
  if (!out) throw new Error('rollup: out path required');
233
264
  const r = new JsonlReplayer(projectsDir);
@@ -299,4 +330,4 @@ async function rollupSqlite(r, { since, out }) {
299
330
  return { ...stats, rows, format: 'sqlite', out };
300
331
  }
301
332
 
302
- module.exports = { JsonlWatcher, JsonlReplayer, watch, replay, rollup };
333
+ module.exports = { JsonlWatcher, JsonlReplayer, watch, replay, rollup, vault };
package/src/index.js CHANGED
@@ -234,6 +234,37 @@ export function replay(projectsDir, opts) {
234
234
  return new JsonlReplayer(projectsDir);
235
235
  }
236
236
 
237
+ export function vault({ projectsDir = DEFAULT_DIR, destDir = path.join(os.homedir(), '.claude', 'history-backup') } = {}) {
238
+ if (!fs.existsSync(projectsDir)) return { copied: 0, skipped: 0 };
239
+ let copied = 0, skipped = 0;
240
+ const walk = (src, depth) => {
241
+ if (depth > 5) return;
242
+ let entries;
243
+ try { entries = fs.readdirSync(src, { withFileTypes: true }); } catch { return; }
244
+ for (const d of entries) {
245
+ const srcPath = path.join(src, d.name);
246
+ const rel = path.relative(projectsDir, srcPath);
247
+ const dstPath = path.join(destDir, rel);
248
+ if (d.isDirectory()) { walk(srcPath, depth + 1); continue; }
249
+ if (!d.name.endsWith('.jsonl')) continue;
250
+ let srcStat;
251
+ try { srcStat = fs.statSync(srcPath); } catch { continue; }
252
+ try {
253
+ const dstStat = fs.statSync(dstPath);
254
+ if (dstStat.size === srcStat.size && dstStat.mtimeMs >= srcStat.mtimeMs) { skipped++; continue; }
255
+ } catch {}
256
+ try {
257
+ fs.mkdirSync(path.dirname(dstPath), { recursive: true });
258
+ fs.copyFileSync(srcPath, dstPath);
259
+ fs.utimesSync(dstPath, srcStat.atime, srcStat.mtime);
260
+ copied++;
261
+ } catch {}
262
+ }
263
+ };
264
+ walk(projectsDir, 0);
265
+ return { copied, skipped };
266
+ }
267
+
237
268
  export async function rollup({ projectsDir, since = 0, out, format = 'ndjson' } = {}) {
238
269
  if (!out) throw new Error('rollup: out path required');
239
270
  const r = new JsonlReplayer(projectsDir);