starbridge 0.1.3 → 0.1.4-rc.3

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/README.md CHANGED
@@ -72,7 +72,7 @@ question; steps 1 and 3 say when it asks more. A rerun repairs only what is miss
72
72
  4. It installs and starts the background service, `starbridge agent`, as a systemd user unit, a
73
73
  launchd agent, or on Windows a Scheduled Task that starts at logon without administrator
74
74
  rights and logs to `%LOCALAPPDATA%\starbridge\agent.log`.
75
- 5. It uploads a first quota snapshot, then offers to send a test question to your phone and
75
+ 5. It uploads a first quota snapshot, then offers to send a test question to your devices and
76
76
  prints your answer.
77
77
 
78
78
  [Permission prompts](#permission-prompts) stay in the terminal unless you turn them on with
@@ -110,7 +110,9 @@ and asks your devices to revoke the machine. It deletes the keys only when you s
110
110
  ### Check a download
111
111
 
112
112
  The scripts and `starbridge update` install a binary only if its hash is in the release's
113
- `SHA256SUMS` and the release key signed `SHA256SUMS.minisig`. `install.ps1` checks the signature
113
+ `SHA256SUMS` and the release key signed `SHA256SUMS.minisig`. `install.sh` checks the signature
114
+ with minisign, else OpenSSL 3, else Python 3, which every RHEL 8 has for dnf; with none of them it
115
+ stops and gives the command that installs minisign. `install.ps1` checks the signature
114
116
  with minisign's own Windows build, pinned by its hash, since Windows has no Ed25519 check. The key is also in
115
117
  [`minisign.pub`](minisign.pub):
116
118
 
@@ -170,10 +172,10 @@ starbridge ask --question "Merge #12 now?" \
170
172
  - Anywhere else, the agent waits for it with `starbridge wait <id> --timeout 5m`, which exits
171
173
  with code 2 when the time runs out.
172
174
 
173
- To check the path to your phone yourself, ask and wait in one command:
175
+ To check the path to your devices yourself, ask and wait in one command:
174
176
 
175
177
  ```bash
176
- starbridge ask --question "Does this reach my phone?" --option Yes --option No --wait
178
+ starbridge ask --question "Does this reach me?" --option Yes --option No --wait
177
179
  ```
178
180
 
179
181
  When the agent runs out of other work, `starbridge waiting <id>` shows "Waiting for you" on
@@ -232,7 +234,7 @@ To upload without the service:
232
234
  starbridge quota push --provider claude --provider codex
233
235
  ```
234
236
 
235
- ### Quiet the phone while you're here
237
+ ### Quiet your other devices while you're here
236
238
 
237
239
  On a desktop or laptop you work at, turn on presence:
238
240
 
@@ -242,9 +244,9 @@ starbridge config presence on
242
244
 
243
245
  While this machine's screen is unlocked and had keyboard or mouse input in the last minute,
244
246
  notifications on your other devices wait for the time set in Starbridge's Settings (30 s by
245
- default) and come only if the question is still open, so one you answer here doesn't buzz your
246
- phone. The question itself shows everywhere at once. The machine reads its lock and idle time
247
- itself and tells the server only yes or no. It works on macOS, Windows, and Linux under GNOME
247
+ default) and come only if the question is still open, so one you answer here doesn't buzz them.
248
+ The question itself shows everywhere at once. The machine reads its lock and idle time itself
249
+ and tells the server only yes or no. It works on macOS, Windows, and Linux under GNOME
248
250
  or X11 with `xprintidle`; a machine with no screen sends nothing.
249
251
 
250
252
  ### Permission prompts
@@ -10203,6 +10203,7 @@ var PermissionAnswer = object({
10203
10203
  if (a.behavior === "allow" && a.message !== undefined)
10204
10204
  ctx.addIssue({ code: "custom", message: "message is for a deny" });
10205
10205
  });
10206
+ var SETTLED_REASON_MAX = 120;
10206
10207
  var Settled = object({
10207
10208
  v: literal(1),
10208
10209
  id: Id,
@@ -10214,7 +10215,8 @@ var Settled = object({
10214
10215
  choice: string2().max(100).optional(),
10215
10216
  text: string2().max(4000).optional(),
10216
10217
  dir: DirectoryHead.optional(),
10217
- behavior: _enum(["allow", "deny"]).optional()
10218
+ behavior: _enum(["allow", "deny"]).optional(),
10219
+ reason: string2().min(1).max(SETTLED_REASON_MAX).regex(/^[^\r\n]*$/, "one line").optional()
10218
10220
  }).refine((s) => s.device !== undefined || s.choice === undefined && s.text === undefined, {
10219
10221
  message: "choice and text come only with a device's outcome"
10220
10222
  }).refine((s) => s.choice === undefined || s.text === undefined, {
@@ -14368,6 +14370,7 @@ function checkJoined(dir, me) {
14368
14370
  if (!entry?.active || entry.member.role !== me.role || entry.member.boxPk !== me.boxPk || entry.member.signPk !== me.signPk)
14369
14371
  throw new ProtocolError("unknown-member", "the directory does not hold my keys");
14370
14372
  }
14373
+ var CHECK_CONFIRM_MS = 10 * 60000;
14371
14374
  function checkInput(entries, id) {
14372
14375
  const nul = new Uint8Array([0]);
14373
14376
  for (const e of entries) {
@@ -14426,7 +14429,10 @@ function publicKeys(keys) {
14426
14429
  // ../packages/protocol/src/notifications.ts
14427
14430
  var NotifyState = _enum(["on", "off", "blocked"]);
14428
14431
  var DeviceNotifications = object({ state: NotifyState });
14429
- var NotificationStates = object({ devices: record(string2(), NotifyState) });
14432
+ var NotificationStates = object({
14433
+ devices: record(string2(), NotifyState),
14434
+ clients: record(string2(), string2()).optional()
14435
+ });
14430
14436
  var AppSignedIn = object({ signedIn: boolean2() });
14431
14437
  // ../packages/protocol/src/pace.ts
14432
14438
  var MIN_ELAPSED_FRACTION = 0.05;
@@ -14649,7 +14655,7 @@ function processAlive(pid) {
14649
14655
  // package.json
14650
14656
  var package_default = {
14651
14657
  name: "starbridge",
14652
- version: "0.1.3",
14658
+ version: "0.1.4-rc.3",
14653
14659
  description: "Post decisions to your Starbridge devices and upload quota windows from CodexBar",
14654
14660
  license: "MIT",
14655
14661
  repository: {
@@ -16123,7 +16129,7 @@ function needsHello(ctx, id) {
16123
16129
  markConversation(ctx, id);
16124
16130
  return false;
16125
16131
  }
16126
- var NUDGE = "Starbridge sends this session's permission prompts and questions to the owner's phone once it has run a starbridge command. Before anything else, run `starbridge hello` once.";
16132
+ var NUDGE = "Starbridge sends this session's permission prompts and questions to the owner's devices once it has run a starbridge command. Before anything else, run `starbridge hello` once.";
16127
16133
  function hookPreInvocation(ctx, text, opts) {
16128
16134
  if (opts.agent !== "antigravity")
16129
16135
  throw new UsageError(`--agent: antigravity (got ${opts.agent ?? "nothing"})`);
@@ -16538,6 +16544,12 @@ function sourceFor(input, ctx, machine) {
16538
16544
  ...r.sessionLinks.length > 0 ? { links: r.sessionLinks } : {}
16539
16545
  };
16540
16546
  }
16547
+ function typedBreaks(context) {
16548
+ if (/[\r\n]/.test(context))
16549
+ return context;
16550
+ return context.split(/(`[^`]+`)/).map((part, i) => i % 2 === 1 ? part : part.replace(/(?<!\\)\\n/g, `
16551
+ `)).join("");
16552
+ }
16541
16553
  function buildDecision(input, ctx, machine, to) {
16542
16554
  if (!input.question)
16543
16555
  throw new UsageError("ask needs --question");
@@ -16731,16 +16743,33 @@ async function settle2(ctx, opts) {
16731
16743
  if (opts.outcome !== undefined && opts.outcome !== "elsewhere" && opts.outcome !== "withdrawn")
16732
16744
  throw new UsageError("--outcome is elsewhere or withdrawn");
16733
16745
  const outcome = opts.outcome;
16746
+ const { reason } = opts;
16747
+ if (reason !== undefined)
16748
+ checkReason(reason);
16734
16749
  if (id) {
16735
16750
  const asked = ctx.store.state().asked[id];
16736
16751
  if (!asked)
16737
16752
  throw new UsageError(`${id} is not a decision this machine asked`);
16753
+ const withdrawn = (outcome ?? (asked.answerIn ? "elsewhere" : "withdrawn")) === "withdrawn";
16754
+ if (withdrawn && reason === undefined)
16755
+ throw new UsageError(`say why you withdraw it, in one short line: --reason 'Fixed it myself after rereading the logs'`);
16756
+ if (!withdrawn && reason !== undefined)
16757
+ throw new UsageError("--reason is for --outcome withdrawn");
16738
16758
  const s = session(ctx);
16739
- await settleOne(ctx, s, await refreshDirectory(ctx, s), id, outcome);
16759
+ await settleOne(ctx, s, await refreshDirectory(ctx, s), id, outcome, false, reason);
16740
16760
  return 0;
16741
16761
  }
16762
+ if (reason !== undefined && outcome !== "withdrawn")
16763
+ throw new UsageError("--reason is for --outcome withdrawn");
16742
16764
  return settleMany(ctx, opts, outcome);
16743
16765
  }
16766
+ function pickerReason(outcome) {
16767
+ return outcome === "withdrawn" ? { reason: "Its answer could no longer reach the agent" } : {};
16768
+ }
16769
+ function checkReason(reason) {
16770
+ if (!reason.trim() || /[\r\n]/.test(reason) || reason.length > SETTLED_REASON_MAX)
16771
+ throw new UsageError(`--reason is one line of at most ${SETTLED_REASON_MAX} characters`);
16772
+ }
16744
16773
  async function settleMany(ctx, opts, outcome) {
16745
16774
  const st = ctx.store.state();
16746
16775
  const open = Object.entries(st.asked).filter(([id, a]) => (!a.settled || a.unposted) && !a.revoked && !st.answers[id]).filter(([, a]) => opts.all || a.session === opts.session).map(([id]) => id);
@@ -16761,7 +16790,7 @@ async function settleMany(ctx, opts, outcome) {
16761
16790
  return 130;
16762
16791
  }
16763
16792
  try {
16764
- if (await settleOne(ctx, s, dir, id, outcome, true))
16793
+ if (await settleOne(ctx, s, dir, id, outcome, true, opts.reason))
16765
16794
  done++;
16766
16795
  break;
16767
16796
  } catch (e) {
@@ -16778,7 +16807,7 @@ async function settleMany(ctx, opts, outcome) {
16778
16807
  ctx.out(`Settled ${done} decision${done === 1 ? "" : "s"}.`);
16779
16808
  return 0;
16780
16809
  }
16781
- async function settleOne(ctx, s, dir, id, outcomeOpt, bulk = false) {
16810
+ async function settleOne(ctx, s, dir, id, outcomeOpt, bulk = false, reason) {
16782
16811
  const asked = ctx.store.state().asked[id];
16783
16812
  if (!asked || asked.revoked)
16784
16813
  return false;
@@ -16803,6 +16832,7 @@ async function settleOne(ctx, s, dir, id, outcomeOpt, bulk = false) {
16803
16832
  to: to.map((d) => d.id),
16804
16833
  at: iso2(ctx.now()),
16805
16834
  outcome,
16835
+ ...reason !== undefined && outcome === "withdrawn" ? { reason } : {},
16806
16836
  dir: signedHead(dir)
16807
16837
  };
16808
16838
  try {
@@ -17636,7 +17666,7 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as rea
17636
17666
  import { dirname as dirname5, join as join11 } from "node:path";
17637
17667
 
17638
17668
  // ../plugin/hooks/rule.md
17639
- var rule_default = "I am often away from this terminal and may not read your final message for hours. Reach me through the `starbridge` skill, not here, when you need a decision that is mine, and before you end a turn on work that waits on me, such as a PR to review or merge, or a failure only I can fix. In Claude Code, a quick question may use AskUserQuestion instead, since it reaches my devices too. Decide everything else yourself and keep working. Ask here only when `starbridge` fails. Write each card to be read in seconds on a phone, as the skill says.\n\nRun a command that blocks me (takes over the screen, keyboard or session, or holds a device I use), or that my instructions ask you to report, whole through `starbridge run`.\n";
17669
+ var rule_default = "I am often away from this terminal and may not read your final message for hours. Reach me through the `starbridge` skill, not here, when you need a decision that is mine, and before you end a turn on work that waits on me, such as a PR to review or merge, or a failure only I can fix. In Claude Code, a quick question may use AskUserQuestion instead, since it reaches my devices too. Decide everything else yourself and keep working. Ask here only when `starbridge` fails. Write each card to be read in seconds on a phone, as the skill says. When I answer one of your open cards here instead, withdraw it, saying why in a few words: `starbridge settle <id> --outcome withdrawn --reason 'You answered in the terminal'`.\n\nRun a command that blocks me (takes over the screen, keyboard or session, or holds a device I use), or that my instructions ask you to report, whole through `starbridge run`.\n";
17640
17670
 
17641
17671
  // ../plugin/skills/starbridge/SKILL.md
17642
17672
  var SKILL_default = `---
@@ -17714,7 +17744,8 @@ starbridge ask \\
17714
17744
  \`\`\`
17715
17745
 
17716
17746
  Quote text in single quotes, since double quotes expand \`$\` and backticks,
17717
- and write apostrophes as ’. Other flags: \`--image\` (up to 4 PNG or JPEG),
17747
+ and write apostrophes as ’. Break lines inside the quotes with real line
17748
+ breaks, as above, never \`\\n\`: bash and zsh pass it on as typed. Other flags: \`--image\` (up to 4 PNG or JPEG),
17718
17749
  \`--link\` (up to 4 HTTPS URLs), \`--waiting\` (only the answer unblocks you), \`--answer-in\`. \`ask\` prints the decision id (\`d_Xk3…\`) and how the
17719
17750
  answer comes back.
17720
17751
 
@@ -17751,6 +17782,13 @@ if it leaves the choice open. Act on the answer at once. Post again only when
17751
17782
  the outcome changes what they would do (the merge failed); otherwise say it
17752
17783
  in your final message.
17753
17784
 
17785
+ A card still open that no longer needs an answer (the owner answered in the
17786
+ terminal, or you found it yourself) stays in their inbox until you take it
17787
+ back, before you act: \`starbridge settle <id> --outcome withdrawn --reason
17788
+ '…'\`. The reason shows in their History: one short line, 120 characters at
17789
+ most, such as \`'You answered in the terminal'\` or \`'Fixed it myself after
17790
+ rereading the logs'\`.
17791
+
17754
17792
  ## Answers in an artifact
17755
17793
 
17756
17794
  When you built a claude.ai artifact whose buttons send the pick to this
@@ -17759,8 +17797,6 @@ arrives, run \`starbridge settle <id>\`, then act; a card answered in Starbridge
17759
17797
  needs no settle. When the owner taps Done on the card instead, you get
17760
17798
  \`Answer to <id> (…): answered on its page; read the answer there\`: the card is
17761
17799
  closed, so read the pick on the page and act, with no settle. An artifact only to look at goes in \`--link\`, with options.
17762
- \`starbridge settle <id> --outcome withdrawn\` takes back a card you no longer
17763
- need.
17764
17800
 
17765
17801
  ## Report a run
17766
17802
 
@@ -18550,7 +18586,7 @@ function pickerOpen(st, session) {
18550
18586
  return Object.entries(st.asked).filter(([id, a]) => a.picker === session && !a.settled && !st.answers[id]).map(([id]) => id);
18551
18587
  }
18552
18588
  async function settlePicker(ctx, session, outcome) {
18553
- await Promise.all(pickerOpen(ctx.store.state(), session).map((id) => settle2({ ...ctx, signal: undefined }, { id, outcome }).catch((e) => ctx.err(`starbridge: could not settle ${id}: ${e.message}`))));
18589
+ await Promise.all(pickerOpen(ctx.store.state(), session).map((id) => settle2({ ...ctx, signal: undefined }, { id, outcome, ...pickerReason(outcome) }).catch((e) => ctx.err(`starbridge: could not settle ${id}: ${e.message}`))));
18554
18590
  }
18555
18591
  async function racePicker(ctx, outer, hook, waitText) {
18556
18592
  const questions = pickerQuestions(hook.tool_input);
@@ -18686,7 +18722,7 @@ async function hookQuestion(outer, stdin, opts) {
18686
18722
  return 0;
18687
18723
  }
18688
18724
  const outcome = outer.signal?.aborted ? "elsewhere" : "withdrawn";
18689
- await Promise.all(ids.map((id) => id ? settle2({ ...ctx, signal: undefined }, { id, outcome }).catch((e) => ctx.err(`starbridge: could not settle ${id}: ${e.message}`)) : undefined));
18725
+ await Promise.all(ids.map((id) => id ? settle2({ ...ctx, signal: undefined }, { id, outcome, ...pickerReason(outcome) }).catch((e) => ctx.err(`starbridge: could not settle ${id}: ${e.message}`)) : undefined));
18690
18726
  } catch (e) {
18691
18727
  ctx.err(`starbridge: question not sent: ${e.message}`);
18692
18728
  } finally {
@@ -19036,7 +19072,7 @@ import { configDir } from "./poller.ts";
19036
19072
 
19037
19073
  /** The agent API revision this mod speaks, sent in the \`starbridge-api\` header. */
19038
19074
  export const API = 1;
19039
- export const VERSION = "0.1.3";
19075
+ export const VERSION = "0.1.4-rc.3";
19040
19076
  const CLIENT = \`starbridge-mod/\${VERSION}\`;
19041
19077
 
19042
19078
  /** Event types this mod submits; others are skipped and left unconfirmed, as the API asks. */
@@ -23041,7 +23077,7 @@ async function ask2(ctx, hook, questions, signal) {
23041
23077
  if (picked.every((p) => p !== undefined) && !stop.aborted)
23042
23078
  return picked;
23043
23079
  const outcome = signal.aborted ? "elsewhere" : "withdrawn";
23044
- await Promise.all(ids.map((id) => id ? settle2({ ...ctx, signal: undefined }, { id, outcome }).catch((e) => ctx.err(`starbridge: could not settle ${id}: ${e.message}`)) : undefined));
23080
+ await Promise.all(ids.map((id) => id ? settle2({ ...ctx, signal: undefined }, { id, outcome, ...pickerReason(outcome) }).catch((e) => ctx.err(`starbridge: could not settle ${id}: ${e.message}`)) : undefined));
23045
23081
  return;
23046
23082
  }
23047
23083
 
@@ -23897,7 +23933,6 @@ function addedAt(entries, id) {
23897
23933
  }
23898
23934
  }
23899
23935
  }
23900
- var CONFIRM_MS = 10 * 60000;
23901
23936
  var waitingFile = (ctx) => join14(ctx.store.dir, "pair-waiting");
23902
23937
  var answerFile = (ctx, code) => join14(ctx.store.dir, `pair-answer.${code}`);
23903
23938
  function writeWhole(path, text) {
@@ -23935,7 +23970,7 @@ async function confirmCheck(ctx, code, name) {
23935
23970
  typed.push(l);
23936
23971
  }) : undefined;
23937
23972
  try {
23938
- const until = ctx.now().getTime() + CONFIRM_MS;
23973
+ const until = ctx.now().getTime() + CHECK_CONFIRM_MS;
23939
23974
  for (;; ) {
23940
23975
  if (ctx.signal?.aborted || ctx.now().getTime() > until)
23941
23976
  return;
@@ -24548,14 +24583,14 @@ import {
24548
24583
  import { dirname as dirname8, join as join16 } from "node:path";
24549
24584
  // src/setup/codexbar-pin.json
24550
24585
  var codexbar_pin_default = {
24551
- version: "0.73.0",
24586
+ version: "0.74.0",
24552
24587
  sha256: {
24553
- "macos-arm64": "cd8f853779f8faec003f53d5fc2a1614aef577ec9f603cbd58a9559b54e159b9",
24554
- "macos-x86_64": "19e7059de688e62be98790bc10198257f5d326e4a3f4191183c23106eb91780a",
24555
- "linux-aarch64": "ae6f9be046b09dc9d17210b661c25e2ee8a0150edb047b917d9eb17c295c7094",
24556
- "linux-x86_64": "a84f556c7ecebc8e606e0a34ee0d121626f72d7afc1e3adf5746410bc10f9611",
24557
- "linux-musl-aarch64": "a4007dfa6577a436eaeb6b46601982c69548c6e20730b3eb9133d4266d5ea92b",
24558
- "linux-musl-x86_64": "75fd9cfc00784eef8badd6e76f4f74cbdf7c13db0b7f9ca6e105ddb793e6e8f8"
24588
+ "macos-arm64": "9aab1b9229f5d1d77de21b99aa887a60a47921bfd9b66e4b941a0e8a9d6e38e8",
24589
+ "macos-x86_64": "4d239b7412683f1eb529d55b851fe922bb462527850b767d6c9b89ba14be3fdb",
24590
+ "linux-aarch64": "46dc3b259b1df48b3fd6f337c754dd60dbece6772d7840cdc9514ab4977a0007",
24591
+ "linux-x86_64": "fac8b2c474e4e37a2b067224437cb182693411ebf639441cd0facbb57bf086b1",
24592
+ "linux-musl-aarch64": "9dab39f1197d9ccc501d1f678234cf15f94e8b84aa7349ba87103a5866dd793b",
24593
+ "linux-musl-x86_64": "e7b0bdc6d6931a4e6fd5ce4037e1125a073ec6cf38cd72f1b3bd8aeecfde51f5"
24559
24594
  }
24560
24595
  };
24561
24596
 
@@ -25479,7 +25514,7 @@ async function setup(sys, opts) {
25479
25514
  section(ctx, "Test");
25480
25515
  if (upload)
25481
25516
  await firstUpload(ctx, upload);
25482
- if (machine && !opts.yes && await prompt.confirm(" Send a test decision to your phone?", true))
25517
+ if (machine && !opts.yes && await prompt.confirm(" Send a test decision to your devices?", true))
25483
25518
  await testDecision(ctx, machine.name);
25484
25519
  section(ctx, failed.length === 0 ? "Starbridge is set up." : `Setup is done, but ${failed.length === 1 ? "one step" : `${failed.length} steps`} failed.`);
25485
25520
  for (const line of await otherCopies(sys))
@@ -25489,7 +25524,7 @@ async function setup(sys, opts) {
25489
25524
  for (const line of commandTable([
25490
25525
  ["Check it", "starbridge status"],
25491
25526
  ...permissionsEnabled(ctx) ? [] : [["Send permission prompts", "starbridge config permissions on"]],
25492
- ...ctx.store.agentConfig().presence?.enabled || !["desktop", "laptop"].includes(ctx.store.agentConfig().machineKind ?? "") ? [] : [["Quiet the phone while here", "starbridge config presence on"]],
25527
+ ...ctx.store.agentConfig().presence?.enabled || !["desktop", "laptop"].includes(ctx.store.agentConfig().machineKind ?? "") ? [] : [["Quiet other devices while here", "starbridge config presence on"]],
25493
25528
  ["Remove from one agent", `starbridge uninstall --agent ${one}`],
25494
25529
  ["Remove everything", "starbridge uninstall"]
25495
25530
  ]))
@@ -25781,7 +25816,7 @@ async function testDecision(ctx, name) {
25781
25816
  session: ""
25782
25817
  };
25783
25818
  const opts = { wait: true, timeout: "10m" };
25784
- ctx.out(" Sent. Answer it on your phone or the web page (Ctrl-C skips)…");
25819
+ ctx.out(" Sent. Answer it on any of your devices (Ctrl-C skips)…");
25785
25820
  let id;
25786
25821
  const asking = {
25787
25822
  ...ctx,
@@ -25809,7 +25844,7 @@ async function testDecision(ctx, name) {
25809
25844
  if (code !== EXIT_INTERRUPTED || !id)
25810
25845
  return;
25811
25846
  try {
25812
- await settle2(ctx, { id, outcome: "withdrawn" });
25847
+ await settle2(ctx, { id, outcome: "withdrawn", reason: "Skipped in starbridge setup" });
25813
25848
  ctx.out("– Skipped");
25814
25849
  } catch (e) {
25815
25850
  ctx.out(`– Skipped, but the card stays open on your devices: ${e.message}`);
@@ -26254,11 +26289,12 @@ var HELP = `starbridge: post decisions to your devices, report runs, upload quot
26254
26289
  Show the owner that you are now blocked on the decision ("Waiting for you", which
26255
26290
  notifies them once more), or back to working on other things.
26256
26291
 
26257
- starbridge settle <decision id> [--outcome elsewhere|withdrawn]
26292
+ starbridge settle <decision id> [--outcome elsewhere|withdrawn] [--reason <text>]
26258
26293
  starbridge settle --session <id> | --all [--yes] [--outcome elsewhere|withdrawn]
26259
26294
  Close a decision without a Starbridge answer: answered on its --answer-in page
26260
26295
  (elsewhere, the default for those) or no longer needed (withdrawn). Devices move it
26261
- out of the inbox. --session closes every open decision that session asked, --all every
26296
+ out of the inbox. A withdrawal says why in --reason, one short line of at most 120
26297
+ characters that History shows: --reason 'Fixed it myself after rereading the logs'. --session closes every open decision that session asked, --all every
26262
26298
  one this machine asked, after asking (--yes skips the question), at the pace the
26263
26299
  server allows: a looping agent's thousands take over an hour.
26264
26300
 
@@ -26313,7 +26349,7 @@ var HELP = `starbridge: post decisions to your devices, report runs, upload quot
26313
26349
  starbridge plugin's permission hook exits at once. presence: while this machine's
26314
26350
  screen is unlocked and had keyboard or mouse input in the last minute, notifications
26315
26351
  on your other devices wait a few seconds, so a question you answer here doesn't buzz
26316
- your phone; the server hears only yes or no. Off by default. machine-kind: the icon
26352
+ them; the server hears only yes or no. Off by default. machine-kind: the icon
26317
26353
  devices show, detected by setup.
26318
26354
 
26319
26355
  starbridge hook permission --agent claude-code|pi|opencode|antigravity [--wait 570s]
@@ -26465,7 +26501,7 @@ async function run2(argv, ctx) {
26465
26501
  const input = {
26466
26502
  ...fromJson,
26467
26503
  ...v.question !== undefined ? { question: v.question } : {},
26468
- ...v.context !== undefined ? { context: v.context } : {},
26504
+ ...v.context !== undefined ? { context: typedBreaks(v.context) } : {},
26469
26505
  ...v["context-file"] !== undefined ? { context: readText5(v["context-file"]) } : {},
26470
26506
  ...v.option !== undefined ? { options: v.option } : {},
26471
26507
  ...v.recommended !== undefined ? { recommended: v.recommended } : {},
@@ -26490,6 +26526,7 @@ async function run2(argv, ctx) {
26490
26526
  allowPositionals: true,
26491
26527
  options: {
26492
26528
  outcome: { type: "string" },
26529
+ reason: { type: "string" },
26493
26530
  session: { type: "string" },
26494
26531
  all: { type: "boolean" },
26495
26532
  yes: { type: "boolean", short: "y" }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starbridge",
3
- "version": "0.1.3",
3
+ "version": "0.1.4-rc.3",
4
4
  "description": "Post decisions to your Starbridge devices and upload quota windows from CodexBar",
5
5
  "license": "MIT",
6
6
  "repository": {