@usebitl/cli 0.1.14 → 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 +64 -2
- package/dist/chunks/{chunk-RIX6JB6S.mjs → chunk-65TI66EM.mjs} +2 -2
- package/dist/chunks/{src-WI4MJOSQ.mjs → src-TJPPU3TC.mjs} +1 -1
- package/dist/cli.mjs +4 -4
- package/package.json +1 -1
- package/web/assets/{index-Cry_RPpK.js → index-DKJ1-yaM.js} +1 -1
- 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-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;
|
|
@@ -469,7 +471,7 @@ function readCloudConfig(home = bitlHome()) {
|
|
|
469
471
|
}
|
|
470
472
|
}
|
|
471
473
|
function clientVersionHeader() {
|
|
472
|
-
const v = "0.1.
|
|
474
|
+
const v = "0.1.15";
|
|
473
475
|
return v ? { "x-bitl-client": v } : {};
|
|
474
476
|
}
|
|
475
477
|
function resolveTarget(home = bitlHome()) {
|
|
@@ -815,6 +817,65 @@ async function requestRemoteApproval(payload, opts = {}) {
|
|
|
815
817
|
return null;
|
|
816
818
|
}
|
|
817
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
|
+
|
|
818
879
|
// ../../shared/src/node/index.ts
|
|
819
880
|
async function prepareRaw(rec, opts = {}) {
|
|
820
881
|
const filled = rec.source === "antigravity" && opts.enrich !== false ? { ...rec, payload: await fillAntigravityText(rec.payload) } : rec;
|
|
@@ -834,5 +895,6 @@ export {
|
|
|
834
895
|
agyWatchStep,
|
|
835
896
|
prepareForTarget,
|
|
836
897
|
prepareRaw,
|
|
837
|
-
requestRemoteApproval
|
|
898
|
+
requestRemoteApproval,
|
|
899
|
+
watchClaudeTurn
|
|
838
900
|
};
|
|
@@ -20062,7 +20062,7 @@ function removeCloudLogin(home = bitlHome()) {
|
|
|
20062
20062
|
rmSync(cloudKeyPath(home), { force: true });
|
|
20063
20063
|
}
|
|
20064
20064
|
function clientVersionHeader() {
|
|
20065
|
-
const v = "0.1.
|
|
20065
|
+
const v = "0.1.15";
|
|
20066
20066
|
return v ? { "x-bitl-client": v } : {};
|
|
20067
20067
|
}
|
|
20068
20068
|
function resolveTarget(home = bitlHome()) {
|
|
@@ -20119,7 +20119,7 @@ async function latestVersion(home, now) {
|
|
|
20119
20119
|
return latest;
|
|
20120
20120
|
}
|
|
20121
20121
|
var updateCommand = (install = process.env.BITL_INSTALL) => install === "npx" ? "npx @usebitl/cli@latest start" : "npm i -g @usebitl/cli";
|
|
20122
|
-
async function checkCliUpdate(current = "0.1.
|
|
20122
|
+
async function checkCliUpdate(current = "0.1.15", opts = {}) {
|
|
20123
20123
|
if (!current || process.env.BITL_NO_UPDATE_CHECK === "1") return void 0;
|
|
20124
20124
|
const latest = await latestVersion(opts.home ?? bitlHome(), opts.now ?? Date.now());
|
|
20125
20125
|
if (!latest || compareVersions(latest, current) <= 0) return void 0;
|
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