@claude-transcripts/cli 0.3.0 → 0.3.1

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 (2) hide show
  1. package/dist/cli.js +67 -8
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -39113,6 +39113,10 @@ async function errorDetail(res) {
39113
39113
  return "";
39114
39114
  }
39115
39115
  }
39116
+ async function exists(path) {
39117
+ const res = await fetch(`${base2()}${path}`);
39118
+ return res.ok;
39119
+ }
39116
39120
  async function getOrNull(path) {
39117
39121
  const res = await fetch(`${base2()}${path}`);
39118
39122
  if (!res.ok)
@@ -39327,6 +39331,9 @@ class DryRunSink {
39327
39331
  async existingSession() {
39328
39332
  return null;
39329
39333
  }
39334
+ async hasTranscriptBlob() {
39335
+ return true;
39336
+ }
39330
39337
  async hasTurns() {
39331
39338
  return false;
39332
39339
  }
@@ -39360,6 +39367,9 @@ class WebapiSink {
39360
39367
  const res = await getOrNull(`/api/sessions/${encodeURIComponent(sessionId)}/turns?limit=1`);
39361
39368
  return (res?.turns?.length ?? 0) > 0;
39362
39369
  }
39370
+ async hasTranscriptBlob(sessionId) {
39371
+ return exists(`/api/s3/sessions/${encodeURIComponent(sessionId)}/transcript.jsonl`);
39372
+ }
39363
39373
  async resetSession(sessionId) {
39364
39374
  const res = await resetSession(sessionId);
39365
39375
  return res.deleted;
@@ -39394,8 +39404,9 @@ function planReingest(existing, opts) {
39394
39404
  if (opts.repair) {
39395
39405
  if (existing.status === "running")
39396
39406
  return { action: "skip", reason: "running" };
39397
- if (opts.hasTurns)
39398
- return { action: "skip", reason: "has-turns" };
39407
+ if (opts.hasTurns) {
39408
+ return opts.hasBlob === false ? { action: "repair-blob" } : { action: "skip", reason: "has-turns" };
39409
+ }
39399
39410
  return { action: "repair" };
39400
39411
  }
39401
39412
  if (!opts.force)
@@ -39463,7 +39474,8 @@ async function runBackfill(argv) {
39463
39474
  }
39464
39475
  const existing = dryRun ? null : await sink.existingSession(t.sessionId);
39465
39476
  const hasTurns = repair && existing ? await sink.hasTurns(t.sessionId) : false;
39466
- const plan = planReingest(existing, { force, replaceLive, repair, hasTurns });
39477
+ const hasBlob = hasTurns ? await sink.hasTranscriptBlob(t.sessionId) : undefined;
39478
+ const plan = planReingest(existing, { force, replaceLive, repair, hasTurns, hasBlob });
39467
39479
  if (plan.action === "skip") {
39468
39480
  if (plan.reason === "live-record") {
39469
39481
  console.warn(` ! ${t.sessionId}: skipped \u2014 recorded live. --force would delete that record and ` + "rebuild it from the transcript, losing provenance only the hook had. Pass " + "--replace-live to do that deliberately.");
@@ -39473,11 +39485,18 @@ async function runBackfill(argv) {
39473
39485
  console.log(` \xB7 ${t.sessionId}: skipped \u2014 still running`);
39474
39486
  }
39475
39487
  if (plan.reason === "has-turns") {
39476
- console.log(` \xB7 ${t.sessionId}: skipped \u2014 already has turn content`);
39488
+ console.log(` \xB7 ${t.sessionId}: skipped \u2014 already has turn content and a transcript`);
39477
39489
  }
39478
39490
  skipped++;
39479
39491
  continue;
39480
39492
  }
39493
+ if (plan.action === "repair-blob") {
39494
+ const jsonl = await readTranscript(t.path);
39495
+ await sink.putTranscript(t.sessionId, new TextEncoder().encode(jsonl));
39496
+ console.log(` + ${t.sessionId}: re-uploaded the transcript (chunks left alone)`);
39497
+ repaired++;
39498
+ continue;
39499
+ }
39481
39500
  if (plan.action === "repair") {
39482
39501
  const jsonl = await readTranscript(t.path);
39483
39502
  const facts = deriveSessionFacts(jsonl, { hostname: host, sessionIdHint: t.sessionId });
@@ -40293,6 +40312,9 @@ function makeMirrorCouch(target, onWrite) {
40293
40312
  },
40294
40313
  async putDoc(_db, id, doc, timeoutMs) {
40295
40314
  await send({ ...doc, _id: id }, timeoutMs ?? fallback);
40315
+ },
40316
+ async upsertDoc(_db, id, doc, timeoutMs) {
40317
+ await send({ ...doc, _id: id }, timeoutMs ?? fallback);
40296
40318
  }
40297
40319
  };
40298
40320
  }
@@ -40328,6 +40350,9 @@ function fanOutCouch(clients) {
40328
40350
  },
40329
40351
  async putDoc(db, id, doc, timeoutMs) {
40330
40352
  await Promise.allSettled(clients.map((c) => c.putDoc(db, id, doc, timeoutMs)));
40353
+ },
40354
+ async upsertDoc(db, id, doc, timeoutMs) {
40355
+ await Promise.allSettled(clients.map((c) => c.upsertDoc(db, id, doc, timeoutMs)));
40331
40356
  }
40332
40357
  };
40333
40358
  }
@@ -40403,6 +40428,39 @@ function makeDirectCouch(config, onWrite) {
40403
40428
  } catch {
40404
40429
  onWrite?.(false);
40405
40430
  }
40431
+ },
40432
+ async upsertDoc(db, id, doc, timeoutMs = 5000) {
40433
+ const url = `${root}/${db}/${encodeURIComponent(id)}`;
40434
+ const put = (body) => fetch(url, {
40435
+ method: "PUT",
40436
+ headers,
40437
+ body: JSON.stringify(body),
40438
+ signal: AbortSignal.timeout(timeoutMs)
40439
+ });
40440
+ try {
40441
+ const res = await put(doc);
40442
+ if (res.status !== 409) {
40443
+ onWrite?.(res.ok);
40444
+ return;
40445
+ }
40446
+ const current = await fetch(url, {
40447
+ headers,
40448
+ signal: AbortSignal.timeout(timeoutMs)
40449
+ });
40450
+ if (!current.ok) {
40451
+ onWrite?.(false);
40452
+ return;
40453
+ }
40454
+ const { _rev } = await current.json();
40455
+ if (!_rev) {
40456
+ onWrite?.(false);
40457
+ return;
40458
+ }
40459
+ const retry = await put({ ...doc, _rev });
40460
+ onWrite?.(retry.ok);
40461
+ } catch {
40462
+ onWrite?.(false);
40463
+ }
40406
40464
  }
40407
40465
  };
40408
40466
  }
@@ -40913,11 +40971,10 @@ var writeSummary = async (ctx) => {
40913
40971
  system_checks: {},
40914
40972
  source: "live"
40915
40973
  };
40916
- await ctx.couch.putDoc(ctx.sessionsDb, `summary:${ctx.sessionId}`, doc, 30000);
40974
+ await ctx.couch.upsertDoc(ctx.sessionsDb, `summary:${ctx.sessionId}`, doc, 30000);
40917
40975
  if (ctx.blob.enabled && ctx.sessionsBucket) {
40918
40976
  await ctx.blob.put(ctx.sessionsBucket, `${ctx.sessionId}/summary.json`, JSON.stringify(doc), "application/json");
40919
40977
  }
40920
- ctx.counts.clear();
40921
40978
  ctx.targets.clear();
40922
40979
  };
40923
40980
  var uploadBlobs = async (ctx) => {
@@ -42779,7 +42836,7 @@ function tools(counts) {
42779
42836
  function summaryLine(s) {
42780
42837
  return row([
42781
42838
  [s.sessionId.slice(0, 8), 8],
42782
- [when(s.timestamp), 16],
42839
+ [when(s.startTimestamp ?? s.timestamp), 16],
42783
42840
  [s.status, 10],
42784
42841
  [project2(s.cwd), 18],
42785
42842
  [num3(s.promptCount), 7],
@@ -42829,7 +42886,9 @@ async function showDetail(id, limit, json) {
42829
42886
  return 0;
42830
42887
  }
42831
42888
  console.log(`session ${s.sessionId} [${s.status}]`);
42832
- console.log(` started ${s.timestamp ?? "\u2014"}`);
42889
+ console.log(` started ${s.startTimestamp ?? s.timestamp ?? "\u2014"}`);
42890
+ if (s.status === "ended")
42891
+ console.log(` ended ${s.timestamp ?? "\u2014"}`);
42833
42892
  console.log(` project ${s.cwd || "\u2014"}`);
42834
42893
  console.log(` model ${s.model ?? "\u2014"}`);
42835
42894
  console.log(` hostname ${s.hostname || "\u2014"}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-transcripts/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "description": "Claude Transcripts CLI — browse, search, back up, and administer your self-hosted Claude Code session history.",
6
6
  "keywords": [