@claude-transcripts/cli 0.2.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.
- package/dist/cli.js +81 -18
- package/package.json +2 -2
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
|
|
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.
|
|
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) => {
|
|
@@ -42121,6 +42178,9 @@ function ago(ms, now) {
|
|
|
42121
42178
|
return `${m}m ago`;
|
|
42122
42179
|
return `${Math.round(m / 60)}h ago`;
|
|
42123
42180
|
}
|
|
42181
|
+
function versionLabel(version = VERSION) {
|
|
42182
|
+
return `ct@${version === DEV_VERSION ? "dev" : version}`;
|
|
42183
|
+
}
|
|
42124
42184
|
function whereLabel(t) {
|
|
42125
42185
|
const host = t.couchUrl.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
42126
42186
|
return `${t.sessionsDb}@${host}`;
|
|
@@ -42131,11 +42191,12 @@ function storeState(s, now) {
|
|
|
42131
42191
|
return s.lastWriteMs > 0 ? "healthy" : "idle";
|
|
42132
42192
|
}
|
|
42133
42193
|
function renderStatusline(state, now = Date.now()) {
|
|
42194
|
+
const ct = versionLabel(state.version);
|
|
42134
42195
|
if (!state.configured)
|
|
42135
|
-
return
|
|
42196
|
+
return `\u25CB ${ct} off \xB7 no instance configured`;
|
|
42136
42197
|
const t = state.targets;
|
|
42137
42198
|
if (!t)
|
|
42138
|
-
return
|
|
42199
|
+
return `\u25CB ${ct} off \xB7 not recording this session`;
|
|
42139
42200
|
const c = state.counts;
|
|
42140
42201
|
const toolCalls = c ? Object.values(c.tools).reduce((a, b) => a + b, 0) : 0;
|
|
42141
42202
|
const counts = c ? `${c.events} ev \xB7 ${toolCalls} tools` : "0 ev";
|
|
@@ -42145,29 +42206,29 @@ function renderStatusline(state, now = Date.now()) {
|
|
|
42145
42206
|
const failing = t.lastFailureMs > t.lastWriteMs && now - t.lastWriteMs > STALL_AFTER_MS;
|
|
42146
42207
|
if (failing) {
|
|
42147
42208
|
const last = written ? `last write ${ago(t.lastWriteMs, now)}` : "no write landed";
|
|
42148
|
-
return `\u25D0 ct stalled \xB7 ${counts} \xB7 ${last} \u2192 ${whereLabel(t)}`;
|
|
42209
|
+
return `\u25D0 ${ct} stalled \xB7 ${counts} \xB7 ${last} \u2192 ${whereLabel(t)}`;
|
|
42149
42210
|
}
|
|
42150
42211
|
if (!written)
|
|
42151
|
-
return `\u25CC ct ready \xB7 ${counts} \xB7 no write yet \u2192 ${whereLabel(t)}`;
|
|
42152
|
-
return `\u25CF ct rec \xB7 ${counts} \xB7 ${ago(t.lastWriteMs, now)} \u2192 ${whereLabel(t)}`;
|
|
42212
|
+
return `\u25CC ${ct} ready \xB7 ${counts} \xB7 no write yet \u2192 ${whereLabel(t)}`;
|
|
42213
|
+
return `\u25CF ${ct} rec \xB7 ${counts} \xB7 ${ago(t.lastWriteMs, now)} \u2192 ${whereLabel(t)}`;
|
|
42153
42214
|
}
|
|
42154
42215
|
const direct = stores.find((s) => s.kind === "direct") ?? stores[0];
|
|
42155
42216
|
const headline = direct ?? { label: whereLabel(t), lastWriteMs: 0, lastFailureMs: 0 };
|
|
42156
42217
|
if (direct && storeState(direct, now) === "healthy") {
|
|
42157
|
-
return `\u25CF ct rec \xB7 ${counts} \xB7 ${ago(direct.lastWriteMs, now)} \u2192 ${direct.label}`;
|
|
42218
|
+
return `\u25CF ${ct} rec \xB7 ${counts} \xB7 ${ago(direct.lastWriteMs, now)} \u2192 ${direct.label}`;
|
|
42158
42219
|
}
|
|
42159
42220
|
const healthyMirrors = stores.filter((s) => s !== direct && storeState(s, now) === "healthy");
|
|
42160
42221
|
const first = healthyMirrors[0];
|
|
42161
42222
|
if (first) {
|
|
42162
42223
|
const more = healthyMirrors.length > 1 ? ` +${healthyMirrors.length - 1}` : "";
|
|
42163
|
-
return `\u25CF ct rec (mirror) \xB7 ${counts} \xB7 ${ago(first.lastWriteMs, now)} \u2192 ${first.label}${more}`;
|
|
42224
|
+
return `\u25CF ${ct} rec (mirror) \xB7 ${counts} \xB7 ${ago(first.lastWriteMs, now)} \u2192 ${first.label}${more}`;
|
|
42164
42225
|
}
|
|
42165
42226
|
if (stores.some((s) => storeState(s, now) === "failing")) {
|
|
42166
42227
|
const best = Math.max(...stores.map((s) => s.lastWriteMs));
|
|
42167
42228
|
const last = best > 0 ? `last write ${ago(best, now)}` : "no write landed";
|
|
42168
|
-
return `\u25D0 ct stalled \xB7 ${counts} \xB7 ${last} \u2192 ${headline.label}`;
|
|
42229
|
+
return `\u25D0 ${ct} stalled \xB7 ${counts} \xB7 ${last} \u2192 ${headline.label}`;
|
|
42169
42230
|
}
|
|
42170
|
-
return `\u25CC ct ready \xB7 ${counts} \xB7 no write yet \u2192 ${headline.label}`;
|
|
42231
|
+
return `\u25CC ${ct} ready \xB7 ${counts} \xB7 no write yet \u2192 ${headline.label}`;
|
|
42171
42232
|
}
|
|
42172
42233
|
|
|
42173
42234
|
// packages/cli/src/commands/statusline.ts
|
|
@@ -42201,7 +42262,7 @@ async function render2() {
|
|
|
42201
42262
|
process.stdout.write(`${line}
|
|
42202
42263
|
`);
|
|
42203
42264
|
} catch {
|
|
42204
|
-
process.stdout.write(`\u25CB
|
|
42265
|
+
process.stdout.write(`\u25CB ${versionLabel()} off
|
|
42205
42266
|
`);
|
|
42206
42267
|
}
|
|
42207
42268
|
return 0;
|
|
@@ -42775,7 +42836,7 @@ function tools(counts) {
|
|
|
42775
42836
|
function summaryLine(s) {
|
|
42776
42837
|
return row([
|
|
42777
42838
|
[s.sessionId.slice(0, 8), 8],
|
|
42778
|
-
[when(s.timestamp), 16],
|
|
42839
|
+
[when(s.startTimestamp ?? s.timestamp), 16],
|
|
42779
42840
|
[s.status, 10],
|
|
42780
42841
|
[project2(s.cwd), 18],
|
|
42781
42842
|
[num3(s.promptCount), 7],
|
|
@@ -42825,7 +42886,9 @@ async function showDetail(id, limit, json) {
|
|
|
42825
42886
|
return 0;
|
|
42826
42887
|
}
|
|
42827
42888
|
console.log(`session ${s.sessionId} [${s.status}]`);
|
|
42828
|
-
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"}`);
|
|
42829
42892
|
console.log(` project ${s.cwd || "\u2014"}`);
|
|
42830
42893
|
console.log(` model ${s.model ?? "\u2014"}`);
|
|
42831
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
|
+
"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": [
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"LICENSE"
|
|
35
35
|
],
|
|
36
36
|
"engines": {
|
|
37
|
-
"bun": ">=1.
|
|
37
|
+
"bun": ">=1.4.0"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|