@usebitl/cli 0.1.15 → 0.1.17

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.
@@ -471,7 +471,7 @@ function readCloudConfig(home = bitlHome()) {
471
471
  }
472
472
  }
473
473
  function clientVersionHeader() {
474
- const v = "0.1.15";
474
+ const v = "0.1.17";
475
475
  return v ? { "x-bitl-client": v } : {};
476
476
  }
477
477
  function resolveTarget(home = bitlHome()) {
@@ -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.15";
20065
+ const v = "0.1.17";
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.15", opts = {}) {
20122
+ async function checkCliUpdate(current = "0.1.17", 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;
@@ -11,7 +11,7 @@ import {
11
11
  isEmptySession,
12
12
  parseWireRecord,
13
13
  sessionKey
14
- } from "./chunk-65TI66EM.mjs";
14
+ } from "./chunk-3QUAEA6A.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({
@@ -8842,6 +8842,102 @@ var ApprovalStore = class {
8842
8842
  }
8843
8843
  };
8844
8844
 
8845
+ // ../../server/src/store/entry-store.ts
8846
+ var ENTRY_SCHEMA = `
8847
+ CREATE TABLE IF NOT EXISTS entries (
8848
+ id TEXT NOT NULL,
8849
+ account_id TEXT NOT NULL,
8850
+ session_key TEXT,
8851
+ data TEXT NOT NULL,
8852
+ created_at TEXT NOT NULL,
8853
+ updated_at TEXT NOT NULL,
8854
+ PRIMARY KEY (account_id, id)
8855
+ );
8856
+ CREATE INDEX IF NOT EXISTS entries_time ON entries(account_id, created_at);
8857
+ CREATE INDEX IF NOT EXISTS entries_session ON entries(account_id, session_key, created_at);
8858
+ `;
8859
+ var MAX_ENTRIES = 5e4;
8860
+ var toEntry = (r) => ({
8861
+ id: r.id,
8862
+ sessionKey: r.session_key ?? void 0,
8863
+ data: r.data,
8864
+ createdAt: r.created_at,
8865
+ updatedAt: r.updated_at
8866
+ });
8867
+ var EntryStore = class {
8868
+ constructor(db) {
8869
+ this.db = db;
8870
+ }
8871
+ db;
8872
+ listeners = /* @__PURE__ */ new Set();
8873
+ subscribe(fn) {
8874
+ this.listeners.add(fn);
8875
+ return () => this.listeners.delete(fn);
8876
+ }
8877
+ emit(c) {
8878
+ for (const l of this.listeners) l(c);
8879
+ }
8880
+ /**
8881
+ * Newest first. session: only that card's entries. since/before: written at or after / strictly before (paging back).
8882
+ */
8883
+ async list(accountId, opts = {}) {
8884
+ const where = ["account_id = ?"];
8885
+ const params = [accountId];
8886
+ if (opts.session !== void 0) {
8887
+ where.push("session_key = ?");
8888
+ params.push(opts.session);
8889
+ }
8890
+ if (opts.since) {
8891
+ where.push("created_at >= ?");
8892
+ params.push(opts.since);
8893
+ }
8894
+ if (opts.before) {
8895
+ where.push("created_at < ?");
8896
+ params.push(opts.before);
8897
+ }
8898
+ params.push(Math.min(Math.max(opts.limit ?? 200, 1), 1e3));
8899
+ const { rows } = await this.db.query(`SELECT * FROM entries WHERE ${where.join(" AND ")} ORDER BY created_at DESC, id DESC LIMIT ?`, params);
8900
+ return rows.map(toEntry);
8901
+ }
8902
+ /** The browser picks the id (it is part of the seal's AAD). undefined if it exists or the limit is reached */
8903
+ async create(accountId, id, data, sessionKey2, at = (/* @__PURE__ */ new Date()).toISOString()) {
8904
+ const created = await this.db.transaction(async (q) => {
8905
+ const { rows } = await q("SELECT COUNT(*) AS n, SUM(CASE WHEN id = ? THEN 1 ELSE 0 END) AS dup FROM entries WHERE account_id = ?", [id, accountId]);
8906
+ if (Number(rows[0]?.n ?? 0) >= MAX_ENTRIES || Number(rows[0]?.dup ?? 0) > 0) return false;
8907
+ await q("INSERT INTO entries (id, account_id, session_key, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", [
8908
+ id,
8909
+ accountId,
8910
+ sessionKey2 ?? null,
8911
+ data,
8912
+ at,
8913
+ at
8914
+ ]);
8915
+ return true;
8916
+ });
8917
+ if (!created) return void 0;
8918
+ const entry = { id, ...sessionKey2 ? { sessionKey: sessionKey2 } : {}, data, createdAt: at, updatedAt: at };
8919
+ this.emit({ accountId, type: "entry", entry });
8920
+ return entry;
8921
+ }
8922
+ /** Rewrites the sealed content. The time it was written never changes */
8923
+ async update(accountId, id, data) {
8924
+ const at = (/* @__PURE__ */ new Date()).toISOString();
8925
+ const entry = await this.db.transaction(async (q) => {
8926
+ const { changes } = await q("UPDATE entries SET data = ?, updated_at = ? WHERE account_id = ? AND id = ?", [data, at, accountId, id]);
8927
+ if (!changes) return void 0;
8928
+ const { rows } = await q("SELECT * FROM entries WHERE account_id = ? AND id = ?", [accountId, id]);
8929
+ return rows[0] ? toEntry(rows[0]) : void 0;
8930
+ });
8931
+ if (entry) this.emit({ accountId, type: "entry", entry });
8932
+ return entry;
8933
+ }
8934
+ async delete(accountId, id) {
8935
+ const ok = await this.db.transaction(async (q) => (await q("DELETE FROM entries WHERE account_id = ? AND id = ?", [accountId, id])).changes > 0);
8936
+ if (ok) this.emit({ accountId, type: "entry-removed", id });
8937
+ return ok;
8938
+ }
8939
+ };
8940
+
8845
8941
  // ../../server/src/store/goal-store.ts
8846
8942
  var GOAL_SCHEMA = `
8847
8943
  CREATE TABLE IF NOT EXISTS goals (
@@ -9385,7 +9481,7 @@ import { DatabaseSync } from "node:sqlite";
9385
9481
  var LOCAL_ACCOUNT = "local";
9386
9482
 
9387
9483
  // ../../server/src/store/sqlite.ts
9388
- var SCHEMA_VERSION = 10;
9484
+ var SCHEMA_VERSION = 11;
9389
9485
  var SCHEMA = `
9390
9486
  CREATE TABLE IF NOT EXISTS sessions (
9391
9487
  account_id TEXT NOT NULL,
@@ -9427,7 +9523,8 @@ CREATE INDEX IF NOT EXISTS events_session ON events(account_id, session_key, at)
9427
9523
  CREATE INDEX IF NOT EXISTS events_at ON events(at);
9428
9524
  ${PUSH_SCHEMA}
9429
9525
  ${APPROVAL_SCHEMA}
9430
- ${GOAL_SCHEMA}`;
9526
+ ${GOAL_SCHEMA}
9527
+ ${ENTRY_SCHEMA}`;
9431
9528
  var MIGRATE_2_TO_3 = `
9432
9529
  ALTER TABLE sessions RENAME TO sessions_v2;
9433
9530
  ALTER TABLE turns RENAME TO turns_v2;
@@ -12108,10 +12205,49 @@ var streamSSE = (c, cb, onError) => {
12108
12205
  return c.newResponse(stream2.responseReadable);
12109
12206
  };
12110
12207
 
12208
+ // ../../server/src/entries-api.ts
12209
+ var ENTRY_ID = /^[A-Za-z0-9_-]{8,32}$/;
12210
+ var MAX_DATA = 8e3;
12211
+ var MAX_KEY = 300;
12212
+ var validData = (v) => typeof v === "string" && v.length <= MAX_DATA && SEALED_PATTERN.test(v);
12213
+ var validTime = (v) => typeof v === "string" && !Number.isNaN(Date.parse(v));
12214
+ function mountEntries(app, entries, access = allowAll) {
12215
+ const notFound = { error: "not found" };
12216
+ const readonly = { error: "subscription_required" };
12217
+ app.get("/api/entries", async (c) => {
12218
+ const q = (k) => c.req.query(k) || void 0;
12219
+ const since = q("since");
12220
+ const before = q("before");
12221
+ if (since && !validTime(since) || before && !validTime(before)) return c.json({ error: "invalid time" }, 400);
12222
+ const limit = Number(q("limit") ?? 200);
12223
+ return c.json(await entries.list(c.var.accountId, { session: q("session"), since, before, limit: Number.isFinite(limit) ? limit : 200 }));
12224
+ });
12225
+ app.post("/api/entries", async (c) => {
12226
+ if (!await access.canWrite(c.var.accountId)) return c.json(readonly, 402);
12227
+ const body = await c.req.json().catch(() => null);
12228
+ if (typeof body?.id !== "string" || !ENTRY_ID.test(body.id) || !validData(body.data)) return c.json({ error: "invalid body" }, 400);
12229
+ if (body.sessionKey !== void 0 && (typeof body.sessionKey !== "string" || !body.sessionKey || body.sessionKey.length > MAX_KEY))
12230
+ return c.json({ error: "invalid sessionKey" }, 400);
12231
+ const entry = await entries.create(c.var.accountId, body.id, body.data, body.sessionKey);
12232
+ return entry ? c.json(entry, 201) : c.json({ error: "entry exists or too many entries" }, 409);
12233
+ });
12234
+ app.put("/api/entries/:id", async (c) => {
12235
+ if (!await access.canWrite(c.var.accountId)) return c.json(readonly, 402);
12236
+ const body = await c.req.json().catch(() => null);
12237
+ if (!validData(body?.data)) return c.json({ error: "invalid body" }, 400);
12238
+ const entry = await entries.update(c.var.accountId, c.req.param("id"), body.data);
12239
+ return entry ? c.json(entry) : c.json(notFound, 404);
12240
+ });
12241
+ app.delete(
12242
+ "/api/entries/:id",
12243
+ async (c) => await entries.delete(c.var.accountId, c.req.param("id")) ? c.json({ ok: true }) : c.json(notFound, 404)
12244
+ );
12245
+ }
12246
+
12111
12247
  // ../../server/src/goals-api.ts
12112
12248
  var GOAL_ID = /^[A-Za-z0-9_-]{8,32}$/;
12113
- var MAX_DATA = 64e3;
12114
- var validData = (v) => typeof v === "string" && v.length <= MAX_DATA && SEALED_PATTERN.test(v);
12249
+ var MAX_DATA2 = 64e3;
12250
+ var validData2 = (v) => typeof v === "string" && v.length <= MAX_DATA2 && SEALED_PATTERN.test(v);
12115
12251
  function mountGoals(app, goals, access = allowAll) {
12116
12252
  const notFound = { error: "not found" };
12117
12253
  const readonly = { error: "subscription_required" };
@@ -12119,7 +12255,7 @@ function mountGoals(app, goals, access = allowAll) {
12119
12255
  app.post("/api/goals", async (c) => {
12120
12256
  if (!await access.canWrite(c.var.accountId)) return c.json(readonly, 402);
12121
12257
  const body = await c.req.json().catch(() => null);
12122
- if (typeof body?.id !== "string" || !GOAL_ID.test(body.id) || !validData(body.data)) return c.json({ error: "invalid body" }, 400);
12258
+ if (typeof body?.id !== "string" || !GOAL_ID.test(body.id) || !validData2(body.data)) return c.json({ error: "invalid body" }, 400);
12123
12259
  const goal = await goals.create(c.var.accountId, body.id, body.data);
12124
12260
  return goal ? c.json(goal, 201) : c.json({ error: "goal exists or too many goals" }, 409);
12125
12261
  });
@@ -12127,7 +12263,7 @@ function mountGoals(app, goals, access = allowAll) {
12127
12263
  if (!await access.canWrite(c.var.accountId)) return c.json(readonly, 402);
12128
12264
  const body = await c.req.json().catch(() => null);
12129
12265
  if (!body || !Number.isInteger(body.version)) return c.json({ error: "invalid body" }, 400);
12130
- if (body.data !== void 0 && !validData(body.data)) return c.json({ error: "invalid data" }, 400);
12266
+ if (body.data !== void 0 && !validData2(body.data)) return c.json({ error: "invalid data" }, 400);
12131
12267
  if (body.closed !== void 0 && typeof body.closed !== "boolean") return c.json({ error: "invalid closed" }, 400);
12132
12268
  const r = await goals.update(c.var.accountId, c.req.param("id"), body.version, {
12133
12269
  data: body.data,
@@ -12147,7 +12283,7 @@ async function ingestRecord(store, accountId, body) {
12147
12283
  const rec = parseWireRecord(body);
12148
12284
  return rec ? store.ingest(accountId, rec) : null;
12149
12285
  }
12150
- function createApp({ store, webDist, account, push, approvals, goals, access = allowAll, extension, build }) {
12286
+ function createApp({ store, webDist, account, push, approvals, goals, entries, access = allowAll, extension, build }) {
12151
12287
  const app = new Hono2();
12152
12288
  const resolve2 = account ?? (() => LOCAL_ACCOUNT);
12153
12289
  app.get("/api/health", (c) => c.json(build ? { ok: true, build } : { ok: true }));
@@ -12171,6 +12307,7 @@ function createApp({ store, webDist, account, push, approvals, goals, access = a
12171
12307
  if (push) mountPush(app, push.store);
12172
12308
  if (approvals) mountApprovals(app, approvals, access);
12173
12309
  if (goals) mountGoals(app, goals, access);
12310
+ if (entries) mountEntries(app, entries, access);
12174
12311
  app.post("/api/events", async (c) => {
12175
12312
  let body;
12176
12313
  try {
@@ -12264,10 +12401,16 @@ function createApp({ store, webDist, account, push, approvals, goals, access = a
12264
12401
  queue.push(change);
12265
12402
  wake?.();
12266
12403
  });
12404
+ const offEntries = entries?.subscribe((change) => {
12405
+ if (change.accountId !== accountId) return;
12406
+ queue.push(change);
12407
+ wake?.();
12408
+ });
12267
12409
  const unsubscribe = () => {
12268
12410
  offSessions();
12269
12411
  offApprovals?.();
12270
12412
  offGoals?.();
12413
+ offEntries?.();
12271
12414
  };
12272
12415
  stream2.onAbort(() => {
12273
12416
  unsubscribe();
@@ -12288,7 +12431,7 @@ function createApp({ store, webDist, account, push, approvals, goals, access = a
12288
12431
  }
12289
12432
  const change = queue.shift();
12290
12433
  await stream2.writeSSE(
12291
- change.type === "session" ? { event: "session", data: JSON.stringify(change.session) } : change.type === "approval" ? { event: "approval", data: JSON.stringify(change.approval) } : change.type === "goal" ? { event: "goal", data: JSON.stringify(change.goal) } : change.type === "goal-removed" ? { event: "goal-removed", data: JSON.stringify({ id: change.id }) } : { event: "removed", data: JSON.stringify({ key: change.key }) }
12434
+ change.type === "session" ? { event: "session", data: JSON.stringify(change.session) } : change.type === "approval" ? { event: "approval", data: JSON.stringify(change.approval) } : change.type === "goal" ? { event: "goal", data: JSON.stringify(change.goal) } : change.type === "goal-removed" ? { event: "goal-removed", data: JSON.stringify({ id: change.id }) } : change.type === "entry" ? { event: "entry", data: JSON.stringify(change.entry) } : change.type === "entry-removed" ? { event: "entry-removed", data: JSON.stringify({ id: change.id }) } : { event: "removed", data: JSON.stringify({ key: change.key }) }
12292
12435
  );
12293
12436
  }
12294
12437
  unsubscribe();
@@ -12466,6 +12609,7 @@ function coreServices(store, access = allowAll) {
12466
12609
  const pushStore = vapidPublic && vapidPrivate ? new PushStore(store.db) : void 0;
12467
12610
  const approvals = new ApprovalStore(store.db);
12468
12611
  const goals = new GoalStore(store.db);
12612
+ const entries = new EntryStore(store.db);
12469
12613
  const offApprovalCleanup = closeApprovalsOnResume(store, approvals, logError("approval request cleanup"));
12470
12614
  const notifier = pushStore ? startPushNotifier({
12471
12615
  store,
@@ -12477,7 +12621,7 @@ function coreServices(store, access = allowAll) {
12477
12621
  }) : void 0;
12478
12622
  return {
12479
12623
  /** Passed to createApp */
12480
- app: { push: pushStore && { store: pushStore, publicKey: vapidPublic }, approvals, goals, access },
12624
+ app: { push: pushStore && { store: pushStore, publicKey: vapidPublic }, approvals, goals, entries, access },
12481
12625
  approvals,
12482
12626
  pushing: Boolean(notifier),
12483
12627
  stop() {
package/dist/cli.mjs CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  unwrapMasterKey,
22
22
  verifyPublicKey,
23
23
  wrapMasterKey
24
- } from "./chunks/chunk-65TI66EM.mjs";
24
+ } from "./chunks/chunk-3QUAEA6A.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.15") {
456
+ async function updateNotice(current = "0.1.17") {
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-TJPPU3TC.mjs");
612
+ const { startServer } = await import("./chunks/src-DWPF6JDY.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.15");
634
+ console.log("0.1.17");
635
635
  return;
636
636
  case "remote":
637
637
  case "away":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebitl/cli",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "BITL — see which of your AI coding agents (Claude Code, Codex, Antigravity) are waiting for you. Local board or usebitl.com",
5
5
  "type": "module",
6
6
  "bin": {