@claude-transcripts/cli 0.3.0 → 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 +231 -141
- 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,124 +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 getOrNull(path) {
|
|
39117
|
-
const res = await fetch(`${base2()}${path}`);
|
|
39118
|
-
if (!res.ok)
|
|
39119
|
-
return null;
|
|
39120
|
-
return await res.json();
|
|
39121
|
-
}
|
|
39122
|
-
async function putStream(path, filePath, contentType) {
|
|
39123
|
-
const res = await fetch(`${base2()}${path}`, {
|
|
39124
|
-
method: "PUT",
|
|
39125
|
-
headers: { "content-type": contentType },
|
|
39126
|
-
body: Bun.file(filePath).stream(),
|
|
39127
|
-
duplex: "half"
|
|
39128
|
-
});
|
|
39129
|
-
if (!res.ok) {
|
|
39130
|
-
throw new Error(`PUT ${path} \u2192 ${res.status} ${res.statusText}${await errorDetail(res)}`);
|
|
39131
|
-
}
|
|
39132
|
-
}
|
|
39133
|
-
async function putRaw(path, body, contentType) {
|
|
39134
|
-
const res = await fetch(`${base2()}${path}`, {
|
|
39135
|
-
method: "PUT",
|
|
39136
|
-
headers: { "content-type": contentType },
|
|
39137
|
-
body
|
|
39138
|
-
});
|
|
39139
|
-
if (!res.ok) {
|
|
39140
|
-
throw new Error(`PUT ${path} \u2192 ${res.status} ${res.statusText}${await errorDetail(res)}`);
|
|
39141
|
-
}
|
|
39142
|
-
}
|
|
39143
|
-
|
|
39144
39151
|
// packages/cli/src/api/generated.ts
|
|
39145
39152
|
var getListSessionsUrl = (params) => {
|
|
39146
39153
|
const normalizedParams = new URLSearchParams;
|
|
@@ -39324,11 +39331,41 @@ var migrateDown = async (migrateDownBody, options) => {
|
|
|
39324
39331
|
// packages/cli/src/lib/sink.ts
|
|
39325
39332
|
class DryRunSink {
|
|
39326
39333
|
label = "dry-run";
|
|
39327
|
-
|
|
39328
|
-
|
|
39334
|
+
blind = false;
|
|
39335
|
+
reader;
|
|
39336
|
+
constructor(reader) {
|
|
39337
|
+
this.reader = reader;
|
|
39329
39338
|
}
|
|
39330
|
-
|
|
39331
|
-
|
|
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;
|
|
39349
|
+
}
|
|
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);
|
|
39332
39369
|
}
|
|
39333
39370
|
async resetSession(sessionId) {
|
|
39334
39371
|
console.log(` [dry-run] DELETE ${sessionId} (summary + event + chunk docs)`);
|
|
@@ -39360,6 +39397,9 @@ class WebapiSink {
|
|
|
39360
39397
|
const res = await getOrNull(`/api/sessions/${encodeURIComponent(sessionId)}/turns?limit=1`);
|
|
39361
39398
|
return (res?.turns?.length ?? 0) > 0;
|
|
39362
39399
|
}
|
|
39400
|
+
async hasTranscriptBlob(sessionId) {
|
|
39401
|
+
return exists(`/api/s3/sessions/${encodeURIComponent(sessionId)}/transcript.jsonl`);
|
|
39402
|
+
}
|
|
39363
39403
|
async resetSession(sessionId) {
|
|
39364
39404
|
const res = await resetSession(sessionId);
|
|
39365
39405
|
return res.deleted;
|
|
@@ -39380,11 +39420,9 @@ class WebapiSink {
|
|
|
39380
39420
|
}
|
|
39381
39421
|
}
|
|
39382
39422
|
function makeSink(opts) {
|
|
39383
|
-
if (opts.dryRun)
|
|
39384
|
-
return new DryRunSink;
|
|
39385
39423
|
if (opts.webapiUrl)
|
|
39386
39424
|
setWebapiUrl(opts.webapiUrl);
|
|
39387
|
-
return new WebapiSink;
|
|
39425
|
+
return opts.dryRun ? new DryRunSink : new WebapiSink;
|
|
39388
39426
|
}
|
|
39389
39427
|
|
|
39390
39428
|
// packages/cli/src/commands/backfill.ts
|
|
@@ -39394,8 +39432,9 @@ function planReingest(existing, opts) {
|
|
|
39394
39432
|
if (opts.repair) {
|
|
39395
39433
|
if (existing.status === "running")
|
|
39396
39434
|
return { action: "skip", reason: "running" };
|
|
39397
|
-
if (opts.hasTurns)
|
|
39398
|
-
return { action: "skip", reason: "has-turns" };
|
|
39435
|
+
if (opts.hasTurns) {
|
|
39436
|
+
return opts.hasBlob === false ? { action: "repair-blob" } : { action: "skip", reason: "has-turns" };
|
|
39437
|
+
}
|
|
39399
39438
|
return { action: "repair" };
|
|
39400
39439
|
}
|
|
39401
39440
|
if (!opts.force)
|
|
@@ -39461,9 +39500,10 @@ async function runBackfill(argv) {
|
|
|
39461
39500
|
skipped++;
|
|
39462
39501
|
continue;
|
|
39463
39502
|
}
|
|
39464
|
-
const existing =
|
|
39503
|
+
const existing = await sink.existingSession(t.sessionId);
|
|
39465
39504
|
const hasTurns = repair && existing ? await sink.hasTurns(t.sessionId) : false;
|
|
39466
|
-
const
|
|
39505
|
+
const hasBlob = hasTurns ? await sink.hasTranscriptBlob(t.sessionId) : undefined;
|
|
39506
|
+
const plan = planReingest(existing, { force, replaceLive, repair, hasTurns, hasBlob });
|
|
39467
39507
|
if (plan.action === "skip") {
|
|
39468
39508
|
if (plan.reason === "live-record") {
|
|
39469
39509
|
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 +39513,18 @@ async function runBackfill(argv) {
|
|
|
39473
39513
|
console.log(` \xB7 ${t.sessionId}: skipped \u2014 still running`);
|
|
39474
39514
|
}
|
|
39475
39515
|
if (plan.reason === "has-turns") {
|
|
39476
|
-
console.log(` \xB7 ${t.sessionId}: skipped \u2014 already has turn content`);
|
|
39516
|
+
console.log(` \xB7 ${t.sessionId}: skipped \u2014 already has turn content and a transcript`);
|
|
39477
39517
|
}
|
|
39478
39518
|
skipped++;
|
|
39479
39519
|
continue;
|
|
39480
39520
|
}
|
|
39521
|
+
if (plan.action === "repair-blob") {
|
|
39522
|
+
const jsonl = await readTranscript(t.path);
|
|
39523
|
+
await sink.putTranscript(t.sessionId, new TextEncoder().encode(jsonl));
|
|
39524
|
+
console.log(` + ${t.sessionId}: re-uploaded the transcript (chunks left alone)`);
|
|
39525
|
+
repaired++;
|
|
39526
|
+
continue;
|
|
39527
|
+
}
|
|
39481
39528
|
if (plan.action === "repair") {
|
|
39482
39529
|
const jsonl = await readTranscript(t.path);
|
|
39483
39530
|
const facts = deriveSessionFacts(jsonl, { hostname: host, sessionIdHint: t.sessionId });
|
|
@@ -39507,6 +39554,9 @@ async function runBackfill(argv) {
|
|
|
39507
39554
|
console.error(` ! ${t.sessionId}: ${err.message}`);
|
|
39508
39555
|
}
|
|
39509
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
|
+
}
|
|
39510
39560
|
console.log(`backfill: ${written} backfilled${repaired ? `, ${repaired} repaired` : ""}` + `${reprocessed ? ` (${reprocessed} re-processed)` : ""}, ` + `${skipped} skipped${refusedLive ? ` (${refusedLive} live, not replaced)` : ""}, ` + `${failed} failed`);
|
|
39511
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).");
|
|
39512
39562
|
if (reprocessed && !dryRun) {
|
|
@@ -40293,6 +40343,9 @@ function makeMirrorCouch(target, onWrite) {
|
|
|
40293
40343
|
},
|
|
40294
40344
|
async putDoc(_db, id, doc, timeoutMs) {
|
|
40295
40345
|
await send({ ...doc, _id: id }, timeoutMs ?? fallback);
|
|
40346
|
+
},
|
|
40347
|
+
async upsertDoc(_db, id, doc, timeoutMs) {
|
|
40348
|
+
await send({ ...doc, _id: id }, timeoutMs ?? fallback);
|
|
40296
40349
|
}
|
|
40297
40350
|
};
|
|
40298
40351
|
}
|
|
@@ -40328,6 +40381,9 @@ function fanOutCouch(clients) {
|
|
|
40328
40381
|
},
|
|
40329
40382
|
async putDoc(db, id, doc, timeoutMs) {
|
|
40330
40383
|
await Promise.allSettled(clients.map((c) => c.putDoc(db, id, doc, timeoutMs)));
|
|
40384
|
+
},
|
|
40385
|
+
async upsertDoc(db, id, doc, timeoutMs) {
|
|
40386
|
+
await Promise.allSettled(clients.map((c) => c.upsertDoc(db, id, doc, timeoutMs)));
|
|
40331
40387
|
}
|
|
40332
40388
|
};
|
|
40333
40389
|
}
|
|
@@ -40403,6 +40459,39 @@ function makeDirectCouch(config, onWrite) {
|
|
|
40403
40459
|
} catch {
|
|
40404
40460
|
onWrite?.(false);
|
|
40405
40461
|
}
|
|
40462
|
+
},
|
|
40463
|
+
async upsertDoc(db, id, doc, timeoutMs = 5000) {
|
|
40464
|
+
const url = `${root}/${db}/${encodeURIComponent(id)}`;
|
|
40465
|
+
const put = (body) => fetch(url, {
|
|
40466
|
+
method: "PUT",
|
|
40467
|
+
headers,
|
|
40468
|
+
body: JSON.stringify(body),
|
|
40469
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
40470
|
+
});
|
|
40471
|
+
try {
|
|
40472
|
+
const res = await put(doc);
|
|
40473
|
+
if (res.status !== 409) {
|
|
40474
|
+
onWrite?.(res.ok);
|
|
40475
|
+
return;
|
|
40476
|
+
}
|
|
40477
|
+
const current = await fetch(url, {
|
|
40478
|
+
headers,
|
|
40479
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
40480
|
+
});
|
|
40481
|
+
if (!current.ok) {
|
|
40482
|
+
onWrite?.(false);
|
|
40483
|
+
return;
|
|
40484
|
+
}
|
|
40485
|
+
const { _rev } = await current.json();
|
|
40486
|
+
if (!_rev) {
|
|
40487
|
+
onWrite?.(false);
|
|
40488
|
+
return;
|
|
40489
|
+
}
|
|
40490
|
+
const retry = await put({ ...doc, _rev });
|
|
40491
|
+
onWrite?.(retry.ok);
|
|
40492
|
+
} catch {
|
|
40493
|
+
onWrite?.(false);
|
|
40494
|
+
}
|
|
40406
40495
|
}
|
|
40407
40496
|
};
|
|
40408
40497
|
}
|
|
@@ -40913,11 +41002,10 @@ var writeSummary = async (ctx) => {
|
|
|
40913
41002
|
system_checks: {},
|
|
40914
41003
|
source: "live"
|
|
40915
41004
|
};
|
|
40916
|
-
await ctx.couch.
|
|
41005
|
+
await ctx.couch.upsertDoc(ctx.sessionsDb, `summary:${ctx.sessionId}`, doc, 30000);
|
|
40917
41006
|
if (ctx.blob.enabled && ctx.sessionsBucket) {
|
|
40918
41007
|
await ctx.blob.put(ctx.sessionsBucket, `${ctx.sessionId}/summary.json`, JSON.stringify(doc), "application/json");
|
|
40919
41008
|
}
|
|
40920
|
-
ctx.counts.clear();
|
|
40921
41009
|
ctx.targets.clear();
|
|
40922
41010
|
};
|
|
40923
41011
|
var uploadBlobs = async (ctx) => {
|
|
@@ -42779,7 +42867,7 @@ function tools(counts) {
|
|
|
42779
42867
|
function summaryLine(s) {
|
|
42780
42868
|
return row([
|
|
42781
42869
|
[s.sessionId.slice(0, 8), 8],
|
|
42782
|
-
[when(s.timestamp), 16],
|
|
42870
|
+
[when(s.startTimestamp ?? s.timestamp), 16],
|
|
42783
42871
|
[s.status, 10],
|
|
42784
42872
|
[project2(s.cwd), 18],
|
|
42785
42873
|
[num3(s.promptCount), 7],
|
|
@@ -42829,7 +42917,9 @@ async function showDetail(id, limit, json) {
|
|
|
42829
42917
|
return 0;
|
|
42830
42918
|
}
|
|
42831
42919
|
console.log(`session ${s.sessionId} [${s.status}]`);
|
|
42832
|
-
console.log(` started ${s.timestamp ?? "\u2014"}`);
|
|
42920
|
+
console.log(` started ${s.startTimestamp ?? s.timestamp ?? "\u2014"}`);
|
|
42921
|
+
if (s.status === "ended")
|
|
42922
|
+
console.log(` ended ${s.timestamp ?? "\u2014"}`);
|
|
42833
42923
|
console.log(` project ${s.cwd || "\u2014"}`);
|
|
42834
42924
|
console.log(` model ${s.model ?? "\u2014"}`);
|
|
42835
42925
|
console.log(` hostname ${s.hostname || "\u2014"}`);
|
package/package.json
CHANGED