@claude-transcripts/cli 0.4.0 → 0.5.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.
@@ -7,7 +7,7 @@ import {
7
7
  cliArgLabel,
8
8
  CLI_SPEC,
9
9
  COMMANDS
10
- } from "./cli-fptk6bgx.js";
10
+ } from "./cli-byvjee1b.js";
11
11
  import {
12
12
  __toESM,
13
13
  __commonJS,
@@ -3885,13 +3885,189 @@ async function runExport(argv) {
3885
3885
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
3886
3886
  import { dirname as dirname2 } from "path";
3887
3887
 
3888
+ // packages/cli/src/lib/statusline.ts
3889
+ import { statSync } from "fs";
3890
+ var STALL_AFTER_MS = 60000;
3891
+ var HOOK_SILENT_AFTER_MS = 5 * 60000;
3892
+ function transcriptMtimeMs(path) {
3893
+ if (typeof path !== "string" || !path)
3894
+ return null;
3895
+ try {
3896
+ return statSync(path).mtimeMs;
3897
+ } catch {
3898
+ return null;
3899
+ }
3900
+ }
3901
+ function hookSilent(lastActivityMs, transcriptMs) {
3902
+ if (!transcriptMs || lastActivityMs <= 0)
3903
+ return false;
3904
+ return transcriptMs - lastActivityMs > HOOK_SILENT_AFTER_MS;
3905
+ }
3906
+ function lastWrite(ms, now) {
3907
+ return ms > 0 ? `last write ${ago(ms, now)}` : "no write landed";
3908
+ }
3909
+ function ago(ms, now) {
3910
+ const s = Math.max(0, Math.round((now - ms) / 1000));
3911
+ if (s < 60)
3912
+ return `${s}s ago`;
3913
+ const m = Math.round(s / 60);
3914
+ if (m < 60)
3915
+ return `${m}m ago`;
3916
+ return `${Math.round(m / 60)}h ago`;
3917
+ }
3918
+ function versionLabel(version = VERSION) {
3919
+ return `ct@${version === DEV_VERSION ? "dev" : version}`;
3920
+ }
3921
+ function whereLabel(t) {
3922
+ const host = t.couchUrl.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
3923
+ return `${t.sessionsDb}@${host}`;
3924
+ }
3925
+ function storeState(s, now) {
3926
+ if (s.lastFailureMs > s.lastWriteMs && now - s.lastWriteMs > STALL_AFTER_MS)
3927
+ return "failing";
3928
+ return s.lastWriteMs > 0 ? "healthy" : "idle";
3929
+ }
3930
+ var GLYPH = { rec: "\u25CF", stalled: "\u25D0", ready: "\u25CC", off: "\u25CB" };
3931
+ function sessionLink(webapiUrl, sessionId) {
3932
+ return webapiUrl ? `${webapiUrl.replace(/\/$/, "")}/app/sessions/${sessionId}` : null;
3933
+ }
3934
+ function renderStatusline(state, now = Date.now(), opts = {}) {
3935
+ return formatStatusline(statuslineLine(state, now), opts);
3936
+ }
3937
+ function statuslineLine(state, now = Date.now()) {
3938
+ const version = versionLabel(state.version);
3939
+ if (!state.configured)
3940
+ return { state: "off", version, note: "no instance configured" };
3941
+ const t = state.targets;
3942
+ if (!t)
3943
+ return { state: "off", version, note: "not recording this session" };
3944
+ const c = state.counts;
3945
+ const counted = c ? { events: c.events, tools: Object.values(c.tools).reduce((a, b) => a + b, 0) } : { events: 0 };
3946
+ const line = (state, rest) => ({
3947
+ state,
3948
+ version,
3949
+ ...counted,
3950
+ ...rest
3951
+ });
3952
+ const stores = t.stores?.length ? t.stores : null;
3953
+ if (!stores) {
3954
+ const where = whereLabel(t);
3955
+ const age = lastWrite(t.lastWriteMs, now);
3956
+ if (hookSilent(Math.max(t.lastWriteMs, t.lastFailureMs), state.transcriptMtimeMs)) {
3957
+ return line("stalled", { note: "hook silent", age, where });
3958
+ }
3959
+ if (t.lastFailureMs > t.lastWriteMs && now - t.lastWriteMs > STALL_AFTER_MS) {
3960
+ return line("stalled", { age, where });
3961
+ }
3962
+ if (t.lastWriteMs <= 0)
3963
+ return line("ready", { age: "no write yet", where });
3964
+ return line("rec", { age: ago(t.lastWriteMs, now), where });
3965
+ }
3966
+ const direct = stores.find((s) => s.kind === "direct") ?? stores[0];
3967
+ const headline = direct ?? { label: whereLabel(t), lastWriteMs: 0, lastFailureMs: 0 };
3968
+ const lastActivity = Math.max(...stores.flatMap((s) => [s.lastWriteMs, s.lastFailureMs]));
3969
+ if (hookSilent(lastActivity, state.transcriptMtimeMs)) {
3970
+ const newest = stores.reduce((a, b) => b.lastWriteMs > a.lastWriteMs ? b : a, headline);
3971
+ return line("stalled", {
3972
+ note: "hook silent",
3973
+ age: lastWrite(newest.lastWriteMs, now),
3974
+ where: newest.label
3975
+ });
3976
+ }
3977
+ if (direct && storeState(direct, now) === "healthy") {
3978
+ return line("rec", { age: ago(direct.lastWriteMs, now), where: direct.label });
3979
+ }
3980
+ const healthyMirrors = stores.filter((s) => s !== direct && storeState(s, now) === "healthy");
3981
+ const first = healthyMirrors[0];
3982
+ if (first) {
3983
+ return line("rec", {
3984
+ qualifier: "(mirror)",
3985
+ age: ago(first.lastWriteMs, now),
3986
+ where: first.label,
3987
+ ...healthyMirrors.length > 1 ? { more: `+${healthyMirrors.length - 1}` } : {}
3988
+ });
3989
+ }
3990
+ if (stores.some((s) => storeState(s, now) === "failing")) {
3991
+ const best = Math.max(...stores.map((s) => s.lastWriteMs));
3992
+ return line("stalled", { age: lastWrite(best, now), where: headline.label });
3993
+ }
3994
+ return line("ready", { age: "no write yet", where: headline.label });
3995
+ }
3996
+ var SGR = {
3997
+ reset: "\x1B[0m",
3998
+ dim: "\x1B[2m",
3999
+ green: "\x1B[32m",
4000
+ yellow: "\x1B[33m",
4001
+ cyan: "\x1B[36m"
4002
+ };
4003
+ var STATE_COLOR = {
4004
+ rec: SGR.green,
4005
+ stalled: SGR.yellow,
4006
+ ready: SGR.cyan,
4007
+ off: SGR.dim
4008
+ };
4009
+ var SHRINK = [
4010
+ ({ tools: _, ...l }) => l,
4011
+ (l) => l.where ? { ...l, where: shortWhere(l.where) } : l,
4012
+ ({ events: _, ...l }) => l,
4013
+ ({ age: _, ...l }) => l,
4014
+ ({ where: _, more: __, ...l }) => l
4015
+ ];
4016
+ function shortWhere(where) {
4017
+ const at = where.indexOf("@");
4018
+ if (at > 0)
4019
+ return where.slice(0, at);
4020
+ const host = where.startsWith("[") ? where.slice(0, where.indexOf("]") + 1) : where.split(":")[0];
4021
+ if (!host || host.startsWith("[") || /^\d+(\.\d+){3}$/.test(host))
4022
+ return host || where;
4023
+ return host.split(".")[0] || host;
4024
+ }
4025
+ function formatStatusline(line, opts = {}) {
4026
+ let l = line;
4027
+ if (opts.columns && opts.columns > 0) {
4028
+ for (const shrink of SHRINK) {
4029
+ if ([...draw(l, {})].length <= opts.columns)
4030
+ break;
4031
+ l = shrink(l);
4032
+ }
4033
+ }
4034
+ return draw(l, opts);
4035
+ }
4036
+ function draw(l, opts) {
4037
+ const paint = (code, s) => opts.color ? `${code}${s}${SGR.reset}` : s;
4038
+ const dim = (s) => paint(SGR.dim, s);
4039
+ const color = STATE_COLOR[l.state];
4040
+ const head = [
4041
+ paint(color, `${GLYPH[l.state]}`),
4042
+ l.version,
4043
+ paint(color, l.state),
4044
+ ...l.qualifier ? [paint(SGR.yellow, l.qualifier)] : []
4045
+ ].join(" ");
4046
+ const counts = l.events === undefined ? undefined : l.tools === undefined ? `${l.events} ev` : `${l.events} ev \xB7 ${l.tools} tools`;
4047
+ const details = [
4048
+ l.note && (l.note === "hook silent" ? paint(SGR.yellow, l.note) : l.note),
4049
+ counts && dim(counts),
4050
+ l.age && dim(l.age)
4051
+ ].filter(Boolean);
4052
+ const sep = dim(" \xB7 ");
4053
+ let out = [head, ...details].join(sep);
4054
+ if (l.where) {
4055
+ const label = opts.link ? osc8(opts.link, l.where) : l.where;
4056
+ out += `${dim(" \u2192")} ${label}${l.more ? ` ${l.more}` : ""}`;
4057
+ }
4058
+ return out;
4059
+ }
4060
+ function osc8(url, text) {
4061
+ return `\x1B]8;;${url}\x1B\\${text}\x1B]8;;\x1B\\`;
4062
+ }
4063
+
3888
4064
  // packages/cli/src/hook/runtime.ts
3889
4065
  import {
3890
4066
  closeSync,
3891
4067
  openSync,
3892
4068
  readFileSync as readFileSync4,
3893
4069
  readSync,
3894
- statSync,
4070
+ statSync as statSync2,
3895
4071
  unlinkSync,
3896
4072
  writeFileSync as writeFileSync2
3897
4073
  } from "fs";
@@ -4278,7 +4454,7 @@ function makeChunkState(sessionId) {
4278
4454
  return true;
4279
4455
  } catch {
4280
4456
  try {
4281
- if (Date.now() - statSync(lockFile).mtimeMs > STALE_LOCK_MS) {
4457
+ if (Date.now() - statSync2(lockFile).mtimeMs > STALE_LOCK_MS) {
4282
4458
  unlinkSync(lockFile);
4283
4459
  closeSync(openSync(lockFile, "wx"));
4284
4460
  return true;
@@ -4293,7 +4469,7 @@ function makeChunkState(sessionId) {
4293
4469
  } catch {}
4294
4470
  },
4295
4471
  readTail(path, offset) {
4296
- const size = statSync(path).size;
4472
+ const size = statSync2(path).size;
4297
4473
  if (size <= offset)
4298
4474
  return "";
4299
4475
  const fd = openSync(path, "r");
@@ -4369,20 +4545,19 @@ function commonFields(ctx) {
4369
4545
  }
4370
4546
 
4371
4547
  // packages/cli/src/hook/announce.ts
4372
- function sessionLink(webapiUrl, sessionId) {
4373
- return webapiUrl ? `${webapiUrl.replace(/\/$/, "")}/app/sessions/${sessionId}` : null;
4374
- }
4375
4548
  function recordingBanner(targets, sessionId) {
4376
- const where = [`couchdb://${targets.couchUrl.replace(/^https?:\/\//, "")}/${targets.sessionsDb}`];
4549
+ const also = [];
4377
4550
  if (targets.bucket)
4378
- where.push(`s3://${targets.bucket}`);
4551
+ also.push(`s3://${targets.bucket}`);
4379
4552
  if (targets.mirrors.length) {
4380
- where.push(`mirrors: ${targets.mirrors.map(hostOf).join(", ")}`);
4553
+ also.push(`mirrors: ${targets.mirrors.map(hostOf).join(", ")}`);
4381
4554
  }
4555
+ const head = `\u25CF Claude Transcripts recording \u2192 ${whereLabel(targets)}${also.length ? ` (+ ${also.join(", ")})` : ""}`;
4382
4556
  const link = sessionLink(targets.webapiUrl, sessionId);
4383
- return `Claude Transcripts \u2014 recording to ${where.join(" + ")}${link ? ` \xB7 ${link}` : ""}`;
4557
+ return link ? `${head}
4558
+ ${link}` : head;
4384
4559
  }
4385
- var NOT_RECORDING_BANNER = "Claude Transcripts \u2014 not recording (no instance configured). Run `claude-transcripts install`.";
4560
+ var NOT_RECORDING_BANNER = "\u25CB Claude Transcripts not recording \u2014 no instance configured. Run `claude-transcripts install`.";
4386
4561
  function sessionStartEnvelope(out) {
4387
4562
  if (!out.systemMessage && !out.additionalContext)
4388
4563
  return null;
@@ -4406,7 +4581,7 @@ function emitSessionStart(out) {
4406
4581
  }
4407
4582
 
4408
4583
  // packages/cli/src/hook/handlers.ts
4409
- import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
4584
+ import { readFileSync as readFileSync5, statSync as statSync3 } from "fs";
4410
4585
 
4411
4586
  // packages/cli/src/hook/recall.ts
4412
4587
  var FACTS_TIMEOUT_MS = 2000;
@@ -4480,7 +4655,7 @@ var seedSessionStart = async (ctx) => {
4480
4655
  };
4481
4656
  function fileSize(path) {
4482
4657
  try {
4483
- return path ? statSync2(path).size : null;
4658
+ return path ? statSync3(path).size : null;
4484
4659
  } catch {
4485
4660
  return null;
4486
4661
  }
@@ -4989,7 +5164,7 @@ async function runHook(argv) {
4989
5164
  }
4990
5165
 
4991
5166
  // packages/cli/src/commands/import.ts
4992
- import { existsSync as existsSync4, statSync as statSync3 } from "fs";
5167
+ import { existsSync as existsSync4, statSync as statSync4 } from "fs";
4993
5168
  import { join as join7 } from "path";
4994
5169
  var BATCH = 500;
4995
5170
  function num(n) {
@@ -5208,7 +5383,7 @@ async function runImport(argv) {
5208
5383
  const path = join7(dir, rel);
5209
5384
  await putStream(`/api/ingest/${sessionId}/transcript`, path, "application/x-ndjson");
5210
5385
  blobs++;
5211
- blobBytes += statSync3(path).size;
5386
+ blobBytes += statSync4(path).size;
5212
5387
  }
5213
5388
  console.log(` blobs ${blobs} restored, ${formatBytes(blobBytes)}${skipped ? ` (${skipped} skipped)` : ""}`);
5214
5389
  } else {
@@ -5859,96 +6034,6 @@ async function removeStaleKeys(api, keep) {
5859
6034
  // packages/cli/src/commands/statusline.ts
5860
6035
  import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
5861
6036
  import { dirname as dirname6 } from "path";
5862
-
5863
- // packages/cli/src/lib/statusline.ts
5864
- import { statSync as statSync4 } from "fs";
5865
- var STALL_AFTER_MS = 60000;
5866
- var HOOK_SILENT_AFTER_MS = 5 * 60000;
5867
- function transcriptMtimeMs(path) {
5868
- if (typeof path !== "string" || !path)
5869
- return null;
5870
- try {
5871
- return statSync4(path).mtimeMs;
5872
- } catch {
5873
- return null;
5874
- }
5875
- }
5876
- function hookSilent(lastActivityMs, transcriptMs) {
5877
- if (!transcriptMs || lastActivityMs <= 0)
5878
- return false;
5879
- return transcriptMs - lastActivityMs > HOOK_SILENT_AFTER_MS;
5880
- }
5881
- function lastWrite(ms, now) {
5882
- return ms > 0 ? `last write ${ago(ms, now)}` : "no write landed";
5883
- }
5884
- function ago(ms, now) {
5885
- const s = Math.max(0, Math.round((now - ms) / 1000));
5886
- if (s < 60)
5887
- return `${s}s ago`;
5888
- const m = Math.round(s / 60);
5889
- if (m < 60)
5890
- return `${m}m ago`;
5891
- return `${Math.round(m / 60)}h ago`;
5892
- }
5893
- function versionLabel(version = VERSION) {
5894
- return `ct@${version === DEV_VERSION ? "dev" : version}`;
5895
- }
5896
- function whereLabel(t) {
5897
- const host = t.couchUrl.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
5898
- return `${t.sessionsDb}@${host}`;
5899
- }
5900
- function storeState(s, now) {
5901
- if (s.lastFailureMs > s.lastWriteMs && now - s.lastWriteMs > STALL_AFTER_MS)
5902
- return "failing";
5903
- return s.lastWriteMs > 0 ? "healthy" : "idle";
5904
- }
5905
- function renderStatusline(state, now = Date.now()) {
5906
- const ct = versionLabel(state.version);
5907
- if (!state.configured)
5908
- return `\u25CB ${ct} off \xB7 no instance configured`;
5909
- const t = state.targets;
5910
- if (!t)
5911
- return `\u25CB ${ct} off \xB7 not recording this session`;
5912
- const c = state.counts;
5913
- const toolCalls = c ? Object.values(c.tools).reduce((a, b) => a + b, 0) : 0;
5914
- const counts = c ? `${c.events} ev \xB7 ${toolCalls} tools` : "0 ev";
5915
- const stores = t.stores?.length ? t.stores : null;
5916
- if (!stores) {
5917
- const last = lastWrite(t.lastWriteMs, now);
5918
- if (hookSilent(Math.max(t.lastWriteMs, t.lastFailureMs), state.transcriptMtimeMs)) {
5919
- return `\u25D0 ${ct} stalled \xB7 hook silent \xB7 ${counts} \xB7 ${last} \u2192 ${whereLabel(t)}`;
5920
- }
5921
- if (t.lastFailureMs > t.lastWriteMs && now - t.lastWriteMs > STALL_AFTER_MS) {
5922
- return `\u25D0 ${ct} stalled \xB7 ${counts} \xB7 ${last} \u2192 ${whereLabel(t)}`;
5923
- }
5924
- if (t.lastWriteMs <= 0)
5925
- return `\u25CC ${ct} ready \xB7 ${counts} \xB7 no write yet \u2192 ${whereLabel(t)}`;
5926
- return `\u25CF ${ct} rec \xB7 ${counts} \xB7 ${ago(t.lastWriteMs, now)} \u2192 ${whereLabel(t)}`;
5927
- }
5928
- const direct = stores.find((s) => s.kind === "direct") ?? stores[0];
5929
- const headline = direct ?? { label: whereLabel(t), lastWriteMs: 0, lastFailureMs: 0 };
5930
- const lastActivity = Math.max(...stores.flatMap((s) => [s.lastWriteMs, s.lastFailureMs]));
5931
- if (hookSilent(lastActivity, state.transcriptMtimeMs)) {
5932
- const newest = stores.reduce((a, b) => b.lastWriteMs > a.lastWriteMs ? b : a, headline);
5933
- return `\u25D0 ${ct} stalled \xB7 hook silent \xB7 ${counts} \xB7 ${lastWrite(newest.lastWriteMs, now)} \u2192 ${newest.label}`;
5934
- }
5935
- if (direct && storeState(direct, now) === "healthy") {
5936
- return `\u25CF ${ct} rec \xB7 ${counts} \xB7 ${ago(direct.lastWriteMs, now)} \u2192 ${direct.label}`;
5937
- }
5938
- const healthyMirrors = stores.filter((s) => s !== direct && storeState(s, now) === "healthy");
5939
- const first = healthyMirrors[0];
5940
- if (first) {
5941
- const more = healthyMirrors.length > 1 ? ` +${healthyMirrors.length - 1}` : "";
5942
- return `\u25CF ${ct} rec (mirror) \xB7 ${counts} \xB7 ${ago(first.lastWriteMs, now)} \u2192 ${first.label}${more}`;
5943
- }
5944
- if (stores.some((s) => storeState(s, now) === "failing")) {
5945
- const best = Math.max(...stores.map((s) => s.lastWriteMs));
5946
- return `\u25D0 ${ct} stalled \xB7 ${counts} \xB7 ${lastWrite(best, now)} \u2192 ${headline.label}`;
5947
- }
5948
- return `\u25CC ${ct} ready \xB7 ${counts} \xB7 no write yet \u2192 ${headline.label}`;
5949
- }
5950
-
5951
- // packages/cli/src/commands/statusline.ts
5952
6037
  function readSettings2(path) {
5953
6038
  try {
5954
6039
  return JSON.parse(readFileSync11(path, "utf8"));
@@ -5977,6 +6062,10 @@ async function render() {
5977
6062
  targets,
5978
6063
  counts: id ? makeCounts(id).read() : null,
5979
6064
  transcriptMtimeMs: targets ? transcriptMtimeMs(input.transcript_path) : null
6065
+ }, Date.now(), {
6066
+ color: !process.env.NO_COLOR,
6067
+ link: targets && id ? sessionLink(targets.webapiUrl, id) : null,
6068
+ columns: Number(process.env.COLUMNS) || undefined
5980
6069
  });
5981
6070
  process.stdout.write(`${line}
5982
6071
  `);
package/dist/cli.js CHANGED
@@ -7,13 +7,13 @@ import {
7
7
  parseFlags,
8
8
  VERSION,
9
9
  COMMANDS
10
- } from "./cli-fptk6bgx.js";
10
+ } from "./cli-byvjee1b.js";
11
11
 
12
12
  // packages/cli/src/cli.tsx
13
13
  var [, , first, ...rest] = process.argv;
14
14
  var USAGE_ERROR = 2;
15
15
  async function showHelp(props, stream = process.stdout) {
16
- const { renderHelp } = await import("./app-a93wk4rv.js");
16
+ const { renderHelp } = await import("./app-2rqkxqp9.js");
17
17
  renderHelp(props, stream);
18
18
  }
19
19
  var argv = first === undefined ? [] : [first, ...rest];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-transcripts/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
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": [