@claude-transcripts/cli 0.3.1 → 0.3.2

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 +170 -139
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -29928,14 +29928,17 @@ var CLI_SPEC = {
29928
29928
  },
29929
29929
  {
29930
29930
  name: "--repair",
29931
- description: "add missing chunk docs to adopted sessions; leaves summary + events alone"
29931
+ description: "add what an interrupted write left out of an adopted session: missing chunk docs, or a transcript that never reached S3; leaves summary + events alone"
29932
29932
  },
29933
29933
  {
29934
29934
  name: "--session",
29935
29935
  type: "string",
29936
29936
  description: "with --force: re-process only this session"
29937
29937
  },
29938
- { name: "--dry-run", description: "preview without writing" }
29938
+ {
29939
+ name: "--dry-run",
29940
+ description: "preview without writing (reads the store, so skips are shown)"
29941
+ }
29939
29942
  ],
29940
29943
  examples: ["backfill --dry-run", "backfill", "backfill --force --session 3f9a2c1e"]
29941
29944
  },
@@ -38772,6 +38775,128 @@ var import_react28 = __toESM(require_react(), 1);
38772
38775
  // packages/cli/src/commands/backfill.ts
38773
38776
  import { hostname } from "os";
38774
38777
 
38778
+ // packages/cli/src/api/http.ts
38779
+ import { readFileSync as readFileSync2 } from "fs";
38780
+
38781
+ // packages/cli/src/lib/paths.ts
38782
+ import { homedir } from "os";
38783
+ import { join as join2 } from "path";
38784
+ function xdg(envVar, fallback) {
38785
+ const v = process.env[envVar];
38786
+ return v?.trim() ? v : fallback;
38787
+ }
38788
+ function installPaths() {
38789
+ const home = homedir();
38790
+ const ctHome = process.env.CT_HOME;
38791
+ const configDir = ctHome ? join2(ctHome, "config") : join2(xdg("XDG_CONFIG_HOME", join2(home, ".config")), "claude-transcripts");
38792
+ const dataDir = ctHome ? join2(ctHome, "data") : join2(xdg("XDG_DATA_HOME", join2(home, ".local", "share")), "claude-transcripts");
38793
+ const binDir = ctHome ? join2(ctHome, "bin") : join2(home, ".local", "bin");
38794
+ const claudeSettings = ctHome ? join2(ctHome, "claude", "settings.json") : join2(home, ".claude", "settings.json");
38795
+ return {
38796
+ configDir,
38797
+ dataDir,
38798
+ binDir,
38799
+ claudeSettings,
38800
+ hookConfig: process.env.CT_HOOK_CONFIG ?? join2(configDir, "config.json"),
38801
+ instanceEnv: join2(configDir, "instance.env"),
38802
+ appConfig: join2(configDir, "app.json"),
38803
+ deployDir: join2(dataDir, "deploy"),
38804
+ versionFile: join2(dataDir, "version")
38805
+ };
38806
+ }
38807
+
38808
+ // packages/cli/src/api/http.ts
38809
+ var DEFAULT_WEBAPI_PORT = "7650";
38810
+ var resolved = null;
38811
+ function base2() {
38812
+ resolved ??= resolveWebapiUrl();
38813
+ return resolved;
38814
+ }
38815
+ function resolveWebapiUrl() {
38816
+ if (process.env.CT_WEBAPI_URL)
38817
+ return process.env.CT_WEBAPI_URL.replace(/\/$/, "");
38818
+ const host = process.env.WEBAPI_HOST || "127.0.0.1";
38819
+ if (process.env.WEBAPI_PORT)
38820
+ return `http://${host}:${process.env.WEBAPI_PORT}`;
38821
+ const fromInstance = instanceWebapiUrl();
38822
+ if (fromInstance)
38823
+ return fromInstance;
38824
+ return `http://${host}:${DEFAULT_WEBAPI_PORT}`;
38825
+ }
38826
+ function instanceWebapiUrl() {
38827
+ try {
38828
+ const raw = readFileSync2(installPaths().instanceEnv, "utf8");
38829
+ const port = /^WEBAPI_PORT=(\d+)\s*$/m.exec(raw)?.[1];
38830
+ if (!port)
38831
+ return null;
38832
+ const host = /^WEBAPI_HOST=(\S+)\s*$/m.exec(raw)?.[1] ?? "127.0.0.1";
38833
+ return `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}`;
38834
+ } catch {
38835
+ return null;
38836
+ }
38837
+ }
38838
+ function setWebapiUrl(url) {
38839
+ resolved = url.replace(/\/$/, "");
38840
+ }
38841
+ function webapiUrl() {
38842
+ return base2();
38843
+ }
38844
+ async function customFetch(url, init) {
38845
+ const res = await fetch(`${base2()}${url}`, init);
38846
+ if (!res.ok) {
38847
+ throw new Error(`${init?.method ?? "GET"} ${url} \u2192 ${res.status} ${res.statusText}${await errorDetail(res)}`);
38848
+ }
38849
+ const text = await res.text();
38850
+ return text ? JSON.parse(text) : undefined;
38851
+ }
38852
+ async function errorDetail(res) {
38853
+ try {
38854
+ const text = (await res.text()).trim();
38855
+ if (!text)
38856
+ return "";
38857
+ try {
38858
+ const body = JSON.parse(text);
38859
+ const msg = body.error ?? body.reason;
38860
+ if (typeof msg === "string" && msg)
38861
+ return `: ${msg}`;
38862
+ } catch {}
38863
+ return `: ${text.slice(0, 300)}`;
38864
+ } catch {
38865
+ return "";
38866
+ }
38867
+ }
38868
+ async function exists(path) {
38869
+ const res = await fetch(`${base2()}${path}`);
38870
+ return res.ok;
38871
+ }
38872
+ async function getOrNull(path) {
38873
+ const res = await fetch(`${base2()}${path}`);
38874
+ if (!res.ok)
38875
+ return null;
38876
+ return await res.json();
38877
+ }
38878
+ async function putStream(path, filePath, contentType) {
38879
+ const res = await fetch(`${base2()}${path}`, {
38880
+ method: "PUT",
38881
+ headers: { "content-type": contentType },
38882
+ body: Bun.file(filePath).stream(),
38883
+ duplex: "half"
38884
+ });
38885
+ if (!res.ok) {
38886
+ throw new Error(`PUT ${path} \u2192 ${res.status} ${res.statusText}${await errorDetail(res)}`);
38887
+ }
38888
+ }
38889
+ async function putRaw(path, body, contentType) {
38890
+ const res = await fetch(`${base2()}${path}`, {
38891
+ method: "PUT",
38892
+ headers: { "content-type": contentType },
38893
+ body
38894
+ });
38895
+ if (!res.ok) {
38896
+ throw new Error(`PUT ${path} \u2192 ${res.status} ${res.statusText}${await errorDetail(res)}`);
38897
+ }
38898
+ }
38899
+
38775
38900
  // packages/cli/src/lib/args.ts
38776
38901
  function parseFlags(argv) {
38777
38902
  const positionals = [];
@@ -38807,10 +38932,10 @@ function strOpt(opts, key) {
38807
38932
 
38808
38933
  // packages/cli/src/lib/claude-fs.ts
38809
38934
  import { readdir, readFile } from "fs/promises";
38810
- import { homedir } from "os";
38811
- import { join as join2 } from "path";
38935
+ import { homedir as homedir2 } from "os";
38936
+ import { join as join3 } from "path";
38812
38937
  function defaultProjectsDir() {
38813
- return join2(homedir(), ".claude", "projects");
38938
+ return join3(homedir2(), ".claude", "projects");
38814
38939
  }
38815
38940
  async function discoverTranscripts(root) {
38816
38941
  const out = [];
@@ -38818,12 +38943,12 @@ async function discoverTranscripts(root) {
38818
38943
  for (const proj of projects) {
38819
38944
  if (!proj.isDirectory())
38820
38945
  continue;
38821
- const dir = join2(root, proj.name);
38946
+ const dir = join3(root, proj.name);
38822
38947
  const files = await readdir(dir).catch(() => []);
38823
38948
  for (const f of files) {
38824
38949
  if (!f.endsWith(".jsonl"))
38825
38950
  continue;
38826
- out.push({ path: join2(dir, f), project: proj.name, sessionId: f.replace(/\.jsonl$/, "") });
38951
+ out.push({ path: join3(dir, f), project: proj.name, sessionId: f.replace(/\.jsonl$/, "") });
38827
38952
  }
38828
38953
  }
38829
38954
  return out.sort((a, b) => a.sessionId.localeCompare(b.sessionId));
@@ -39023,128 +39148,6 @@ function safeStringify(v) {
39023
39148
  }
39024
39149
  }
39025
39150
 
39026
- // packages/cli/src/api/http.ts
39027
- import { readFileSync as readFileSync2 } from "fs";
39028
-
39029
- // packages/cli/src/lib/paths.ts
39030
- import { homedir as homedir2 } from "os";
39031
- import { join as join3 } from "path";
39032
- function xdg(envVar, fallback) {
39033
- const v = process.env[envVar];
39034
- return v?.trim() ? v : fallback;
39035
- }
39036
- function installPaths() {
39037
- const home = homedir2();
39038
- const ctHome = process.env.CT_HOME;
39039
- const configDir = ctHome ? join3(ctHome, "config") : join3(xdg("XDG_CONFIG_HOME", join3(home, ".config")), "claude-transcripts");
39040
- const dataDir = ctHome ? join3(ctHome, "data") : join3(xdg("XDG_DATA_HOME", join3(home, ".local", "share")), "claude-transcripts");
39041
- const binDir = ctHome ? join3(ctHome, "bin") : join3(home, ".local", "bin");
39042
- const claudeSettings = ctHome ? join3(ctHome, "claude", "settings.json") : join3(home, ".claude", "settings.json");
39043
- return {
39044
- configDir,
39045
- dataDir,
39046
- binDir,
39047
- claudeSettings,
39048
- hookConfig: process.env.CT_HOOK_CONFIG ?? join3(configDir, "config.json"),
39049
- instanceEnv: join3(configDir, "instance.env"),
39050
- appConfig: join3(configDir, "app.json"),
39051
- deployDir: join3(dataDir, "deploy"),
39052
- versionFile: join3(dataDir, "version")
39053
- };
39054
- }
39055
-
39056
- // packages/cli/src/api/http.ts
39057
- var DEFAULT_WEBAPI_PORT = "7650";
39058
- var resolved = null;
39059
- function base2() {
39060
- resolved ??= resolveWebapiUrl();
39061
- return resolved;
39062
- }
39063
- function resolveWebapiUrl() {
39064
- if (process.env.CT_WEBAPI_URL)
39065
- return process.env.CT_WEBAPI_URL.replace(/\/$/, "");
39066
- const host = process.env.WEBAPI_HOST || "127.0.0.1";
39067
- if (process.env.WEBAPI_PORT)
39068
- return `http://${host}:${process.env.WEBAPI_PORT}`;
39069
- const fromInstance = instanceWebapiUrl();
39070
- if (fromInstance)
39071
- return fromInstance;
39072
- return `http://${host}:${DEFAULT_WEBAPI_PORT}`;
39073
- }
39074
- function instanceWebapiUrl() {
39075
- try {
39076
- const raw = readFileSync2(installPaths().instanceEnv, "utf8");
39077
- const port = /^WEBAPI_PORT=(\d+)\s*$/m.exec(raw)?.[1];
39078
- if (!port)
39079
- return null;
39080
- const host = /^WEBAPI_HOST=(\S+)\s*$/m.exec(raw)?.[1] ?? "127.0.0.1";
39081
- return `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}`;
39082
- } catch {
39083
- return null;
39084
- }
39085
- }
39086
- function setWebapiUrl(url) {
39087
- resolved = url.replace(/\/$/, "");
39088
- }
39089
- function webapiUrl() {
39090
- return base2();
39091
- }
39092
- async function customFetch(url, init) {
39093
- const res = await fetch(`${base2()}${url}`, init);
39094
- if (!res.ok) {
39095
- throw new Error(`${init?.method ?? "GET"} ${url} \u2192 ${res.status} ${res.statusText}${await errorDetail(res)}`);
39096
- }
39097
- const text = await res.text();
39098
- return text ? JSON.parse(text) : undefined;
39099
- }
39100
- async function errorDetail(res) {
39101
- try {
39102
- const text = (await res.text()).trim();
39103
- if (!text)
39104
- return "";
39105
- try {
39106
- const body = JSON.parse(text);
39107
- const msg = body.error ?? body.reason;
39108
- if (typeof msg === "string" && msg)
39109
- return `: ${msg}`;
39110
- } catch {}
39111
- return `: ${text.slice(0, 300)}`;
39112
- } catch {
39113
- return "";
39114
- }
39115
- }
39116
- async function exists(path) {
39117
- const res = await fetch(`${base2()}${path}`);
39118
- return res.ok;
39119
- }
39120
- async function getOrNull(path) {
39121
- const res = await fetch(`${base2()}${path}`);
39122
- if (!res.ok)
39123
- return null;
39124
- return await res.json();
39125
- }
39126
- async function putStream(path, filePath, contentType) {
39127
- const res = await fetch(`${base2()}${path}`, {
39128
- method: "PUT",
39129
- headers: { "content-type": contentType },
39130
- body: Bun.file(filePath).stream(),
39131
- duplex: "half"
39132
- });
39133
- if (!res.ok) {
39134
- throw new Error(`PUT ${path} \u2192 ${res.status} ${res.statusText}${await errorDetail(res)}`);
39135
- }
39136
- }
39137
- async function putRaw(path, body, contentType) {
39138
- const res = await fetch(`${base2()}${path}`, {
39139
- method: "PUT",
39140
- headers: { "content-type": contentType },
39141
- body
39142
- });
39143
- if (!res.ok) {
39144
- throw new Error(`PUT ${path} \u2192 ${res.status} ${res.statusText}${await errorDetail(res)}`);
39145
- }
39146
- }
39147
-
39148
39151
  // packages/cli/src/api/generated.ts
39149
39152
  var getListSessionsUrl = (params) => {
39150
39153
  const normalizedParams = new URLSearchParams;
@@ -39328,14 +39331,41 @@ var migrateDown = async (migrateDownBody, options) => {
39328
39331
  // packages/cli/src/lib/sink.ts
39329
39332
  class DryRunSink {
39330
39333
  label = "dry-run";
39331
- async existingSession() {
39332
- return null;
39334
+ blind = false;
39335
+ reader;
39336
+ constructor(reader) {
39337
+ this.reader = reader;
39333
39338
  }
39334
- async hasTranscriptBlob() {
39335
- return true;
39339
+ reads() {
39340
+ if (this.reader === undefined) {
39341
+ try {
39342
+ this.reader = new WebapiSink;
39343
+ } catch {
39344
+ this.reader = null;
39345
+ this.blind = true;
39346
+ }
39347
+ }
39348
+ return this.reader;
39336
39349
  }
39337
- async hasTurns() {
39338
- return false;
39350
+ async ask(run, whenBlind) {
39351
+ const r = this.reads();
39352
+ if (!r)
39353
+ return whenBlind;
39354
+ try {
39355
+ return await run(r);
39356
+ } catch {
39357
+ this.blind = true;
39358
+ return whenBlind;
39359
+ }
39360
+ }
39361
+ async existingSession(sessionId) {
39362
+ return this.ask((r) => r.existingSession(sessionId), null);
39363
+ }
39364
+ async hasTranscriptBlob(sessionId) {
39365
+ return this.ask((r) => r.hasTranscriptBlob(sessionId), true);
39366
+ }
39367
+ async hasTurns(sessionId) {
39368
+ return this.ask((r) => r.hasTurns(sessionId), false);
39339
39369
  }
39340
39370
  async resetSession(sessionId) {
39341
39371
  console.log(` [dry-run] DELETE ${sessionId} (summary + event + chunk docs)`);
@@ -39390,11 +39420,9 @@ class WebapiSink {
39390
39420
  }
39391
39421
  }
39392
39422
  function makeSink(opts) {
39393
- if (opts.dryRun)
39394
- return new DryRunSink;
39395
39423
  if (opts.webapiUrl)
39396
39424
  setWebapiUrl(opts.webapiUrl);
39397
- return new WebapiSink;
39425
+ return opts.dryRun ? new DryRunSink : new WebapiSink;
39398
39426
  }
39399
39427
 
39400
39428
  // packages/cli/src/commands/backfill.ts
@@ -39472,7 +39500,7 @@ async function runBackfill(argv) {
39472
39500
  skipped++;
39473
39501
  continue;
39474
39502
  }
39475
- const existing = dryRun ? null : await sink.existingSession(t.sessionId);
39503
+ const existing = await sink.existingSession(t.sessionId);
39476
39504
  const hasTurns = repair && existing ? await sink.hasTurns(t.sessionId) : false;
39477
39505
  const hasBlob = hasTurns ? await sink.hasTranscriptBlob(t.sessionId) : undefined;
39478
39506
  const plan = planReingest(existing, { force, replaceLive, repair, hasTurns, hasBlob });
@@ -39526,6 +39554,9 @@ async function runBackfill(argv) {
39526
39554
  console.error(` ! ${t.sessionId}: ${err.message}`);
39527
39555
  }
39528
39556
  }
39557
+ if (sink instanceof DryRunSink && sink.blind) {
39558
+ console.warn(`backfill: WARNING \u2014 could not read ${webapiUrl()}, so this preview assumed nothing ` + "is stored yet. A real run will skip whatever is already there; re-run the " + "preview once the webapi is reachable to see the true plan.");
39559
+ }
39529
39560
  console.log(`backfill: ${written} backfilled${repaired ? `, ${repaired} repaired` : ""}` + `${reprocessed ? ` (${reprocessed} re-processed)` : ""}, ` + `${skipped} skipped${refusedLive ? ` (${refusedLive} live, not replaced)` : ""}, ` + `${failed} failed`);
39530
39561
  console.log("backfill: NOTE \u2014 summary + per-event markers + chunk docs reconstructed" + `${sidechains ? `; subagent sub-transcripts (${sidechains} session(s) have them) still TODO` : ""}` + " (see docs/operate/tools.md).");
39531
39562
  if (reprocessed && !dryRun) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-transcripts/cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
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": [