@usebitl/cli 0.1.13 → 0.1.15
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/adapters/bitl-cc-watch.mjs +27 -0
- package/adapters/bitl-hook.mjs +17 -0
- package/adapters/dist/core.mjs +149 -18
- package/dist/chunks/{chunk-VVNOFPMO.mjs → chunk-65TI66EM.mjs} +8 -4
- package/dist/chunks/{src-MWG3MA6Y.mjs → src-TJPPU3TC.mjs} +44 -21
- package/dist/cli.mjs +4 -4
- package/package.json +1 -1
- package/web/assets/index-DKJ1-yaM.js +9 -0
- package/web/assets/index-pbEhErgU.css +2 -0
- package/web/index.html +2 -2
- package/web/og/og-en.png +0 -0
- package/web/og/og-ko.png +0 -0
- package/web/sw.js +1 -1
- package/web/assets/index-B7qa9R6O.js +0 -9
- package/web/assets/index-BRI9Dc0z.css +0 -2
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Claude Code interrupt watcher. bitl-hook.mjs spawns it separately on each UserPromptSubmit (Claude Code doesn't wait).
|
|
3
|
+
//
|
|
4
|
+
// node bitl-cc-watch.mjs <session id> <transcript path> <cwd>
|
|
5
|
+
//
|
|
6
|
+
// Claude Code sends no hook when the user interrupts a turn, so the card would stay "running". Follow the transcript
|
|
7
|
+
// until the turn ends; if it was interrupted, send bitl:Interrupted (ends the turn on the board). Only the event goes
|
|
8
|
+
// out — no transcript content. Exits quietly on a normal end, after 2 hours, or on any error. Always exit 0.
|
|
9
|
+
|
|
10
|
+
import { sendRecord } from './send.mjs';
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
const [sessionId, transcriptPath, cwd] = process.argv.slice(2);
|
|
14
|
+
if (!sessionId || !transcriptPath) return;
|
|
15
|
+
const { watchClaudeTurn } = await import('./dist/core.mjs');
|
|
16
|
+
const mark = await watchClaudeTurn(transcriptPath);
|
|
17
|
+
if (mark?.mark !== 'interrupted') return;
|
|
18
|
+
await sendRecord({
|
|
19
|
+
source: 'claude-code',
|
|
20
|
+
receivedAt: mark.at ?? new Date().toISOString(),
|
|
21
|
+
payload: { hook_event_name: 'bitl:Interrupted', session_id: sessionId, transcript_path: transcriptPath, ...(cwd ? { cwd } : {}) },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
main()
|
|
26
|
+
.catch(() => {})
|
|
27
|
+
.finally(() => process.exit(0));
|
package/adapters/bitl-hook.mjs
CHANGED
|
@@ -56,6 +56,22 @@ function watchAgyPermission(payload) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Claude Code sends no hook when the user interrupts a turn → on each prompt, spawn a watcher that reads the transcript
|
|
61
|
+
* until the turn ends and reports an interrupt (bitl-cc-watch.mjs). Only ids and the path are passed, never the prompt
|
|
62
|
+
*/
|
|
63
|
+
function watchClaudeTurn(payload) {
|
|
64
|
+
try {
|
|
65
|
+
const { session_id: sessionId, transcript_path: transcriptPath, cwd } = payload;
|
|
66
|
+
if (typeof sessionId !== 'string' || typeof transcriptPath !== 'string') return;
|
|
67
|
+
const script = join(import.meta.dirname, 'bitl-cc-watch.mjs');
|
|
68
|
+
const args = [script, sessionId, transcriptPath, typeof cwd === 'string' ? cwd : ''];
|
|
69
|
+
spawn(process.execPath, args, { detached: true, stdio: 'ignore', windowsHide: true }).unref();
|
|
70
|
+
} catch {
|
|
71
|
+
// the hook carries on even if the watcher can't start
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
/**
|
|
60
76
|
* Claude Code agent view (`claude agents`): the daemon keeps pre-warmed sessions parked as spares (CLAUDE_BG_SOURCE=spare).
|
|
61
77
|
* A parked spare fires SessionStart but gets no prompt until a task is dispatched to it, and is retired after an hour idle
|
|
@@ -84,6 +100,7 @@ async function main() {
|
|
|
84
100
|
const event = argValue('--event');
|
|
85
101
|
if (event && payload && typeof payload === 'object' && payload.hook_event_name === undefined) payload.hook_event_name = event;
|
|
86
102
|
if (source === 'antigravity' && event === 'PreToolUse') watchAgyPermission(payload);
|
|
103
|
+
if (source === 'claude-code' && payload?.hook_event_name === 'UserPromptSubmit') watchClaudeTurn(payload);
|
|
87
104
|
if (isParkedSpareStart(source, payload)) return;
|
|
88
105
|
await sendRecord({ source, receivedAt, payload });
|
|
89
106
|
}
|
package/adapters/dist/core.mjs
CHANGED
|
@@ -153,6 +153,8 @@ function normalizeHook(source, raw, receivedAt) {
|
|
|
153
153
|
}
|
|
154
154
|
case "Stop":
|
|
155
155
|
case "Interrupt":
|
|
156
|
+
// Codex: the in-progress turn was interrupted
|
|
157
|
+
case "bitl:Interrupted":
|
|
156
158
|
type = "turn_end";
|
|
157
159
|
text = str(raw.last_assistant_message);
|
|
158
160
|
break;
|
|
@@ -326,7 +328,8 @@ function activityOf(e) {
|
|
|
326
328
|
const base = e.tool.summary ? `${e.tool.name}: ${e.tool.summary}` : e.tool.name;
|
|
327
329
|
return e.agentId && e.agentType ? `[${e.agentType}] ${base}` : base;
|
|
328
330
|
}
|
|
329
|
-
function splitEvent(e,
|
|
331
|
+
function splitEvent(e, enrichment = {}) {
|
|
332
|
+
const { resumedFrom, ...extra } = enrichment;
|
|
330
333
|
const newCwd = e.nativeType === "CwdChanged" && typeof e.raw.new_cwd === "string" ? e.raw.new_cwd : void 0;
|
|
331
334
|
const signal = {
|
|
332
335
|
source: e.source,
|
|
@@ -338,7 +341,8 @@ function splitEvent(e, extra = {}) {
|
|
|
338
341
|
agentId: e.agentId,
|
|
339
342
|
agentNamed: e.agentId || e.agentType !== void 0 ? Boolean(e.agentType) : void 0,
|
|
340
343
|
waitingReason: e.waitingReason,
|
|
341
|
-
toolFailed: e.tool?.failed || void 0
|
|
344
|
+
toolFailed: e.tool?.failed || void 0,
|
|
345
|
+
resumedFrom
|
|
342
346
|
};
|
|
343
347
|
const content = {
|
|
344
348
|
title: e.type === "prompt" && e.text ? oneLine(e.text, TITLE_LIMIT) : void 0,
|
|
@@ -467,7 +471,7 @@ function readCloudConfig(home = bitlHome()) {
|
|
|
467
471
|
}
|
|
468
472
|
}
|
|
469
473
|
function clientVersionHeader() {
|
|
470
|
-
const v = "0.1.
|
|
474
|
+
const v = "0.1.15";
|
|
471
475
|
return v ? { "x-bitl-client": v } : {};
|
|
472
476
|
}
|
|
473
477
|
function resolveTarget(home = bitlHome()) {
|
|
@@ -490,11 +494,73 @@ function resolveTarget(home = bitlHome()) {
|
|
|
490
494
|
|
|
491
495
|
// ../../shared/src/node/enrich.ts
|
|
492
496
|
import { execFile } from "node:child_process";
|
|
493
|
-
import { readFile as
|
|
497
|
+
import { readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
494
498
|
import { homedir as homedir2 } from "node:os";
|
|
495
|
-
import { basename, join as
|
|
496
|
-
|
|
499
|
+
import { basename as basename2, join as join4 } from "node:path";
|
|
500
|
+
|
|
501
|
+
// ../../shared/src/node/resume.ts
|
|
502
|
+
import { readdir, readFile as readFile2, stat as stat2, writeFile } from "node:fs/promises";
|
|
503
|
+
import { basename, dirname as dirname3, join as join3 } from "node:path";
|
|
497
504
|
var MAX_TRANSCRIPT_BYTES2 = 20 * 1024 * 1024;
|
|
505
|
+
var MAX_CANDIDATES = 200;
|
|
506
|
+
var CACHE_FILE = "resume-links.json";
|
|
507
|
+
var CACHE_LIMIT = 200;
|
|
508
|
+
var UUID = /"uuid":"([0-9a-f-]{36})"/g;
|
|
509
|
+
var messageIds = (text) => [...text.matchAll(UUID)].map((m) => m[1]);
|
|
510
|
+
async function findResumedFrom(transcriptPath) {
|
|
511
|
+
const own = basename(transcriptPath, ".jsonl");
|
|
512
|
+
if ((await stat2(transcriptPath)).size > MAX_TRANSCRIPT_BYTES2) return null;
|
|
513
|
+
const ids = messageIds(await readFile2(transcriptPath, "utf8"));
|
|
514
|
+
if (ids.length === 0) return void 0;
|
|
515
|
+
const dir = dirname3(transcriptPath);
|
|
516
|
+
const candidates = [];
|
|
517
|
+
const names = (await readdir(dir)).filter((n) => n.endsWith(".jsonl") && basename(n, ".jsonl") !== own);
|
|
518
|
+
for (const name of names.slice(0, MAX_CANDIDATES)) {
|
|
519
|
+
try {
|
|
520
|
+
const file = join3(dir, name);
|
|
521
|
+
const info2 = await stat2(file);
|
|
522
|
+
if (info2.size > MAX_TRANSCRIPT_BYTES2) continue;
|
|
523
|
+
const list = messageIds(await readFile2(file, "utf8"));
|
|
524
|
+
if (list.length) candidates.push({ id: basename(name, ".jsonl"), ids: new Set(list), last: list.at(-1), mtime: info2.mtimeMs });
|
|
525
|
+
} catch {
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const ourLast = ids.at(-1);
|
|
529
|
+
const earlier = candidates.filter((c) => !(c.ids.has(ourLast) && c.last !== ourLast));
|
|
530
|
+
for (let i = ids.length - 1; i >= 0; i--) {
|
|
531
|
+
const id = ids[i];
|
|
532
|
+
const holders = earlier.filter((c) => c.ids.has(id));
|
|
533
|
+
if (!holders.length) continue;
|
|
534
|
+
const ended = holders.find((c) => c.last === id);
|
|
535
|
+
return (ended ?? holders.sort((a, b) => b.mtime - a.mtime)[0]).id;
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
async function readCache(home) {
|
|
540
|
+
try {
|
|
541
|
+
return JSON.parse(await readFile2(join3(home, CACHE_FILE), "utf8"));
|
|
542
|
+
} catch {
|
|
543
|
+
return {};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async function resumedFromFor(e, home) {
|
|
547
|
+
if (e.source !== "claude-code" || !e.transcriptPath) return void 0;
|
|
548
|
+
const cache = await readCache(home);
|
|
549
|
+
const hit = cache[e.sessionId];
|
|
550
|
+
if (hit && !hit.pending) return hit.from;
|
|
551
|
+
const resumed = e.nativeType === "SessionStart" && e.raw.source === "resume";
|
|
552
|
+
if (!resumed && !(hit?.pending && e.type === "prompt")) return void 0;
|
|
553
|
+
const found = await findResumedFrom(e.transcriptPath).catch(() => null);
|
|
554
|
+
cache[e.sessionId] = found === void 0 ? { pending: true, at: e.at } : { ...found ? { from: found } : {}, at: e.at };
|
|
555
|
+
const kept = Object.entries(cache).sort((a, b) => b[1].at.localeCompare(a[1].at)).slice(0, CACHE_LIMIT);
|
|
556
|
+
await writeFile(join3(home, CACHE_FILE), JSON.stringify(Object.fromEntries(kept))).catch(() => {
|
|
557
|
+
});
|
|
558
|
+
return found ?? void 0;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ../../shared/src/node/enrich.ts
|
|
562
|
+
var BRANCH_TTL_MS = 6e4;
|
|
563
|
+
var MAX_TRANSCRIPT_BYTES3 = 20 * 1024 * 1024;
|
|
498
564
|
var branchCache = /* @__PURE__ */ new Map();
|
|
499
565
|
function gitBranch(cwd) {
|
|
500
566
|
const hit = branchCache.get(cwd);
|
|
@@ -509,8 +575,8 @@ function gitBranch(cwd) {
|
|
|
509
575
|
}
|
|
510
576
|
async function titleFromTranscript(path) {
|
|
511
577
|
try {
|
|
512
|
-
if ((await
|
|
513
|
-
const text = await
|
|
578
|
+
if ((await stat3(path)).size > MAX_TRANSCRIPT_BYTES3) return void 0;
|
|
579
|
+
const text = await readFile3(path, "utf8");
|
|
514
580
|
let aiTitle;
|
|
515
581
|
let customTitle;
|
|
516
582
|
for (const line of text.split("\n")) {
|
|
@@ -527,12 +593,12 @@ async function titleFromTranscript(path) {
|
|
|
527
593
|
return void 0;
|
|
528
594
|
}
|
|
529
595
|
}
|
|
530
|
-
async function codexThreadName(threadId, codexHome = process.env.CODEX_HOME?.trim() ||
|
|
596
|
+
async function codexThreadName(threadId, codexHome = process.env.CODEX_HOME?.trim() || join4(homedir2(), ".codex")) {
|
|
531
597
|
try {
|
|
532
|
-
const file =
|
|
533
|
-
if ((await
|
|
598
|
+
const file = join4(codexHome, "session_index.jsonl");
|
|
599
|
+
if ((await stat3(file)).size > MAX_TRANSCRIPT_BYTES3) return void 0;
|
|
534
600
|
let name;
|
|
535
|
-
for (const line of (await
|
|
601
|
+
for (const line of (await readFile3(file, "utf8")).split("\n")) {
|
|
536
602
|
if (!line.includes(threadId)) continue;
|
|
537
603
|
try {
|
|
538
604
|
const rec = JSON.parse(line);
|
|
@@ -557,9 +623,9 @@ async function importSqliteQuietly() {
|
|
|
557
623
|
process.emitWarning = emitWarning;
|
|
558
624
|
}
|
|
559
625
|
}
|
|
560
|
-
async function antigravityTitle(conversationId, dbPath =
|
|
626
|
+
async function antigravityTitle(conversationId, dbPath = join4(homedir2(), ".gemini", "antigravity-cli", "conversation_summaries.db")) {
|
|
561
627
|
try {
|
|
562
|
-
await
|
|
628
|
+
await stat3(dbPath);
|
|
563
629
|
const { DatabaseSync } = await importSqliteQuietly();
|
|
564
630
|
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
565
631
|
try {
|
|
@@ -586,9 +652,9 @@ function gitRoot(cwd) {
|
|
|
586
652
|
var rootCache = /* @__PURE__ */ new Map();
|
|
587
653
|
async function projectName(cwd) {
|
|
588
654
|
const root = await gitRoot(cwd);
|
|
589
|
-
return
|
|
655
|
+
return basename2((root ?? cwd).replace(/[\\/]+$/, ""));
|
|
590
656
|
}
|
|
591
|
-
async function enrichEvent(e) {
|
|
657
|
+
async function enrichEvent(e, home = bitlHome()) {
|
|
592
658
|
const extra = {};
|
|
593
659
|
const newCwd = e.nativeType === "CwdChanged" && typeof e.raw.new_cwd === "string" ? e.raw.new_cwd : void 0;
|
|
594
660
|
const cwd = newCwd ?? e.cwd;
|
|
@@ -610,6 +676,11 @@ async function enrichEvent(e) {
|
|
|
610
676
|
})
|
|
611
677
|
);
|
|
612
678
|
}
|
|
679
|
+
tasks.push(
|
|
680
|
+
resumedFromFor(e, home).then((from) => {
|
|
681
|
+
if (from) extra.resumedFrom = from;
|
|
682
|
+
})
|
|
683
|
+
);
|
|
613
684
|
await Promise.all(tasks.map((t) => t.catch(() => {
|
|
614
685
|
})));
|
|
615
686
|
return extra;
|
|
@@ -746,12 +817,71 @@ async function requestRemoteApproval(payload, opts = {}) {
|
|
|
746
817
|
return null;
|
|
747
818
|
}
|
|
748
819
|
|
|
820
|
+
// ../../shared/src/node/claude-watch.ts
|
|
821
|
+
import { open as open2, stat as stat4 } from "node:fs/promises";
|
|
822
|
+
var INTERRUPTED = "[Request interrupted by user";
|
|
823
|
+
function claudeTurnMark(line) {
|
|
824
|
+
if (!line.includes(INTERRUPTED) && !line.includes('"turn_duration"')) return null;
|
|
825
|
+
let rec;
|
|
826
|
+
try {
|
|
827
|
+
rec = JSON.parse(line);
|
|
828
|
+
} catch {
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
if (rec.isSidechain) return null;
|
|
832
|
+
const at = typeof rec.timestamp === "string" ? rec.timestamp : void 0;
|
|
833
|
+
if (rec.type === "system" && rec.subtype === "turn_duration") return { mark: "ended", at };
|
|
834
|
+
if (rec.type !== "user" || rec.isMeta) return null;
|
|
835
|
+
const c = rec.message?.content;
|
|
836
|
+
const text = typeof c === "string" ? c : Array.isArray(c) && c[0]?.type === "text" ? String(c[0].text ?? "") : "";
|
|
837
|
+
return text.startsWith(INTERRUPTED) ? { mark: "interrupted", at } : null;
|
|
838
|
+
}
|
|
839
|
+
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
840
|
+
async function watchClaudeTurn(path, opts = {}) {
|
|
841
|
+
const pollMs = opts.pollMs ?? 1500;
|
|
842
|
+
const until = Date.now() + (opts.maxMs ?? 2 * 60 * 6e4);
|
|
843
|
+
let offset;
|
|
844
|
+
try {
|
|
845
|
+
offset = (await stat4(path)).size;
|
|
846
|
+
} catch {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
let rest = "";
|
|
850
|
+
while (Date.now() < until) {
|
|
851
|
+
await sleep2(pollMs);
|
|
852
|
+
let size;
|
|
853
|
+
try {
|
|
854
|
+
size = (await stat4(path)).size;
|
|
855
|
+
} catch {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
if (size < offset) return null;
|
|
859
|
+
if (size === offset) continue;
|
|
860
|
+
const file = await open2(path, "r");
|
|
861
|
+
try {
|
|
862
|
+
const buf = Buffer.alloc(size - offset);
|
|
863
|
+
await file.read(buf, 0, buf.length, offset);
|
|
864
|
+
offset = size;
|
|
865
|
+
const lines = (rest + buf.toString("utf8")).split("\n");
|
|
866
|
+
rest = lines.pop() ?? "";
|
|
867
|
+
for (const line of lines) {
|
|
868
|
+
const mark = claudeTurnMark(line);
|
|
869
|
+
if (mark?.mark === "interrupted") return mark;
|
|
870
|
+
if (mark?.mark === "ended") return null;
|
|
871
|
+
}
|
|
872
|
+
} finally {
|
|
873
|
+
await file.close();
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
|
|
749
879
|
// ../../shared/src/node/index.ts
|
|
750
880
|
async function prepareRaw(rec, opts = {}) {
|
|
751
881
|
const filled = rec.source === "antigravity" && opts.enrich !== false ? { ...rec, payload: await fillAntigravityText(rec.payload) } : rec;
|
|
752
882
|
const e = normalizeRecord(filled);
|
|
753
883
|
if (!e) return null;
|
|
754
|
-
const extra = opts.enrich === false ? {} : await enrichEvent(e);
|
|
884
|
+
const extra = opts.enrich === false ? {} : await enrichEvent(e, opts.home ?? bitlHome());
|
|
755
885
|
const keys = opts.keys ?? await loadKeys(opts.home ?? bitlHome());
|
|
756
886
|
return prepareRecord(e, sealerFor(keys), extra);
|
|
757
887
|
}
|
|
@@ -765,5 +895,6 @@ export {
|
|
|
765
895
|
agyWatchStep,
|
|
766
896
|
prepareForTarget,
|
|
767
897
|
prepareRaw,
|
|
768
|
-
requestRemoteApproval
|
|
898
|
+
requestRemoteApproval,
|
|
899
|
+
watchClaudeTurn
|
|
769
900
|
};
|
|
@@ -19997,7 +19997,8 @@ var SignalSchema = external_exports.object({
|
|
|
19997
19997
|
agentNamed: external_exports.boolean().optional(),
|
|
19998
19998
|
waitingReason: WaitingReason.optional(),
|
|
19999
19999
|
toolFailed: external_exports.boolean().optional(),
|
|
20000
|
-
requestTag: external_exports.string().max(100).optional()
|
|
20000
|
+
requestTag: external_exports.string().max(100).optional(),
|
|
20001
|
+
resumedFrom: id.optional()
|
|
20001
20002
|
});
|
|
20002
20003
|
var WireRecordSchema = external_exports.object({
|
|
20003
20004
|
v: external_exports.literal(WIRE_VERSION),
|
|
@@ -20061,7 +20062,7 @@ function removeCloudLogin(home = bitlHome()) {
|
|
|
20061
20062
|
rmSync(cloudKeyPath(home), { force: true });
|
|
20062
20063
|
}
|
|
20063
20064
|
function clientVersionHeader() {
|
|
20064
|
-
const v = "0.1.
|
|
20065
|
+
const v = "0.1.15";
|
|
20065
20066
|
return v ? { "x-bitl-client": v } : {};
|
|
20066
20067
|
}
|
|
20067
20068
|
function resolveTarget(home = bitlHome()) {
|
|
@@ -20118,7 +20119,7 @@ async function latestVersion(home, now) {
|
|
|
20118
20119
|
return latest;
|
|
20119
20120
|
}
|
|
20120
20121
|
var updateCommand = (install = process.env.BITL_INSTALL) => install === "npx" ? "npx @usebitl/cli@latest start" : "npm i -g @usebitl/cli";
|
|
20121
|
-
async function checkCliUpdate(current = "0.1.
|
|
20122
|
+
async function checkCliUpdate(current = "0.1.15", opts = {}) {
|
|
20122
20123
|
if (!current || process.env.BITL_NO_UPDATE_CHECK === "1") return void 0;
|
|
20123
20124
|
const latest = await latestVersion(opts.home ?? bitlHome(), opts.now ?? Date.now());
|
|
20124
20125
|
if (!latest || compareVersions(latest, current) <= 0) return void 0;
|
|
@@ -20128,9 +20129,12 @@ async function checkCliUpdate(current = "0.1.13", opts = {}) {
|
|
|
20128
20129
|
// ../../shared/src/node/antigravity.ts
|
|
20129
20130
|
var MAX_TRANSCRIPT_BYTES = 20 * 1024 * 1024;
|
|
20130
20131
|
|
|
20131
|
-
// ../../shared/src/node/
|
|
20132
|
+
// ../../shared/src/node/resume.ts
|
|
20132
20133
|
var MAX_TRANSCRIPT_BYTES2 = 20 * 1024 * 1024;
|
|
20133
20134
|
|
|
20135
|
+
// ../../shared/src/node/enrich.ts
|
|
20136
|
+
var MAX_TRANSCRIPT_BYTES3 = 20 * 1024 * 1024;
|
|
20137
|
+
|
|
20134
20138
|
// ../../shared/src/node/approve.ts
|
|
20135
20139
|
var DEFAULT_APPROVAL_WAIT_MS = 10 * 6e4;
|
|
20136
20140
|
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isEmptySession,
|
|
12
12
|
parseWireRecord,
|
|
13
13
|
sessionKey
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-65TI66EM.mjs";
|
|
15
15
|
|
|
16
16
|
// ../../../node_modules/.pnpm/bn.js@4.12.5/node_modules/bn.js/lib/bn.js
|
|
17
17
|
var require_bn = __commonJS({
|
|
@@ -9232,8 +9232,21 @@ var SqlStore = class _SqlStore {
|
|
|
9232
9232
|
return session2;
|
|
9233
9233
|
}, _SqlStore.lockKey(accountId, key));
|
|
9234
9234
|
this.emit({ type: "session", accountId, session, prevStatus });
|
|
9235
|
+
if (e.resumedFrom) return await this.linkResumed(accountId, session, sessionKey(e.source, e.resumedFrom)) ?? session;
|
|
9235
9236
|
return session;
|
|
9236
9237
|
}
|
|
9238
|
+
/**
|
|
9239
|
+
* `--resume` that started a new session: the earlier card leaves the board (replacedBy) and its history shows in the
|
|
9240
|
+
* new card's detail (resumedFrom, carrying the earlier card's own predecessors). Content can't move between cards —
|
|
9241
|
+
* it is sealed with each session's key — so the cards stay apart and are read together
|
|
9242
|
+
*/
|
|
9243
|
+
async linkResumed(accountId, session, oldKey) {
|
|
9244
|
+
if (oldKey === session.key || session.resumedFrom?.includes(oldKey)) return void 0;
|
|
9245
|
+
const old = await this.update(accountId, oldKey, (cur) => cur.replacedBy === session.key ? null : { ...cur, replacedBy: session.key });
|
|
9246
|
+
if (!old) return void 0;
|
|
9247
|
+
const chain = [oldKey, ...old.resumedFrom ?? []].filter((k) => k !== session.key);
|
|
9248
|
+
return this.update(accountId, session.key, (cur) => ({ ...cur, resumedFrom: [.../* @__PURE__ */ new Set([...chain, ...cur.resumedFrom ?? []])] }));
|
|
9249
|
+
}
|
|
9237
9250
|
getSession(accountId, key) {
|
|
9238
9251
|
return _SqlStore.read(this.db.query, accountId, key);
|
|
9239
9252
|
}
|
|
@@ -9293,37 +9306,42 @@ var SqlStore = class _SqlStore {
|
|
|
9293
9306
|
acknowledge(accountId, key, now3 = /* @__PURE__ */ new Date()) {
|
|
9294
9307
|
return this.update(accountId, key, (cur) => acknowledge(cur, now3));
|
|
9295
9308
|
}
|
|
9296
|
-
|
|
9309
|
+
/** Deletes one session with its turns and events (onlyEnded: only if it is still ended, checked under lock) */
|
|
9310
|
+
async remove(accountId, key, onlyEnded = false) {
|
|
9297
9311
|
const deleted = await this.db.transaction(async (q) => {
|
|
9298
|
-
const { changes } = await q(
|
|
9312
|
+
const { changes } = await q(`DELETE FROM sessions WHERE account_id = ? AND key = ?${onlyEnded ? ` AND status = 'ended'` : ""}`, [accountId, key]);
|
|
9313
|
+
if (changes === 0) return false;
|
|
9299
9314
|
await q("DELETE FROM turns WHERE account_id = ? AND session_key = ?", [accountId, key]);
|
|
9300
9315
|
await q("DELETE FROM events WHERE account_id = ? AND session_key = ?", [accountId, key]);
|
|
9301
|
-
return
|
|
9316
|
+
return true;
|
|
9302
9317
|
}, _SqlStore.lockKey(accountId, key));
|
|
9303
9318
|
if (deleted) this.emit({ type: "removed", accountId, key });
|
|
9304
9319
|
return deleted;
|
|
9305
9320
|
}
|
|
9321
|
+
/** The earlier sessions a card continues go with it — they are off the board and only shown through it */
|
|
9322
|
+
async removePredecessors(accountId, s) {
|
|
9323
|
+
for (const key of s.resumedFrom ?? []) await this.remove(accountId, key);
|
|
9324
|
+
}
|
|
9325
|
+
async deleteSession(accountId, key) {
|
|
9326
|
+
const cur = await this.getSession(accountId, key);
|
|
9327
|
+
const deleted = await this.remove(accountId, key);
|
|
9328
|
+
if (deleted && cur) await this.removePredecessors(accountId, cur);
|
|
9329
|
+
return deleted;
|
|
9330
|
+
}
|
|
9306
9331
|
/**
|
|
9307
9332
|
* Deletes ended cards with their history (the board's "Ended → Clear all"). With keys, only the ended ones among them (goal-filtered view).
|
|
9308
9333
|
* To spare cards revived by a new event after the list was read, the status is rechecked under lock while deleting. Returns the number deleted
|
|
9309
9334
|
*/
|
|
9310
9335
|
async deleteEnded(accountId, keys) {
|
|
9311
|
-
const { rows } = await this.db.query(`SELECT
|
|
9336
|
+
const { rows } = await this.db.query(`SELECT data FROM sessions WHERE account_id = ? AND status = 'ended' AND archived = 0`, [accountId]);
|
|
9312
9337
|
const only = keys ? new Set(keys) : null;
|
|
9313
9338
|
let n = 0;
|
|
9314
9339
|
for (const r of rows) {
|
|
9315
|
-
const
|
|
9316
|
-
if (only && !only.has(key)) continue;
|
|
9317
|
-
|
|
9318
|
-
const { changes } = await q(`DELETE FROM sessions WHERE account_id = ? AND key = ? AND status = 'ended'`, [accountId, key]);
|
|
9319
|
-
if (changes === 0) return false;
|
|
9320
|
-
await q("DELETE FROM turns WHERE account_id = ? AND session_key = ?", [accountId, key]);
|
|
9321
|
-
await q("DELETE FROM events WHERE account_id = ? AND session_key = ?", [accountId, key]);
|
|
9322
|
-
return true;
|
|
9323
|
-
}, _SqlStore.lockKey(accountId, key));
|
|
9324
|
-
if (deleted) {
|
|
9340
|
+
const s = JSON.parse(r.data);
|
|
9341
|
+
if (s.replacedBy || only && !only.has(s.key)) continue;
|
|
9342
|
+
if (await this.remove(accountId, s.key, true)) {
|
|
9325
9343
|
n++;
|
|
9326
|
-
this.
|
|
9344
|
+
await this.removePredecessors(accountId, s);
|
|
9327
9345
|
}
|
|
9328
9346
|
}
|
|
9329
9347
|
return n;
|
|
@@ -9334,7 +9352,7 @@ var SqlStore = class _SqlStore {
|
|
|
9334
9352
|
let cleared = 0;
|
|
9335
9353
|
for (const r of rows) {
|
|
9336
9354
|
await this.update(accountId, r.key, (cur) => {
|
|
9337
|
-
if (cur.cleared || cur.archived) return null;
|
|
9355
|
+
if (cur.cleared || cur.archived || cur.replacedBy) return null;
|
|
9338
9356
|
cleared++;
|
|
9339
9357
|
return { ...cur, cleared: true };
|
|
9340
9358
|
});
|
|
@@ -12129,10 +12147,10 @@ async function ingestRecord(store, accountId, body) {
|
|
|
12129
12147
|
const rec = parseWireRecord(body);
|
|
12130
12148
|
return rec ? store.ingest(accountId, rec) : null;
|
|
12131
12149
|
}
|
|
12132
|
-
function createApp({ store, webDist, account, push, approvals, goals, access = allowAll, extension }) {
|
|
12150
|
+
function createApp({ store, webDist, account, push, approvals, goals, access = allowAll, extension, build }) {
|
|
12133
12151
|
const app = new Hono2();
|
|
12134
12152
|
const resolve2 = account ?? (() => LOCAL_ACCOUNT);
|
|
12135
|
-
app.get("/api/health", (c) => c.json({ ok: true }));
|
|
12153
|
+
app.get("/api/health", (c) => c.json(build ? { ok: true, build } : { ok: true }));
|
|
12136
12154
|
app.get("/api/me", async (c) => c.json(extension?.me ? await extension.me(c) : { mode: "local" }));
|
|
12137
12155
|
extension?.mountPublic?.(app);
|
|
12138
12156
|
app.get("/api/push/key", (c) => c.json({ publicKey: push?.publicKey ?? null }));
|
|
@@ -12178,6 +12196,11 @@ function createApp({ store, webDist, account, push, approvals, goals, access = a
|
|
|
12178
12196
|
const session = await store.getSession(accountId, key);
|
|
12179
12197
|
if (!session) return c.json({ error: "not found" }, 404);
|
|
12180
12198
|
const [turns, timeline] = await Promise.all([store.listTurns(accountId, key), store.listTimeline(accountId, key)]);
|
|
12199
|
+
for (const prev of session.resumedFrom ?? []) {
|
|
12200
|
+
const [t, items] = await Promise.all([store.listTurns(accountId, prev), store.listTimeline(accountId, prev)]);
|
|
12201
|
+
turns.push(...t);
|
|
12202
|
+
timeline.push(...items.map((item) => ({ ...item, sessionKey: prev })));
|
|
12203
|
+
}
|
|
12181
12204
|
return c.json({ session, turns, timeline });
|
|
12182
12205
|
});
|
|
12183
12206
|
const found = (c, s) => s ? c.json(s) : c.json({ error: "not found" }, 404);
|
|
@@ -12380,13 +12403,13 @@ function startPushNotifier(opts) {
|
|
|
12380
12403
|
};
|
|
12381
12404
|
const notify = async (accountId, s) => {
|
|
12382
12405
|
if (!(await push.list(accountId)).length) return;
|
|
12383
|
-
const attention = (await store.listSessions(accountId)).filter((x) => ALERT.has(x.status) && !isEmptySession(x) && !x.cleared).length;
|
|
12406
|
+
const attention = (await store.listSessions(accountId)).filter((x) => ALERT.has(x.status) && !isEmptySession(x) && !x.cleared && !x.replacedBy).length;
|
|
12384
12407
|
await sendAll(accountId, buildPayload(s, attention), topicOf(s.key));
|
|
12385
12408
|
};
|
|
12386
12409
|
const off = store.subscribe((change) => {
|
|
12387
12410
|
if (change.type !== "session") return;
|
|
12388
12411
|
const s = change.session;
|
|
12389
|
-
if (!ALERT.has(s.status) || change.prevStatus === s.status || isEmptySession(s) || s.cleared) return;
|
|
12412
|
+
if (!ALERT.has(s.status) || change.prevStatus === s.status || isEmptySession(s) || s.cleared || s.replacedBy) return;
|
|
12390
12413
|
const id = `${change.accountId}
|
|
12391
12414
|
${s.key}`;
|
|
12392
12415
|
const t = Date.now();
|
package/dist/cli.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
unwrapMasterKey,
|
|
22
22
|
verifyPublicKey,
|
|
23
23
|
wrapMasterKey
|
|
24
|
-
} from "./chunks/chunk-
|
|
24
|
+
} from "./chunks/chunk-65TI66EM.mjs";
|
|
25
25
|
|
|
26
26
|
// ../src/index.ts
|
|
27
27
|
import { homedir as homedir2 } from "node:os";
|
|
@@ -453,7 +453,7 @@ async function logout() {
|
|
|
453
453
|
}
|
|
454
454
|
|
|
455
455
|
// ../src/update.ts
|
|
456
|
-
async function updateNotice(current = "0.1.
|
|
456
|
+
async function updateNotice(current = "0.1.15") {
|
|
457
457
|
const u = await checkCliUpdate(current);
|
|
458
458
|
return u && `${t("update.available", { ...u })}
|
|
459
459
|
${t("update.changelog")}`;
|
|
@@ -609,7 +609,7 @@ async function main(argv) {
|
|
|
609
609
|
if (String(typeof warning === "string" ? warning : warning.message).startsWith("SQLite is an experimental feature")) return;
|
|
610
610
|
emitWarning(warning, ...rest2);
|
|
611
611
|
});
|
|
612
|
-
const { startServer } = await import("./chunks/src-
|
|
612
|
+
const { startServer } = await import("./chunks/src-TJPPU3TC.mjs");
|
|
613
613
|
await startServer();
|
|
614
614
|
console.log(t("start.pair", { url: localPairUrl() }));
|
|
615
615
|
return;
|
|
@@ -631,7 +631,7 @@ async function main(argv) {
|
|
|
631
631
|
case "--version":
|
|
632
632
|
case "-v":
|
|
633
633
|
case "version":
|
|
634
|
-
console.log("0.1.
|
|
634
|
+
console.log("0.1.15");
|
|
635
635
|
return;
|
|
636
636
|
case "remote":
|
|
637
637
|
case "away":
|
package/package.json
CHANGED