@usebitl/cli 0.1.3 → 0.1.5
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/LICENSE +661 -0
- package/adapters/bitl-agy-watch.mjs +12 -12
- package/adapters/bitl-approve.mjs +6 -6
- package/adapters/bitl-hook.mjs +14 -14
- package/adapters/dist/core.mjs +14 -10
- package/adapters/send.mjs +19 -19
- package/bin/bitl.mjs +8 -5
- package/dist/cli.mjs +32485 -18526
- package/package.json +4 -3
- package/web/apple-touch-icon.png +0 -0
- package/web/assets/index-Cs6u_G1e.css +2 -0
- package/web/assets/index-DWHlwEXu.js +9 -0
- package/web/badge-96.png +0 -0
- package/web/favicon.svg +1 -0
- package/web/icon-192.png +0 -0
- package/web/icon-512.png +0 -0
- package/web/icon-maskable-512.png +0 -0
- package/web/icon-maskable.svg +1 -0
- package/web/icon.svg +1 -0
- package/web/index.html +23 -0
- package/web/logo-120.png +0 -0
- package/web/manifest.webmanifest +17 -0
- package/web/sw.js +3 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Antigravity(agy)
|
|
2
|
+
// Antigravity (agy) approval-wait watcher. bitl-hook.mjs spawns it separately on each PreToolUse (agy doesn't wait).
|
|
3
3
|
//
|
|
4
|
-
// node bitl-agy-watch.mjs <
|
|
4
|
+
// node bitl-agy-watch.mjs <temp file holding the PreToolUse payload>
|
|
5
5
|
//
|
|
6
|
-
// agy
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// node:sqlite
|
|
6
|
+
// agy hooks have no approval-wait event → briefly watch that step's (stepIdx) status in the conversation DB:
|
|
7
|
+
// when it starts waiting for approval, send bitl:PermissionWaiting; when approved/denied/cancelled, bitl:PermissionResolved.
|
|
8
|
+
// If it finishes before that (most tools), send nothing and exit.
|
|
9
|
+
// On Node without node:sqlite (< 22.5) or without the DB (IDE etc.), exit quietly. Always exit 0.
|
|
10
10
|
|
|
11
11
|
import { readFileSync, unlinkSync } from 'node:fs';
|
|
12
12
|
import { homedir } from 'node:os';
|
|
@@ -14,8 +14,8 @@ import { join } from 'node:path';
|
|
|
14
14
|
import { sendRecord } from './send.mjs';
|
|
15
15
|
|
|
16
16
|
const POLL_MS = 400;
|
|
17
|
-
const MAX_MS = 10 * 60_000; //
|
|
18
|
-
const NO_STEP_MS = 15_000; //
|
|
17
|
+
const MAX_MS = 10 * 60_000; // stop watching if approval is put off longer than this
|
|
18
|
+
const NO_STEP_MS = 15_000; // how long to wait for the step to appear in the DB
|
|
19
19
|
|
|
20
20
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
21
21
|
|
|
@@ -29,7 +29,7 @@ async function main() {
|
|
|
29
29
|
try {
|
|
30
30
|
unlinkSync(file);
|
|
31
31
|
} catch {
|
|
32
|
-
//
|
|
32
|
+
// already gone
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
const { conversationId, stepIdx } = payload ?? {};
|
|
@@ -39,7 +39,7 @@ async function main() {
|
|
|
39
39
|
try {
|
|
40
40
|
({ DatabaseSync } = await import('node:sqlite'));
|
|
41
41
|
} catch {
|
|
42
|
-
return; //
|
|
42
|
+
return; // old Node
|
|
43
43
|
}
|
|
44
44
|
const { agyWatchStep } = await import('./dist/core.mjs');
|
|
45
45
|
const dbPath = join(homedir(), '.gemini', 'antigravity-cli', 'conversations', `${conversationId}.db`);
|
|
@@ -48,7 +48,7 @@ async function main() {
|
|
|
48
48
|
try {
|
|
49
49
|
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
50
50
|
} catch {
|
|
51
|
-
return; // DB
|
|
51
|
+
return; // no DB (IDE etc.)
|
|
52
52
|
}
|
|
53
53
|
const query = db.prepare('SELECT status FROM steps WHERE idx = ?');
|
|
54
54
|
const send = (event) => sendRecord({ source: 'antigravity', payload: { ...payload, hook_event_name: event } });
|
|
@@ -61,7 +61,7 @@ async function main() {
|
|
|
61
61
|
try {
|
|
62
62
|
status = query.get(stepIdx)?.status;
|
|
63
63
|
} catch {
|
|
64
|
-
// agy
|
|
64
|
+
// agy is writing (locked) — try again next time
|
|
65
65
|
}
|
|
66
66
|
if (status === undefined && !waited && Date.now() - started > NO_STEP_MS) return;
|
|
67
67
|
const action = agyWatchStep(status, waited);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
2
|
+
// Remote approval hook (Claude Code PermissionRequest, synchronous). bitl install installs it alongside bitl-hook.mjs.
|
|
3
3
|
//
|
|
4
|
-
// node bitl-approve.mjs [--source claude-code] (
|
|
4
|
+
// node bitl-approve.mjs [--source claude-code] (payload on stdin)
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// Only when remote approval is on (the board's "remote approval" switch or bitl remote on) does it wait for allow/deny from the phone (default 10 min, $BITL_APPROVAL_WAIT_SEC).
|
|
7
|
+
// Otherwise it prints nothing and exits right away → Claude Code shows its approval prompt in the terminal as usual.
|
|
8
|
+
// The phone's decision is followed only after verifying the account key signature (the server can't forge an approval). Every failure falls back to "approve in the terminal".
|
|
9
9
|
import { appendFileSync, mkdirSync, statSync } from 'node:fs';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
11
|
import { join } from 'node:path';
|
|
@@ -57,5 +57,5 @@ async function main() {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
main()
|
|
60
|
-
.catch((err) => log(
|
|
60
|
+
.catch((err) => log(`remote approval:${err?.stack ?? err}`))
|
|
61
61
|
.finally(() => process.exit(0));
|
package/adapters/bitl-hook.mjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
2
|
+
// Forwards agent hook/notify payloads to bitl-server.
|
|
3
3
|
//
|
|
4
|
-
// Claude Code / Codex hooks : node bitl-hook.mjs --source claude-code|codex (
|
|
5
|
-
// Codex notify : node bitl-hook.mjs --source codex-notify (
|
|
6
|
-
// Antigravity hooks : node bitl-hook.mjs --source antigravity --event PreToolUse (
|
|
4
|
+
// Claude Code / Codex hooks : node bitl-hook.mjs --source claude-code|codex (payload on stdin)
|
|
5
|
+
// Codex notify : node bitl-hook.mjs --source codex-notify (payload is the last argv)
|
|
6
|
+
// Antigravity hooks : node bitl-hook.mjs --source antigravity --event PreToolUse (the payload has no event name)
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
8
|
+
// Server: $BITL_URL (default http://127.0.0.1:$BITL_PORT, 7777)
|
|
9
|
+
// If the server is down, records pile up (encrypted) in $BITL_HOME/queue.jsonl (default ~/.bitl) and are processed when the server starts.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// Principle: never get in the agent's way — no stdout, always exit 0, short timeouts.
|
|
12
|
+
// Parsing (normalization), minimization and encryption happen on this PC (send.mjs → dist/core.mjs). The server can't read the content.
|
|
13
|
+
// When the rules change, just rebuild the bundle (pnpm build). The install path stays the same, so no reinstall is needed.
|
|
14
14
|
|
|
15
15
|
import { spawn } from 'node:child_process';
|
|
16
16
|
import { writeFileSync } from 'node:fs';
|
|
@@ -43,7 +43,7 @@ function readStdin() {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/** agy
|
|
46
|
+
/** agy doesn't report approval waits via hooks → spawn a separate process to watch that step (bitl-agy-watch.mjs) */
|
|
47
47
|
function watchAgyPermission(payload) {
|
|
48
48
|
try {
|
|
49
49
|
if (typeof payload.stepIdx !== 'number') return;
|
|
@@ -52,12 +52,12 @@ function watchAgyPermission(payload) {
|
|
|
52
52
|
const script = join(import.meta.dirname, 'bitl-agy-watch.mjs');
|
|
53
53
|
spawn(process.execPath, [script, file], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
|
|
54
54
|
} catch {
|
|
55
|
-
//
|
|
55
|
+
// the hook carries on even if the watcher can't start
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
async function main() {
|
|
60
|
-
const receivedAt = new Date().toISOString(); //
|
|
60
|
+
const receivedAt = new Date().toISOString(); // when the hook ran (not when it reached the server)
|
|
61
61
|
const source = argValue('--source') ?? 'claude-code';
|
|
62
62
|
|
|
63
63
|
const lastArg = process.argv[process.argv.length - 1];
|
|
@@ -66,9 +66,9 @@ async function main() {
|
|
|
66
66
|
try {
|
|
67
67
|
payload = JSON.parse(raw);
|
|
68
68
|
} catch {
|
|
69
|
-
return; //
|
|
69
|
+
return; // drop input we can't parse
|
|
70
70
|
}
|
|
71
|
-
// Antigravity
|
|
71
|
+
// Antigravity payloads have no event name, so it comes via --event
|
|
72
72
|
const event = argValue('--event');
|
|
73
73
|
if (event && payload && typeof payload === 'object' && payload.hook_event_name === undefined) payload.hook_event_name = event;
|
|
74
74
|
if (source === 'antigravity' && event === 'PreToolUse') watchAgyPermission(payload);
|
package/adapters/dist/core.mjs
CHANGED
|
@@ -19,7 +19,7 @@ function fromBase64Url(s) {
|
|
|
19
19
|
var generateMasterKey = () => globalThis.crypto.getRandomValues(new Uint8Array(MASTER_KEY_BYTES));
|
|
20
20
|
var info = (name) => ({ name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: utf8.encode(`bitl/v1/${name}`) });
|
|
21
21
|
async function deriveKeys(master) {
|
|
22
|
-
if (master.length !== MASTER_KEY_BYTES) throw new Error(
|
|
22
|
+
if (master.length !== MASTER_KEY_BYTES) throw new Error(`key length is not ${MASTER_KEY_BYTES} bytes`);
|
|
23
23
|
const base = await subtle().importKey("raw", new Uint8Array(master), "HKDF", false, ["deriveKey", "deriveBits"]);
|
|
24
24
|
const [enc, tag2, kidBits] = await Promise.all([
|
|
25
25
|
subtle().deriveKey(info("enc"), base, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]),
|
|
@@ -71,7 +71,7 @@ function summarizeToolInput(input) {
|
|
|
71
71
|
const i = input;
|
|
72
72
|
const first = (list, key) => Array.isArray(list) ? str(list[0]?.[key]) : void 0;
|
|
73
73
|
const pick = commandText(i.command) ?? str(i.file_path) ?? str(i.path) ?? str(i.pattern) ?? str(i.url) ?? str(i.query) ?? str(i.description) ?? str(i.prompt) ?? first(i.questions, "question") ?? str(i.plan) ?? // ExitPlanMode: { plan }
|
|
74
|
-
// Codex webrun: { search_query: [{ q }] }
|
|
74
|
+
// Codex webrun: { search_query: [{ q }] } or { weather: [{ location }] }
|
|
75
75
|
first(i.search_query, "q") ?? first(i.weather, "location");
|
|
76
76
|
return pick ? oneLine(pick) : void 0;
|
|
77
77
|
}
|
|
@@ -243,7 +243,7 @@ function normalizeAntigravity(raw, receivedAt) {
|
|
|
243
243
|
type = error ? "error" : "turn_end";
|
|
244
244
|
text = error ?? str(raw.last_assistant_message);
|
|
245
245
|
break;
|
|
246
|
-
//
|
|
246
|
+
// The two below come not from agy but from bitl-agy-watch.mjs, based on step state in the conversation DB
|
|
247
247
|
case "bitl:PermissionWaiting":
|
|
248
248
|
type = "waiting";
|
|
249
249
|
waitingReason = "permission";
|
|
@@ -292,7 +292,7 @@ function normalizeReport(source, raw, receivedAt) {
|
|
|
292
292
|
nativeType: `report:${event}`,
|
|
293
293
|
at: receivedAt,
|
|
294
294
|
cwd: str(raw.cwd),
|
|
295
|
-
// start
|
|
295
|
+
// For start the title is the instruction itself; for the rest it's the detail
|
|
296
296
|
text: event === "start" ? title ?? detail : detail,
|
|
297
297
|
request: str(raw.request),
|
|
298
298
|
waitingReason: event === "waiting" ? "input" : void 0,
|
|
@@ -466,13 +466,17 @@ function readCloudConfig(home = bitlHome()) {
|
|
|
466
466
|
return null;
|
|
467
467
|
}
|
|
468
468
|
}
|
|
469
|
+
function clientVersionHeader() {
|
|
470
|
+
const v = "0.1.5";
|
|
471
|
+
return v ? { "x-bitl-client": v } : {};
|
|
472
|
+
}
|
|
469
473
|
function resolveTarget(home = bitlHome()) {
|
|
470
474
|
const cloud = readCloudConfig(home);
|
|
471
475
|
if (cloud) {
|
|
472
476
|
return {
|
|
473
477
|
kind: "cloud",
|
|
474
478
|
url: cloud.url.replace(/\/+$/, ""),
|
|
475
|
-
headers: { authorization: `Bearer ${cloud.apiKey}
|
|
479
|
+
headers: { authorization: `Bearer ${cloud.apiKey}`, ...clientVersionHeader() },
|
|
476
480
|
keys: () => keysFromBase64Url(readFileSync2(cloudKeyPath(home), "utf8"))
|
|
477
481
|
};
|
|
478
482
|
}
|
|
@@ -576,7 +580,7 @@ function describeToolRequest(toolName, input, extra = {}) {
|
|
|
576
580
|
if (typeof i.command === "string") detail = i.command;
|
|
577
581
|
else if (typeof i.file_path === "string") {
|
|
578
582
|
const body = str2(i.content) ?? (typeof i.new_string === "string" ? `- ${oneLine(String(i.old_string ?? ""), 300)}
|
|
579
|
-
+ ${i.new_string}` : void 0) ?? (Array.isArray(i.edits) ? `${i.edits.length}
|
|
583
|
+
+ ${i.new_string}` : void 0) ?? (Array.isArray(i.edits) ? `${i.edits.length} edits` : void 0);
|
|
580
584
|
detail = body ? `${i.file_path}
|
|
581
585
|
|
|
582
586
|
${body}` : i.file_path;
|
|
@@ -621,7 +625,7 @@ async function verifyDecision(keys, id, decision, sig) {
|
|
|
621
625
|
var DEFAULT_APPROVAL_WAIT_MS = 10 * 6e4;
|
|
622
626
|
var SKIP_TOOLS = /* @__PURE__ */ new Set(["AskUserQuestion"]);
|
|
623
627
|
var POLL_MS = 25e3;
|
|
624
|
-
var DENY_MESSAGE = "
|
|
628
|
+
var DENY_MESSAGE = "The user denied this from their phone (BITL remote approval).";
|
|
625
629
|
var sleep = (ms, signal) => new Promise((resolve) => {
|
|
626
630
|
const t = setTimeout(resolve, ms);
|
|
627
631
|
signal?.addEventListener("abort", () => (clearTimeout(t), resolve()), { once: true });
|
|
@@ -646,7 +650,7 @@ async function requestRemoteApproval(payload, opts = {}) {
|
|
|
646
650
|
try {
|
|
647
651
|
if ((await api("/api/approvals/mode", { timeoutMs: 2e3 })).away !== true) return null;
|
|
648
652
|
} catch (err) {
|
|
649
|
-
log(
|
|
653
|
+
log(`remote approval: can't reach the server, approving in the terminal (${err.message})`);
|
|
650
654
|
return null;
|
|
651
655
|
}
|
|
652
656
|
const keys = await target.keys();
|
|
@@ -663,7 +667,7 @@ async function requestRemoteApproval(payload, opts = {}) {
|
|
|
663
667
|
timeoutMs: 5e3
|
|
664
668
|
});
|
|
665
669
|
} catch (err) {
|
|
666
|
-
log(
|
|
670
|
+
log(`remote approval: failed to upload the request (${err.message})`);
|
|
667
671
|
return null;
|
|
668
672
|
}
|
|
669
673
|
const deadline = Date.now() + waitMs;
|
|
@@ -681,7 +685,7 @@ async function requestRemoteApproval(payload, opts = {}) {
|
|
|
681
685
|
if (status === "pending") continue;
|
|
682
686
|
if (status === "allow" || status === "deny") {
|
|
683
687
|
if (!await verifyDecision(keys, id, status, typeof a.sig === "string" ? a.sig : "")) {
|
|
684
|
-
log(
|
|
688
|
+
log(`remote approval: received a decision with an invalid signature (${id}) \u2014 ignoring it and approving in the terminal`);
|
|
685
689
|
return null;
|
|
686
690
|
}
|
|
687
691
|
return status === "allow" ? { behavior: "allow" } : { behavior: "deny", message: DENY_MESSAGE };
|
package/adapters/send.mjs
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
// bitl-hook.mjs
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Shared by bitl-hook.mjs and bitl-mcp.mjs: normalizes, minimizes and encrypts the raw payload and sends it; on failure, appends to queue.jsonl.
|
|
2
|
+
// Destination: the cloud account (device API key) if linked via bitl login, otherwise the local server on this PC.
|
|
3
|
+
// Only signals (for status detection) and ciphertext reach the server.
|
|
4
4
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync } from 'node:fs';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
|
|
8
8
|
const HOME = process.env.BITL_HOME?.trim() || join(homedir(), '.bitl');
|
|
9
9
|
const POST_TIMEOUT_MS = 1500;
|
|
10
|
-
const MAX_QUEUE_BYTES = 20 * 1024 * 1024; //
|
|
10
|
+
const MAX_QUEUE_BYTES = 20 * 1024 * 1024; // don't fill the disk even if the server stays unreachable for long
|
|
11
11
|
const MAX_ERROR_LOG_BYTES = 1024 * 1024;
|
|
12
|
-
const FLUSH_LIMIT = 500; //
|
|
12
|
+
const FLUSH_LIMIT = 500; // max lines to resend at once (so the hook isn't held up for long)
|
|
13
13
|
const QUEUE = join(HOME, 'queue.jsonl');
|
|
14
14
|
|
|
15
|
-
//
|
|
15
|
+
// normalization/encryption logic is a bundle of shared (TypeScript) (built by pnpm install / pnpm build)
|
|
16
16
|
let core;
|
|
17
17
|
const loadCore = () => (core ??= import('./dist/core.mjs'));
|
|
18
18
|
|
|
19
|
-
/**
|
|
19
|
+
/** Never write to the agent's screen — problems are logged only here */
|
|
20
20
|
function logError(err) {
|
|
21
21
|
try {
|
|
22
22
|
const file = join(HOME, 'adapter-error.log');
|
|
23
23
|
try {
|
|
24
24
|
if (statSync(file).size > MAX_ERROR_LOG_BYTES) return;
|
|
25
25
|
} catch {
|
|
26
|
-
//
|
|
26
|
+
// no file
|
|
27
27
|
}
|
|
28
28
|
mkdirSync(HOME, { recursive: true });
|
|
29
29
|
appendFileSync(file, `${new Date().toISOString()} ${err?.stack ?? err}\n`, 'utf8');
|
|
30
30
|
} catch {
|
|
31
|
-
//
|
|
31
|
+
// if even logging fails, move on quietly
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -40,10 +40,10 @@ async function post(target, body) {
|
|
|
40
40
|
body,
|
|
41
41
|
signal: AbortSignal.timeout(POST_TIMEOUT_MS),
|
|
42
42
|
});
|
|
43
|
-
if (res.status === 401) logError(`${target.url}: 401 —
|
|
43
|
+
if (res.status === 401) logError(`${target.url}: 401 — this PC is no longer linked. Run bitl login to reconnect`);
|
|
44
44
|
return res.ok;
|
|
45
45
|
} catch {
|
|
46
|
-
return false; //
|
|
46
|
+
return false; // server down / network / timeout
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -51,14 +51,14 @@ function enqueue(lines) {
|
|
|
51
51
|
try {
|
|
52
52
|
if (statSync(QUEUE).size > MAX_QUEUE_BYTES) return false;
|
|
53
53
|
} catch {
|
|
54
|
-
//
|
|
54
|
+
// no queue file
|
|
55
55
|
}
|
|
56
56
|
mkdirSync(HOME, { recursive: true });
|
|
57
57
|
appendFileSync(QUEUE, lines.map((l) => l + '\n').join(''), 'utf8');
|
|
58
58
|
return true;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/**
|
|
61
|
+
/** Whether the record is sealed with this key (anything queued before the link changed is not sent to the new account) */
|
|
62
62
|
function sealedWith(line, kid) {
|
|
63
63
|
try {
|
|
64
64
|
return Object.values(JSON.parse(line).content ?? {}).every((v) => typeof v === 'string' && v.startsWith(`v1.${kid}.`));
|
|
@@ -68,8 +68,8 @@ function sealedWith(line, kid) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
71
|
+
* After a successful send to the cloud, also send queued events (in local mode the local server reads the queue on start).
|
|
72
|
+
* Move the file first so other hooks don't overlap, and re-queue lines that couldn't be sent.
|
|
73
73
|
*/
|
|
74
74
|
async function flushQueue(target, kid) {
|
|
75
75
|
if (!existsSync(QUEUE)) return;
|
|
@@ -77,14 +77,14 @@ async function flushQueue(target, kid) {
|
|
|
77
77
|
try {
|
|
78
78
|
renameSync(QUEUE, processing);
|
|
79
79
|
} catch {
|
|
80
|
-
return; //
|
|
80
|
+
return; // another hook took it first
|
|
81
81
|
}
|
|
82
82
|
const lines = readFileSync(processing, 'utf8').split('\n').filter(Boolean);
|
|
83
83
|
unlinkSync(processing);
|
|
84
84
|
const later = [];
|
|
85
85
|
let failed = false;
|
|
86
86
|
for (const [i, line] of lines.entries()) {
|
|
87
|
-
if (!sealedWith(line, kid)) continue; //
|
|
87
|
+
if (!sealedWith(line, kid)) continue; // sealed with another key or old format — drop it
|
|
88
88
|
if (failed || i >= FLUSH_LIMIT || !(await post(target, line))) {
|
|
89
89
|
failed = true;
|
|
90
90
|
later.push(line);
|
|
@@ -99,10 +99,10 @@ export async function sendRecord({ source, receivedAt = new Date().toISOString()
|
|
|
99
99
|
try {
|
|
100
100
|
prepared = await (await loadCore()).prepareForTarget({ source, receivedAt, payload }, HOME);
|
|
101
101
|
} catch (err) {
|
|
102
|
-
logError(err); //
|
|
102
|
+
logError(err); // no bundle (not built yet) or key unreadable
|
|
103
103
|
return 'dropped';
|
|
104
104
|
}
|
|
105
|
-
if (!prepared.record) return 'dropped'; //
|
|
105
|
+
if (!prepared.record) return 'dropped'; // unparseable event
|
|
106
106
|
const body = JSON.stringify(prepared.record);
|
|
107
107
|
if (await post(prepared, body)) {
|
|
108
108
|
if (prepared.kind === 'cloud') await flushQueue(prepared, prepared.kid).catch(logError);
|
package/bin/bitl.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// bitl
|
|
3
|
-
//
|
|
4
|
-
// npm
|
|
5
|
-
// node bin/bitl.mjs __sync —
|
|
2
|
+
// Entry point for the bitl command (npm package @usebitl/cli).
|
|
3
|
+
// Copies the adapters that hooks run into ~/.bitl/adapters (only when the version changed) and runs the CLI with that path.
|
|
4
|
+
// The global npm folder changes with the Node version (nvm etc.), so hooks pointing at it directly can silently break.
|
|
5
|
+
// node bin/bitl.mjs __sync — copy only (npm postinstall). A failure does not block the install
|
|
6
6
|
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
9
10
|
|
|
10
11
|
const syncOnly = process.argv[2] === '__sync';
|
|
11
12
|
const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
@@ -21,10 +22,12 @@ try {
|
|
|
21
22
|
}
|
|
22
23
|
} catch (err) {
|
|
23
24
|
if (syncOnly) process.exit(0);
|
|
24
|
-
console.error(`bitl: ${dir}
|
|
25
|
+
console.error(`bitl: could not copy adapters to ${dir} — ${err?.message ?? err}`);
|
|
25
26
|
process.exit(1);
|
|
26
27
|
}
|
|
27
28
|
if (syncOnly) process.exit(0);
|
|
28
29
|
|
|
29
30
|
process.env.BITL_ADAPTER_DIR = dir;
|
|
31
|
+
// Web UI that bitl start serves (built into the package)
|
|
32
|
+
process.env.BITL_WEB_DIST ??= fileURLToPath(new URL('../web/', import.meta.url));
|
|
30
33
|
await import('../dist/cli.mjs');
|