@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.
- package/dist/cli.js +170 -139
- 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
|
|
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
|
-
{
|
|
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
|
|
38935
|
+
import { homedir as homedir2 } from "os";
|
|
38936
|
+
import { join as join3 } from "path";
|
|
38812
38937
|
function defaultProjectsDir() {
|
|
38813
|
-
return
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
39332
|
-
|
|
39334
|
+
blind = false;
|
|
39335
|
+
reader;
|
|
39336
|
+
constructor(reader) {
|
|
39337
|
+
this.reader = reader;
|
|
39333
39338
|
}
|
|
39334
|
-
|
|
39335
|
-
|
|
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
|
|
39338
|
-
|
|
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 =
|
|
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