@appchy/jarvis 0.1.48 → 0.1.50

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/dist/bin.js CHANGED
@@ -669,8 +669,8 @@ function rpcRouter() {
669
669
  return;
670
670
  }
671
671
  if (Array.isArray(parsed)) {
672
- for (const item of parsed) {
673
- await this.dispatch(ctx, item, send);
672
+ for (const item2 of parsed) {
673
+ await this.dispatch(ctx, item2, send);
674
674
  }
675
675
  return;
676
676
  }
@@ -748,7 +748,7 @@ function startIpcServer(socketPath, userId) {
748
748
  };
749
749
  }
750
750
  async function callDaemonIpc(socketPath, method, params, timeoutMs = 5e3) {
751
- return new Promise((resolve9, reject) => {
751
+ return new Promise((resolve10, reject) => {
752
752
  const conn = net.connect(socketPath);
753
753
  let buf = "";
754
754
  let settled = false;
@@ -780,7 +780,7 @@ async function callDaemonIpc(socketPath, method, params, timeoutMs = 5e3) {
780
780
  if (parsed.error) {
781
781
  finish(() => reject(new Error(parsed.error?.message ?? "ipc error")));
782
782
  } else {
783
- finish(() => resolve9(parsed.result));
783
+ finish(() => resolve10(parsed.result));
784
784
  }
785
785
  } catch (err) {
786
786
  finish(() => reject(err instanceof Error ? err : new Error(String(err))));
@@ -1106,9 +1106,21 @@ function writeRootsDirect(roots) {
1106
1106
  }
1107
1107
 
1108
1108
  // src/exchange.ts
1109
- import { execSync as execSync2 } from "child_process";
1110
1109
  import os3 from "os";
1111
1110
  import { createInterface } from "readline";
1111
+
1112
+ // src/browser.ts
1113
+ import { execSync as execSync2 } from "child_process";
1114
+ function openBrowser(req) {
1115
+ try {
1116
+ if (process.platform === "darwin") execSync2(`open "${req.url}"`);
1117
+ else if (process.platform === "win32") execSync2(`start "" "${req.url}"`);
1118
+ else execSync2(`xdg-open "${req.url}"`);
1119
+ } catch {
1120
+ }
1121
+ }
1122
+
1123
+ // src/exchange.ts
1112
1124
  var POLL_INTERVAL_MS2 = 2e3;
1113
1125
  var POLL_TIMEOUT_MS = 10 * 6e4;
1114
1126
  async function exchangeCode(req) {
@@ -1203,21 +1215,13 @@ function humanizeAgo(iso) {
1203
1215
  }
1204
1216
  function readlineQuestion(req) {
1205
1217
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1206
- return new Promise((resolve9) => {
1218
+ return new Promise((resolve10) => {
1207
1219
  rl.question(req.prompt, (answer) => {
1208
1220
  rl.close();
1209
- resolve9(answer);
1221
+ resolve10(answer);
1210
1222
  });
1211
1223
  });
1212
1224
  }
1213
- function openBrowser(req) {
1214
- try {
1215
- if (process.platform === "darwin") execSync2(`open "${req.url}"`);
1216
- else if (process.platform === "win32") execSync2(`start "" "${req.url}"`);
1217
- else execSync2(`xdg-open "${req.url}"`);
1218
- } catch {
1219
- }
1220
- }
1221
1225
  function sleep(ms) {
1222
1226
  return new Promise((r) => setTimeout(r, ms));
1223
1227
  }
@@ -2286,10 +2290,10 @@ function rootsEqual(a, b) {
2286
2290
  }
2287
2291
  async function readlineQuestion2(question) {
2288
2292
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
2289
- return new Promise((resolve9) => {
2293
+ return new Promise((resolve10) => {
2290
2294
  rl.question(question, (input) => {
2291
2295
  rl.close();
2292
- resolve9(input);
2296
+ resolve10(input);
2293
2297
  });
2294
2298
  });
2295
2299
  }
@@ -2366,8 +2370,8 @@ function defaultPrompts() {
2366
2370
  }
2367
2371
  async function promptList(question, fallback) {
2368
2372
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
2369
- const answer = await new Promise((resolve9) => {
2370
- rl.question(question, (input) => resolve9(input.trim()));
2373
+ const answer = await new Promise((resolve10) => {
2374
+ rl.question(question, (input) => resolve10(input.trim()));
2371
2375
  });
2372
2376
  rl.close();
2373
2377
  const raw = !answer ? fallback : answer.split(",").map((s) => s.trim()).filter(Boolean);
@@ -2521,8 +2525,8 @@ function register5(program) {
2521
2525
  }
2522
2526
  async function confirm(question) {
2523
2527
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
2524
- const answer = await new Promise((resolve9) => {
2525
- rl.question(question, (input) => resolve9(input.trim()));
2528
+ const answer = await new Promise((resolve10) => {
2529
+ rl.question(question, (input) => resolve10(input.trim()));
2526
2530
  });
2527
2531
  rl.close();
2528
2532
  return /^y(es)?$/i.test(answer);
@@ -2768,7 +2772,7 @@ async function idOf(term) {
2768
2772
  } catch {
2769
2773
  }
2770
2774
  }
2771
- await new Promise((resolve9) => setTimeout(resolve9, 250));
2775
+ await new Promise((resolve10) => setTimeout(resolve10, 250));
2772
2776
  }
2773
2777
  throw new Error("Claude Code started but never registered a session id");
2774
2778
  }
@@ -2837,13 +2841,13 @@ async function live(req) {
2837
2841
  else options.signal.addEventListener("abort", () => term.kill(), { once: true });
2838
2842
  }
2839
2843
  rest();
2840
- await new Promise((resolve9) => {
2844
+ await new Promise((resolve10) => {
2841
2845
  term.onExit(({ exitCode, signal }) => {
2842
2846
  if (idle) clearTimeout(idle);
2843
2847
  if (closedForIdle) report2("stopped", "closed after sitting idle");
2844
2848
  else if (exitCode === 0) report2("done");
2845
2849
  else report2("failed", endedAs(exitCode, signal));
2846
- resolve9();
2850
+ resolve10();
2847
2851
  });
2848
2852
  });
2849
2853
  }
@@ -4189,6 +4193,19 @@ function toHolders(commits) {
4189
4193
  }
4190
4194
  return distinct;
4191
4195
  }
4196
+ function toHoldersByItem(commits) {
4197
+ const byItem = /* @__PURE__ */ new Map();
4198
+ for (const commit of commits) {
4199
+ for (const id of readTrailers(commit.message).items) {
4200
+ const forItem = byItem.get(id);
4201
+ if (forItem) forItem.push(commit);
4202
+ else byItem.set(id, [commit]);
4203
+ }
4204
+ }
4205
+ const holders = /* @__PURE__ */ new Map();
4206
+ for (const [id, forItem] of byItem) holders.set(id, toHolders(forItem));
4207
+ return holders;
4208
+ }
4192
4209
  function warning(holders, mine) {
4193
4210
  const others = holders.filter((holder) => holder.session !== mine);
4194
4211
  if (others.length === 0) return "";
@@ -4207,6 +4224,7 @@ function isQuiet(commit) {
4207
4224
  }
4208
4225
  function readTrailers(message) {
4209
4226
  const events = [];
4227
+ const items = [];
4210
4228
  let session = "";
4211
4229
  let machine = "";
4212
4230
  for (const raw of message.split("\n")) {
@@ -4214,10 +4232,11 @@ function readTrailers(message) {
4214
4232
  const m = /^(Work-[A-Za-z]+):\s*(.*)$/.exec(line);
4215
4233
  if (!m) continue;
4216
4234
  if (m[1] === "Work-Event" && m[2]) events.push(m[2]);
4235
+ if (m[1] === "Work-Item" && m[2]) items.push(m[2]);
4217
4236
  if (m[1] === "Work-Session") session = m[2] ?? "";
4218
4237
  if (m[1] === "Work-Machine") machine = m[2] ?? "";
4219
4238
  }
4220
- return { session, machine, events };
4239
+ return { session, machine, events, items };
4221
4240
  }
4222
4241
  function describeAge(when) {
4223
4242
  const at = Date.parse(when);
@@ -4232,6 +4251,8 @@ function describeAge(when) {
4232
4251
  // ../../packages/board/src/board.git.ts
4233
4252
  var run3 = promisify4(execFile4);
4234
4253
  var FORMAT = "%x1e%H%x1f%aI%x1f%an%x1f%B";
4254
+ var SOURCED = "%x1e%H%x1f%S%x1f%aI%x1f%an%x1f%B";
4255
+ var TRAILER = "^Work-Item: ";
4235
4256
  var SEP = "";
4236
4257
  var REC = "";
4237
4258
  async function boardCommits(root, id) {
@@ -4279,6 +4300,48 @@ async function git2(root, argv) {
4279
4300
  function escapeGrep(value) {
4280
4301
  return value.replace(/[^A-Za-z0-9_-]/g, (c) => `\\${c}`);
4281
4302
  }
4303
+ async function boardHistory(root) {
4304
+ const [out, tips] = await Promise.all([
4305
+ git2(root, ["log", "--all", "--source", "--reverse", `--format=${SOURCED}`, "-E", `--grep=${TRAILER}`]),
4306
+ branchTips(root)
4307
+ ]);
4308
+ const commits = [];
4309
+ for (const record of out.split(REC)) {
4310
+ if (!record.trim()) continue;
4311
+ const [sha, ref, when, author, message] = record.split(SEP);
4312
+ if (!message || !sha) continue;
4313
+ const branch = shorten(ref ?? "");
4314
+ commits.push({
4315
+ sha,
4316
+ when: when ?? "",
4317
+ by: author ?? "",
4318
+ message,
4319
+ branch,
4320
+ branchLastCommitAt: tips.get(branch) ?? ""
4321
+ });
4322
+ }
4323
+ return commits;
4324
+ }
4325
+ async function branchTips(root) {
4326
+ const out = await git2(root, [
4327
+ "for-each-ref",
4328
+ `--format=%(refname:short)${SEP}%(committerdate:iso-strict)`,
4329
+ "refs/heads",
4330
+ "refs/remotes"
4331
+ ]);
4332
+ const tips = /* @__PURE__ */ new Map();
4333
+ for (const line of out.split("\n")) {
4334
+ const [name, when] = line.split(SEP);
4335
+ if (name && when) tips.set(name, when);
4336
+ }
4337
+ return tips;
4338
+ }
4339
+ function shorten(ref) {
4340
+ return ref.replace(/^refs\/(heads|remotes)\//, "");
4341
+ }
4342
+ async function listHoldersByItem(root) {
4343
+ return toHoldersByItem(await boardHistory(root));
4344
+ }
4282
4345
  async function listHolders(root, id) {
4283
4346
  return toHolders(await boardCommits(root, id));
4284
4347
  }
@@ -4291,11 +4354,11 @@ import { promisify as promisify5 } from "util";
4291
4354
 
4292
4355
  // ../../packages/errors/src/errors.ts
4293
4356
  var ServiceError = class extends Error {
4294
- constructor(message, code, statusCode = 500, context) {
4357
+ constructor(message, code, statusCode = 500, context2) {
4295
4358
  super(message);
4296
4359
  this.code = code;
4297
4360
  this.statusCode = statusCode;
4298
- this.context = context;
4361
+ this.context = context2;
4299
4362
  this.name = "ServiceError";
4300
4363
  Error.captureStackTrace?.(this, this.constructor);
4301
4364
  }
@@ -4320,8 +4383,8 @@ var NotFoundError = class extends ServiceError {
4320
4383
  }
4321
4384
  };
4322
4385
  var ConflictError = class extends ServiceError {
4323
- constructor(message, context) {
4324
- super(message, "CONFLICT", 409, context);
4386
+ constructor(message, context2) {
4387
+ super(message, "CONFLICT", 409, context2);
4325
4388
  this.name = "ConflictError";
4326
4389
  }
4327
4390
  };
@@ -4351,7 +4414,7 @@ function toWorkItem(req) {
4351
4414
  code: asList(front.code),
4352
4415
  covers: asList(front.covers),
4353
4416
  dependsOn: asList(front.depends_on),
4354
- epic: backlog ? parts[1] ?? "" : parts[2] ?? "",
4417
+ epic: backlog ? parts[1] ?? "" : parts.at(-4) ?? "",
4355
4418
  version: backlog ? "backlog" : parts[1] ?? "",
4356
4419
  path: req.path,
4357
4420
  holders: [],
@@ -4359,7 +4422,7 @@ function toWorkItem(req) {
4359
4422
  };
4360
4423
  }
4361
4424
  function toPage(items, req) {
4362
- const matched = items.filter((item) => req.status ? item.status === req.status : true).filter((item) => req.owner ? item.owner === req.owner : true).filter((item) => req.epic ? item.epic === req.epic : true).filter((item) => req.version ? item.version === req.version : true).sort(byPriorityThenName);
4425
+ const matched = items.filter((item2) => req.status ? item2.status === req.status : true).filter((item2) => req.owner ? item2.owner === req.owner : true).filter((item2) => req.epic ? item2.epic === req.epic : true).filter((item2) => req.version ? item2.version === req.version : true).sort(byPriorityThenName);
4363
4426
  const offset = req.offset ?? 0;
4364
4427
  const limit2 = Math.min(req.limit ?? DEFAULT_LIMIT, MAX_LIMIT);
4365
4428
  const page = matched.slice(offset, offset + limit2);
@@ -4447,12 +4510,19 @@ async function listWorkItems(_ctx, req, options) {
4447
4510
  }
4448
4511
  async function findWorkItem(_ctx, req, options) {
4449
4512
  const all = await scanTree(options.root);
4450
- return all.find((item) => item.id === req.id) ?? null;
4513
+ return all.find((item2) => item2.id === req.id) ?? null;
4451
4514
  }
4452
4515
  async function getWorkItem(ctx, req, options) {
4453
- const item = await findWorkItem(ctx, req, options);
4454
- if (!item) throw new NotFoundError("work item", req.id);
4455
- return item;
4516
+ const item2 = await findWorkItem(ctx, req, options);
4517
+ if (!item2) throw new NotFoundError("work item", req.id);
4518
+ return item2;
4519
+ }
4520
+ async function getBrief(_ctx, req, options) {
4521
+ try {
4522
+ return await readFile(join3(options.root, "work", req.path), "utf-8");
4523
+ } catch {
4524
+ return null;
4525
+ }
4456
4526
  }
4457
4527
  async function createWorkItem(ctx, req, options) {
4458
4528
  await harness(
@@ -4577,8 +4647,8 @@ async function scanTree(root) {
4577
4647
  const text = await readFile(file, "utf-8");
4578
4648
  const changedAt = (await stat(file)).mtime.toISOString().slice(0, 10);
4579
4649
  const path22 = relative(work, file).split(sep).join("/");
4580
- const item = toWorkItem({ path: path22, text, changedAt });
4581
- if (item) items.push(item);
4650
+ const item2 = toWorkItem({ path: path22, text, changedAt });
4651
+ if (item2) items.push(item2);
4582
4652
  }
4583
4653
  }
4584
4654
  return items;
@@ -4606,14 +4676,10 @@ function notices(_ctx, _id) {
4606
4676
  }
4607
4677
  async function listWorkItems2(ctx, req, options) {
4608
4678
  const page = await listWorkItems(ctx, req, options);
4609
- const items = await Promise.all(
4610
- page.items.map(async (item) => ({
4611
- ...item,
4612
- holders: await listHolders(options.root, item.id)
4613
- }))
4614
- );
4679
+ const holders = await listHoldersByItem(options.root);
4680
+ const items = page.items.map((item2) => ({ ...item2, holders: holders.get(item2.id) ?? [] }));
4615
4681
  if (!req.takeable) return { ...page, items, notices: notices(ctx) };
4616
- const free = items.filter((item) => item.holders.length === 0);
4682
+ const free = items.filter((item2) => item2.holders.length === 0);
4617
4683
  return {
4618
4684
  items: free,
4619
4685
  offset: page.offset,
@@ -4623,13 +4689,21 @@ async function listWorkItems2(ctx, req, options) {
4623
4689
  notices: notices(ctx)
4624
4690
  };
4625
4691
  }
4692
+ async function getWorkItem2(ctx, req, options) {
4693
+ const item2 = await getWorkItem(ctx, { id: req.id }, options);
4694
+ const [holders, brief3] = await Promise.all([
4695
+ listHolders(options.root, item2.id),
4696
+ getBrief(ctx, { path: item2.path }, options)
4697
+ ]);
4698
+ return { item: { ...item2, holders }, brief: brief3, notices: notices(ctx, item2.id) };
4699
+ }
4626
4700
  async function takeWorkItem(ctx, req, options) {
4627
- const item = await getWorkItem(ctx, { id: req.id }, options);
4701
+ const item2 = await getWorkItem(ctx, { id: req.id }, options);
4628
4702
  const before = await listHolders(options.root, req.id);
4629
4703
  const note = warning(before, req.sessionId ?? "");
4630
4704
  if (req.sessionId)
4631
4705
  await recordSession(ctx, { id: req.id, sessionId: req.sessionId }, options);
4632
- const moved = item.status === "in-progress" ? await getWorkItem(ctx, { id: req.id }, options) : await moveWorkItem(
4706
+ const moved = item2.status === "in-progress" ? await getWorkItem(ctx, { id: req.id }, options) : await moveWorkItem(
4633
4707
  ctx,
4634
4708
  {
4635
4709
  id: req.id,
@@ -4645,19 +4719,19 @@ async function takeWorkItem(ctx, req, options) {
4645
4719
  };
4646
4720
  }
4647
4721
  async function updateWorkItem(ctx, req, options) {
4648
- let item = await getWorkItem(ctx, { id: req.id }, options);
4722
+ let item2 = await getWorkItem(ctx, { id: req.id }, options);
4649
4723
  if (req.observed) {
4650
- item = await observeWorkItem(
4724
+ item2 = await observeWorkItem(
4651
4725
  ctx,
4652
4726
  { id: req.id, ac: req.observed.ac, saw: req.observed.saw },
4653
4727
  options
4654
4728
  );
4655
4729
  }
4656
4730
  if (req.sessionId) {
4657
- item = await recordSession(ctx, { id: req.id, sessionId: req.sessionId }, options);
4731
+ item2 = await recordSession(ctx, { id: req.id, sessionId: req.sessionId }, options);
4658
4732
  }
4659
4733
  if (req.question) {
4660
- item = await askWorkItem(
4734
+ item2 = await askWorkItem(
4661
4735
  ctx,
4662
4736
  {
4663
4737
  id: req.id,
@@ -4669,7 +4743,7 @@ async function updateWorkItem(ctx, req, options) {
4669
4743
  );
4670
4744
  }
4671
4745
  return {
4672
- item: { ...item, holders: await listHolders(options.root, req.id) },
4746
+ item: { ...item2, holders: await listHolders(options.root, req.id) },
4673
4747
  notices: notices(ctx, req.id)
4674
4748
  };
4675
4749
  }
@@ -4677,7 +4751,7 @@ async function completeWorkItem2(ctx, req, options) {
4677
4751
  for (const seen of req.observed ?? []) {
4678
4752
  await observeWorkItem(ctx, { id: req.id, ac: seen.ac, saw: seen.saw }, options);
4679
4753
  }
4680
- const item = await completeWorkItem(
4754
+ const item2 = await completeWorkItem(
4681
4755
  ctx,
4682
4756
  {
4683
4757
  id: req.id,
@@ -4688,7 +4762,7 @@ async function completeWorkItem2(ctx, req, options) {
4688
4762
  options
4689
4763
  );
4690
4764
  return {
4691
- item: { ...item, holders: await listHolders(options.root, req.id) },
4765
+ item: { ...item2, holders: await listHolders(options.root, req.id) },
4692
4766
  notices: notices(ctx, req.id)
4693
4767
  };
4694
4768
  }
@@ -4697,9 +4771,9 @@ async function verifyWorkItem2(ctx, req, options) {
4697
4771
  return { ...verified, notices: notices(ctx, req.id) };
4698
4772
  }
4699
4773
  async function createWorkItem2(ctx, req, options) {
4700
- const item = await createWorkItem(ctx, req, options);
4774
+ const item2 = await createWorkItem(ctx, req, options);
4701
4775
  return {
4702
- item: { ...item, holders: await listHolders(options.root, item.id) },
4776
+ item: { ...item2, holders: await listHolders(options.root, item2.id) },
4703
4777
  notices: notices(ctx)
4704
4778
  };
4705
4779
  }
@@ -4795,6 +4869,9 @@ function parseList(params) {
4795
4869
  if (params && typeof params !== "object") throw new Error("params must be an object");
4796
4870
  return params ?? {};
4797
4871
  }
4872
+ function parseGet(params) {
4873
+ return { id: requireId(params) };
4874
+ }
4798
4875
  function parseTake(params) {
4799
4876
  const p = fields(params);
4800
4877
  const actor = p.actor;
@@ -4845,6 +4922,9 @@ function parseCreate(params) {
4845
4922
  async function list(ctx, req) {
4846
4923
  return listWorkItems2(ctx, req, boardOptions());
4847
4924
  }
4925
+ async function get(ctx, req) {
4926
+ return getWorkItem2(ctx, req, boardOptions());
4927
+ }
4848
4928
  async function take(ctx, req) {
4849
4929
  return takeWorkItem(ctx, req, boardOptions());
4850
4930
  }
@@ -4861,6 +4941,7 @@ async function create(ctx, req) {
4861
4941
  return createWorkItem2(ctx, req, boardOptions());
4862
4942
  }
4863
4943
  router.method("board.list", parseList, list);
4944
+ router.method("board.get", parseGet, get);
4864
4945
  router.method("board.take", parseTake, take);
4865
4946
  router.method("board.update", parseUpdate, update);
4866
4947
  router.method("board.verify", parseVerify, verify);
@@ -5119,10 +5200,10 @@ var GraphStore = class {
5119
5200
  }
5120
5201
  }
5121
5202
  };
5122
- function pushInto(map2, key, item) {
5203
+ function pushInto(map2, key, item2) {
5123
5204
  const list4 = map2.get(key);
5124
- if (list4) list4.push(item);
5125
- else map2.set(key, [item]);
5205
+ if (list4) list4.push(item2);
5206
+ else map2.set(key, [item2]);
5126
5207
  }
5127
5208
  function describeRef(to) {
5128
5209
  return typeof to === "string" ? to : `${to.type}:${to.name}`;
@@ -5237,36 +5318,36 @@ async function find(run6, spec) {
5237
5318
  const listed = await guard(run6, spec.name, () => spec.from.list(ctx));
5238
5319
  if (listed === void 0) return void 0;
5239
5320
  const kept = [];
5240
- for (const item of listed) {
5241
- const checked = await validate(run6, spec, item);
5321
+ for (const item2 of listed) {
5322
+ const checked = await validate(run6, spec, item2);
5242
5323
  if (checked) kept.push(checked);
5243
5324
  }
5244
5325
  return {
5245
5326
  spec,
5246
- entries: kept.map((item) => ({ item, record: {}, items: [] })),
5327
+ entries: kept.map((item2) => ({ item: item2, record: {}, items: [] })),
5247
5328
  ledger: { name: spec.name, items: kept.length, nodes: 0, edges: {}, reserved: {} }
5248
5329
  };
5249
5330
  }
5250
- async function validate(run6, spec, item) {
5331
+ async function validate(run6, spec, item2) {
5251
5332
  const schema = spec.from.schema;
5252
- if (!schema) return item;
5333
+ if (!schema) return item2;
5253
5334
  const checked = await guard(
5254
5335
  run6,
5255
5336
  spec.name,
5256
- async () => await schema["~standard"].validate(item.data)
5337
+ async () => await schema["~standard"].validate(item2.data)
5257
5338
  );
5258
5339
  if (checked === void 0) return void 0;
5259
5340
  if (checked.issues) {
5260
5341
  run6.report({
5261
5342
  severity: "error",
5262
5343
  code: "MAP_SCHEMA",
5263
- message: `${spec.name}: ${item.ref} does not match its schema \u2014 ${issueText(checked.issues)}. Keys present: ${keyText(item.data)}`,
5344
+ message: `${spec.name}: ${item2.ref} does not match its schema \u2014 ${issueText(checked.issues)}. Keys present: ${keyText(item2.data)}`,
5264
5345
  plugin: spec.name,
5265
- ...item.file !== void 0 ? { file: item.file } : {}
5346
+ ...item2.file !== void 0 ? { file: item2.file } : {}
5266
5347
  });
5267
5348
  return void 0;
5268
5349
  }
5269
- return { ...item, data: checked.value };
5350
+ return { ...item2, data: checked.value };
5270
5351
  }
5271
5352
  async function shape(run6, scope2) {
5272
5353
  const ctx = mapContext(run6, scope2.spec.name);
@@ -5378,8 +5459,8 @@ function toNodeInput(id, record, req) {
5378
5459
  if (key === "items" || key === "links" || key === "rel" || key === "inverse" || key === "ref")
5379
5460
  continue;
5380
5461
  if (RESERVED.includes(key) || key === "id") continue;
5381
- const json = jsonOf(value);
5382
- if (json !== void 0) free[key] = json;
5462
+ const json2 = jsonOf(value);
5463
+ if (json2 !== void 0) free[key] = json2;
5383
5464
  }
5384
5465
  return {
5385
5466
  id,
@@ -5409,16 +5490,16 @@ function jsonOf(value) {
5409
5490
  if (Array.isArray(value)) {
5410
5491
  const out = [];
5411
5492
  for (const one of value) {
5412
- const json = jsonOf(one);
5413
- if (json !== void 0) out.push(json);
5493
+ const json2 = jsonOf(one);
5494
+ if (json2 !== void 0) out.push(json2);
5414
5495
  }
5415
5496
  return out;
5416
5497
  }
5417
5498
  if (typeof value === "object") {
5418
5499
  const out = {};
5419
5500
  for (const [key, held] of Object.entries(value)) {
5420
- const json = jsonOf(held);
5421
- if (json !== void 0) out[key] = json;
5501
+ const json2 = jsonOf(held);
5502
+ if (json2 !== void 0) out[key] = json2;
5422
5503
  }
5423
5504
  return out;
5424
5505
  }
@@ -6026,8 +6107,8 @@ async function pool(items, limit2, work) {
6026
6107
  let next = 0;
6027
6108
  const runners = Array.from({ length: Math.min(limit2, items.length) }, async () => {
6028
6109
  for (let i = next++; i < items.length; i = next++) {
6029
- const item = items[i];
6030
- if (item !== void 0) await work(item);
6110
+ const item2 = items[i];
6111
+ if (item2 !== void 0) await work(item2);
6031
6112
  }
6032
6113
  });
6033
6114
  await Promise.all(runners);
@@ -6806,8 +6887,8 @@ function notReadyMs() {
6806
6887
  var PENDING = /* @__PURE__ */ Symbol("pending");
6807
6888
  async function readyWithin(pending) {
6808
6889
  let timer;
6809
- const timeout = new Promise((resolve9) => {
6810
- timer = setTimeout(() => resolve9(PENDING), notReadyMs());
6890
+ const timeout = new Promise((resolve10) => {
6891
+ timer = setTimeout(() => resolve10(PENDING), notReadyMs());
6811
6892
  timer.unref?.();
6812
6893
  });
6813
6894
  try {
@@ -7110,34 +7191,34 @@ function workLadder(graph2) {
7110
7191
  for (const node of graph2.nodes()) {
7111
7192
  if (typeof node.metadata.statusCategory === "string") items.push(node);
7112
7193
  }
7113
- const isItem = new Set(items.map((item) => item.id));
7194
+ const isItem = new Set(items.map((item2) => item2.id));
7114
7195
  const containers = /* @__PURE__ */ new Set();
7115
7196
  const holders = /* @__PURE__ */ new Map();
7116
- for (const item of items) {
7117
- for (const edge of graph2.edges({ from: item.id, relation: "belongs_to" })) {
7197
+ for (const item2 of items) {
7198
+ for (const edge of graph2.edges({ from: item2.id, relation: "belongs_to" })) {
7118
7199
  if (!isItem.has(edge.to)) continue;
7119
7200
  containers.add(edge.to);
7120
- holders.set(item.id, edge.to);
7201
+ holders.set(item2.id, edge.to);
7121
7202
  }
7122
7203
  }
7123
7204
  const children = /* @__PURE__ */ new Map();
7124
7205
  const roots = [];
7125
- for (const item of items) {
7126
- const home = holders.get(item.id);
7206
+ for (const item2 of items) {
7207
+ const home = holders.get(item2.id);
7127
7208
  if (home === void 0) {
7128
- roots.push(item);
7209
+ roots.push(item2);
7129
7210
  continue;
7130
7211
  }
7131
7212
  const bucket = children.get(home) ?? { containers: [], leaves: [] };
7132
- (containers.has(item.id) ? bucket.containers : bucket.leaves).push(item);
7213
+ (containers.has(item2.id) ? bucket.containers : bucket.leaves).push(item2);
7133
7214
  children.set(home, bucket);
7134
7215
  }
7135
7216
  const members = /* @__PURE__ */ new Map();
7136
- for (const item of items) {
7137
- if (containers.has(item.id)) continue;
7138
- const category = String(item.metadata.statusCategory);
7139
- const seen = /* @__PURE__ */ new Set([item.id]);
7140
- let home = holders.get(item.id);
7217
+ for (const item2 of items) {
7218
+ if (containers.has(item2.id)) continue;
7219
+ const category = String(item2.metadata.statusCategory);
7220
+ const seen = /* @__PURE__ */ new Set([item2.id]);
7221
+ let home = holders.get(item2.id);
7141
7222
  while (home !== void 0 && !seen.has(home)) {
7142
7223
  seen.add(home);
7143
7224
  const rollup = members.get(home) ?? {};
@@ -7149,7 +7230,7 @@ function workLadder(graph2) {
7149
7230
  return { items, containers, holders, children, members, roots };
7150
7231
  }
7151
7232
  function leavesOf(ladder) {
7152
- return ladder.items.filter((item) => !ladder.containers.has(item.id));
7233
+ return ladder.items.filter((item2) => !ladder.containers.has(item2.id));
7153
7234
  }
7154
7235
 
7155
7236
  // ../../packages/data/src/mcp/tools/cache.ts
@@ -7314,15 +7395,15 @@ async function brief(ctx, req) {
7314
7395
  const leaves = leavesOf(ladder);
7315
7396
  const byCategory = {};
7316
7397
  const inFlight = [];
7317
- for (const item of leaves) {
7318
- const category = String(item.metadata.statusCategory);
7398
+ for (const item2 of leaves) {
7399
+ const category = String(item2.metadata.statusCategory);
7319
7400
  byCategory[category] = (byCategory[category] ?? 0) + 1;
7320
7401
  if (category === "in-progress")
7321
- inFlight.push(renderWorkItem(ctx, item, ladder.holders.get(item.id)));
7402
+ inFlight.push(renderWorkItem(ctx, item2, ladder.holders.get(item2.id)));
7322
7403
  }
7323
7404
  const openItems = leaves.filter((i) => i.metadata.statusCategory === "open");
7324
- for (const item of openItems.slice(0, Math.max(0, itemCap - inFlight.length))) {
7325
- inFlight.push(renderWorkItem(ctx, item, ladder.holders.get(item.id)));
7405
+ for (const item2 of openItems.slice(0, Math.max(0, itemCap - inFlight.length))) {
7406
+ inFlight.push(renderWorkItem(ctx, item2, ladder.holders.get(item2.id)));
7326
7407
  }
7327
7408
  const shown = inFlight.slice(0, itemCap);
7328
7409
  const allContainers = [...ladder.containers].map((id) => ctx.graph.node(id)).filter((n) => n !== void 0).map((n) => ({
@@ -7407,15 +7488,15 @@ function byContainerRelevance(a, b) {
7407
7488
  const size = (c) => Object.values(c.members).reduce((sum, n) => sum + n, 0);
7408
7489
  return flight(b) - flight(a) || size(b) - size(a) || a.id.localeCompare(b.id);
7409
7490
  }
7410
- function renderWorkItem(ctx, item, home) {
7411
- const priority = item.metadata.priority;
7412
- const dependsOn = ctx.graph.edges({ from: item.id, relation: "depends_on" }).map((e) => e.to);
7491
+ function renderWorkItem(ctx, item2, home) {
7492
+ const priority = item2.metadata.priority;
7493
+ const dependsOn = ctx.graph.edges({ from: item2.id, relation: "depends_on" }).map((e) => e.to);
7413
7494
  return {
7414
- id: item.id,
7415
- ...labelOf(item) ? { title: labelOf(item) } : {},
7416
- type: item.type,
7417
- ...typeof item.metadata.status === "string" ? { status: item.metadata.status } : {},
7418
- statusCategory: String(item.metadata.statusCategory),
7495
+ id: item2.id,
7496
+ ...labelOf(item2) ? { title: labelOf(item2) } : {},
7497
+ type: item2.type,
7498
+ ...typeof item2.metadata.status === "string" ? { status: item2.metadata.status } : {},
7499
+ statusCategory: String(item2.metadata.statusCategory),
7419
7500
  ...typeof priority === "string" ? { priority } : {},
7420
7501
  ...dependsOn.length ? { dependsOn } : {},
7421
7502
  ...home !== void 0 ? { belongsTo: home } : {}
@@ -7476,7 +7557,7 @@ function listConnections(ctx, req) {
7476
7557
  const statusOf0 = start ? statusOf(start) : void 0;
7477
7558
  const seen = new Set(startIds);
7478
7559
  const withStatus = /* @__PURE__ */ new Map();
7479
- const context = /* @__PURE__ */ new Map();
7560
+ const context2 = /* @__PURE__ */ new Map();
7480
7561
  let settled = 0;
7481
7562
  const take2 = (id, via) => {
7482
7563
  if (seen.has(id)) return false;
@@ -7488,7 +7569,7 @@ function listConnections(ctx, req) {
7488
7569
  if (via === void 0 && wants(ctx, node, req.direction)) {
7489
7570
  const thing = render(ctx, node);
7490
7571
  const key = thing.id ?? thing.file ?? id;
7491
- if (!context.has(key)) context.set(key, thing);
7572
+ if (!context2.has(key)) context2.set(key, thing);
7492
7573
  }
7493
7574
  return true;
7494
7575
  }
@@ -7517,7 +7598,7 @@ function listConnections(ctx, req) {
7517
7598
  );
7518
7599
  const page = ordered.slice(0, limit2);
7519
7600
  const label = start ? labelOf(start) ?? start.id : req.node;
7520
- const read3 = [...context.values()];
7601
+ const read3 = [...context2.values()];
7521
7602
  const nothing = ordered.length === 0 && read3.length === 0;
7522
7603
  return {
7523
7604
  summary: nothing ? `Nothing in the map connects to ${label}.` : `${label} connects to ${ordered.length} thing(s) still open${settled > 0 ? ` (and ${settled} settled)` : ""}${read3.length > 0 ? `, plus ${read3.length} it is read against` : ""}.${ordered.length === 0 ? " Nothing open is on it." : ""}`,
@@ -8331,10 +8412,10 @@ function buildEditSet(ctx, found, limit2, namedGovIds, origin) {
8331
8412
  confidence: "precise",
8332
8413
  ...origin ? { origin } : {}
8333
8414
  });
8334
- const named = namedGovIds.has(hit.id);
8415
+ const named2 = namedGovIds.has(hit.id);
8335
8416
  for (const r of enforcing.results) {
8336
8417
  if (!r.file) continue;
8337
- if (named) namedGovFiles.add(r.file);
8418
+ if (named2) namedGovFiles.add(r.file);
8338
8419
  const why = `enforces ${hit.id}`;
8339
8420
  const existing = byFile.get(r.file);
8340
8421
  if (existing) existing.why = existing.why ? `${existing.why}; ${why}` : why;
@@ -9084,11 +9165,11 @@ function graph() {
9084
9165
  }
9085
9166
  function refuseAnotherBranch(req) {
9086
9167
  const asked = req ?? {};
9087
- const named = typeof asked.branch === "string" ? asked.branch : asked.ref;
9088
- if (typeof named !== "string" || !named.trim()) return;
9168
+ const named2 = typeof asked.branch === "string" ? asked.branch : asked.ref;
9169
+ if (typeof named2 !== "string" || !named2.trim()) return;
9089
9170
  throw new RpcError(
9090
9171
  JSON_RPC.INVALID_PARAMS,
9091
- `the map answers for the branch this machine has checked out, and cannot answer for '${named}'. It is built from one checkout, so an answer for another branch would join this branch's code to that branch's work \u2014 which looks like an answer and is not one. Check that branch out and rebuild, or ask the BOARD instead, which does read any branch: \`work list --branch ${named}\` or \`work find\`.`,
9172
+ `the map answers for the branch this machine has checked out, and cannot answer for '${named2}'. It is built from one checkout, so an answer for another branch would join this branch's code to that branch's work \u2014 which looks like an answer and is not one. Check that branch out and rebuild, or ask the BOARD instead, which does read any branch: \`work list --branch ${named2}\` or \`work find\`.`,
9092
9173
  { type: "map_is_per_checkout" }
9093
9174
  );
9094
9175
  }
@@ -9540,7 +9621,7 @@ function parseList2(params) {
9540
9621
  if (params && typeof params !== "object") throw new Error("params must be an object");
9541
9622
  return params ?? {};
9542
9623
  }
9543
- function parseGet(params) {
9624
+ function parseGet2(params) {
9544
9625
  if (!params || typeof params !== "object") throw new Error("params required");
9545
9626
  const p = params;
9546
9627
  if (typeof p.id !== "string") throw new Error("id required");
@@ -9570,7 +9651,7 @@ function parseDelete(params) {
9570
9651
  async function list2(ctx, req) {
9571
9652
  return listNotifications(ctx, req);
9572
9653
  }
9573
- async function get(ctx, req) {
9654
+ async function get2(ctx, req) {
9574
9655
  return getNotification(ctx, req);
9575
9656
  }
9576
9657
  async function create2(ctx, req) {
@@ -9584,7 +9665,7 @@ async function remove(ctx, req) {
9584
9665
  return { ok: true };
9585
9666
  }
9586
9667
  router.method("notifications.list", parseList2, list2);
9587
- router.method("notifications.get", parseGet, get);
9668
+ router.method("notifications.get", parseGet2, get2);
9588
9669
  router.method("notifications.create", parseCreate2, create2);
9589
9670
  router.method("notifications.update", parseUpdate2, update2);
9590
9671
  router.method("notifications.delete", parseDelete, remove);
@@ -9667,7 +9748,7 @@ function parseList3(params) {
9667
9748
  if (params && typeof params !== "object") throw new Error("params must be an object");
9668
9749
  return params ?? {};
9669
9750
  }
9670
- function parseGet2(params) {
9751
+ function parseGet3(params) {
9671
9752
  if (!params || typeof params !== "object") throw new Error("params required");
9672
9753
  const p = params;
9673
9754
  if (typeof p.id !== "string") throw new Error("id required");
@@ -9677,12 +9758,12 @@ async function list3(ctx, req) {
9677
9758
  void ctx;
9678
9759
  return listSessions(req);
9679
9760
  }
9680
- async function get2(ctx, req) {
9761
+ async function get3(ctx, req) {
9681
9762
  void ctx;
9682
9763
  return getSession(req);
9683
9764
  }
9684
9765
  router.method("sessions.list", parseList3, list3);
9685
- router.method("sessions.get", parseGet2, get2);
9766
+ router.method("sessions.get", parseGet3, get3);
9686
9767
 
9687
9768
  // src/workdir.ts
9688
9769
  import { exec as execCb } from "child_process";
@@ -9788,8 +9869,8 @@ import { createRequire as createRequire2 } from "module";
9788
9869
  var _require = createRequire2(import.meta.url);
9789
9870
  var VERSION2 = _require("../package.json").version ?? "0.0.0";
9790
9871
  var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
9791
- var SHA = "5b34716";
9792
- var BUILT = "2026-09-08";
9872
+ var SHA = "de4a370";
9873
+ var BUILT = "2026-09-09";
9793
9874
  var BUILD = SHA ?? "source";
9794
9875
  var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
9795
9876
 
@@ -10139,12 +10220,12 @@ async function startAgent(options) {
10139
10220
  upstream.onConnected(() => {
10140
10221
  if (!paired) {
10141
10222
  paired = true;
10142
- const board = findBoardOrigin(options.workspacePath);
10223
+ const board2 = findBoardOrigin(options.workspacePath);
10143
10224
  publish(envCtx, "env.paired", {
10144
10225
  envId,
10145
10226
  version: process.env.JARVIS_VERSION ?? "0.0.0",
10146
10227
  os: `${os9.type()} ${os9.release()}`,
10147
- ...board ? { boardRepo: board.repo, boardBranch: board.branch } : {}
10228
+ ...board2 ? { boardRepo: board2.repo, boardBranch: board2.branch } : {}
10148
10229
  });
10149
10230
  }
10150
10231
  publish(envCtx, "env.heartbeat", { envId, at: (/* @__PURE__ */ new Date()).toISOString() });
@@ -10242,7 +10323,7 @@ async function startAgent(options) {
10242
10323
  timestamp: Date.now()
10243
10324
  };
10244
10325
  publish(envCtx, "env.disconnected", { envId: options.envId, reason: "shutdown" });
10245
- await new Promise((resolve9) => setImmediate(resolve9));
10326
+ await new Promise((resolve10) => setImmediate(resolve10));
10246
10327
  }
10247
10328
  if (heartbeatTimer) clearInterval(heartbeatTimer);
10248
10329
  if (ipcServer) {
@@ -10519,10 +10600,10 @@ async function getHealth(req = {}) {
10519
10600
  async function probeSocket(url, probeMs) {
10520
10601
  const port = portOf(url);
10521
10602
  if (port === null) return { url, reachable: false };
10522
- const reachable = await new Promise((resolve9) => {
10603
+ const reachable = await new Promise((resolve10) => {
10523
10604
  const done = (answer) => {
10524
10605
  socket.destroy();
10525
- resolve9(answer);
10606
+ resolve10(answer);
10526
10607
  };
10527
10608
  const socket = net2.connect({ host: new URL(url).hostname, port }).once("connect", () => done(true)).once("error", () => done(false));
10528
10609
  socket.setTimeout(probeMs, () => done(false));
@@ -10695,10 +10776,10 @@ function removeDir(p) {
10695
10776
  }
10696
10777
  function confirm2(prompt) {
10697
10778
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
10698
- return new Promise((resolve9) => {
10779
+ return new Promise((resolve10) => {
10699
10780
  rl.question(prompt, (answer) => {
10700
10781
  rl.close();
10701
- resolve9(/^y(es)?$/i.test(answer.trim()));
10782
+ resolve10(/^y(es)?$/i.test(answer.trim()));
10702
10783
  });
10703
10784
  });
10704
10785
  }
@@ -11588,10 +11669,10 @@ function createTool(handlers) {
11588
11669
  }
11589
11670
  };
11590
11671
  }
11591
- function describeItem(item) {
11592
- const held = item.holders && item.holders.length > 0 ? ` \xB7 held by ${item.holders.map((holder) => holder.by || holder.session).join(", ")}` : "";
11593
- const priority = item.priority ? ` ${item.priority}` : "";
11594
- return `${item.id} \xB7 ${item.status}${priority}${held} \u2014 ${item.title}`;
11672
+ function describeItem(item2) {
11673
+ const held = item2.holders && item2.holders.length > 0 ? ` \xB7 held by ${item2.holders.map((holder) => holder.by || holder.session).join(", ")}` : "";
11674
+ const priority = item2.priority ? ` ${item2.priority}` : "";
11675
+ return `${item2.id} \xB7 ${item2.status}${priority}${held} \u2014 ${item2.title}`;
11595
11676
  }
11596
11677
  function describeList(response) {
11597
11678
  if (response.items.length === 0) return "No work items match.";
@@ -11888,9 +11969,140 @@ function register20(program) {
11888
11969
  });
11889
11970
  }
11890
11971
 
11972
+ // src/commands/ui.ts
11973
+ import { basename as basename2 } from "path";
11974
+
11975
+ // src/ui/server.ts
11976
+ import { createServer as createServer2 } from "http";
11977
+ import { readFile as readFile7 } from "fs/promises";
11978
+ import { basename, dirname as dirname7, extname, join as join9, resolve as resolve9 } from "path";
11979
+ import { fileURLToPath as fileURLToPath4 } from "url";
11980
+ var ASSETS = resolve9(dirname7(fileURLToPath4(import.meta.url)), "ui");
11981
+ var TYPES = {
11982
+ ".html": "text/html; charset=utf-8",
11983
+ ".js": "text/javascript; charset=utf-8",
11984
+ ".css": "text/css; charset=utf-8",
11985
+ ".map": "application/json; charset=utf-8"
11986
+ };
11987
+ function named(html, repo) {
11988
+ return html.replace("<title>Jarvis</title>", `<title>${basename(repo).replace(/[<>]/g, "")} \xB7 Jarvis</title>`);
11989
+ }
11990
+ function context() {
11991
+ return { requestId: `ui-${Date.now().toString(36)}`, userId: "local" };
11992
+ }
11993
+ function json(res, status2, body) {
11994
+ const payload2 = JSON.stringify(body);
11995
+ res.writeHead(status2, {
11996
+ "content-type": "application/json; charset=utf-8",
11997
+ "content-length": Buffer.byteLength(payload2),
11998
+ // The board changes under you as sessions work it; a cached page would show a board that has
11999
+ // moved on and give no sign of it.
12000
+ "cache-control": "no-store"
12001
+ });
12002
+ res.end(payload2);
12003
+ }
12004
+ async function board(repo, url, res) {
12005
+ const req = {};
12006
+ const limit2 = url.searchParams.get("limit");
12007
+ if (limit2) req.limit = Number.parseInt(limit2, 10);
12008
+ const offset = url.searchParams.get("offset");
12009
+ if (offset) req.offset = Number.parseInt(offset, 10);
12010
+ const status2 = url.searchParams.get("status");
12011
+ if (status2) req.status = status2;
12012
+ const page = await listWorkItems2(context(), req, { root: repo, harness: harness2() });
12013
+ json(res, 200, { items: page.items, total: page.total, hasMore: page.hasMore });
12014
+ }
12015
+ async function item(repo, id, res) {
12016
+ const read3 = await getWorkItem2(context(), { id }, { root: repo, harness: harness2() });
12017
+ json(res, 200, { item: read3.item, brief: read3.brief });
12018
+ }
12019
+ async function asset(repo, url, res) {
12020
+ const wanted = url.pathname === "/" || !extname(url.pathname) ? "/index.html" : url.pathname;
12021
+ const path22 = join9(ASSETS, wanted);
12022
+ if (!path22.startsWith(ASSETS)) {
12023
+ res.writeHead(403).end("no");
12024
+ return;
12025
+ }
12026
+ try {
12027
+ const raw = await readFile7(path22);
12028
+ const body = extname(path22) === ".html" ? Buffer.from(named(raw.toString("utf-8"), repo)) : raw;
12029
+ res.writeHead(200, {
12030
+ "content-type": TYPES[extname(path22)] ?? "application/octet-stream",
12031
+ "content-length": body.byteLength
12032
+ });
12033
+ res.end(body);
12034
+ } catch {
12035
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
12036
+ res.end(
12037
+ "not found. If this is a fresh checkout, the UI is built by `pnpm --filter @appchy/jarvis build`."
12038
+ );
12039
+ }
12040
+ }
12041
+ function serveUi(repo, port) {
12042
+ const server = createServer2((req, res) => {
12043
+ const url = new URL(req.url ?? "/", "http://localhost");
12044
+ void (async () => {
12045
+ try {
12046
+ if (url.pathname.startsWith("/api/v1/board/items")) {
12047
+ if (req.method !== "GET") {
12048
+ json(res, 405, {
12049
+ error: "this board is read-only here. Changing it runs the harness that owns the tree's invariants \u2014 use `jarvis work` for now."
12050
+ });
12051
+ return;
12052
+ }
12053
+ const id = url.pathname.slice("/api/v1/board/items".length).replace(/^\//, "");
12054
+ if (id) await item(repo, decodeURIComponent(id), res);
12055
+ else await board(repo, url, res);
12056
+ return;
12057
+ }
12058
+ await asset(repo, url, res);
12059
+ } catch (err) {
12060
+ const status2 = err instanceof ServiceError ? err.statusCode : 500;
12061
+ json(res, status2, { error: err instanceof Error ? err.message : String(err) });
12062
+ }
12063
+ })();
12064
+ });
12065
+ return new Promise((ok, fail) => {
12066
+ server.once("error", fail);
12067
+ server.listen(port, "127.0.0.1", () => {
12068
+ ok({
12069
+ url: `http://localhost:${port}`,
12070
+ close: () => new Promise((done) => server.close(() => done()))
12071
+ });
12072
+ });
12073
+ });
12074
+ }
12075
+
12076
+ // src/commands/ui.ts
12077
+ function register21(program) {
12078
+ program.command("ui").description("Open this repo's board in a browser \u2014 nothing to configure, nothing to join").option("--repo <path>", "The repo to show. Default: the working directory").option("--port <number>", "The port to listen on. Default: 3402").option("--no-open", "Print the address instead of opening a browser").action(async (opts) => {
12079
+ const repo = resolveRepo(opts.repo);
12080
+ const port = opts.port ? Number.parseInt(opts.port, 10) : 3402;
12081
+ let server;
12082
+ try {
12083
+ server = await serveUi(repo, port);
12084
+ } catch (err) {
12085
+ const busy = err.code === "EADDRINUSE";
12086
+ console.error(
12087
+ busy ? `port ${port} is already in use. Something else is on it \u2014 pass --port to pick another.` : `could not start: ${err instanceof Error ? err.message : String(err)}`
12088
+ );
12089
+ process.exitCode = 1;
12090
+ return;
12091
+ }
12092
+ console.log(`${basename2(repo)} \u2192 ${server.url}`);
12093
+ console.log("Reading the board from this checkout. Ctrl-C to stop.");
12094
+ if (opts.open !== false) openBrowser({ url: server.url });
12095
+ await new Promise((done) => {
12096
+ const stop = () => void server.close().then(done);
12097
+ process.once("SIGINT", stop);
12098
+ process.once("SIGTERM", stop);
12099
+ });
12100
+ });
12101
+ }
12102
+
11891
12103
  // src/commands/work.ts
11892
12104
  import { spawnSync as spawnSync3 } from "child_process";
11893
- function register21(program) {
12105
+ function register22(program) {
11894
12106
  program.command("work").description("The board \u2014 take work, prove it, finish it").argument("[args...]", "Passed to the harness untouched").allowUnknownOption().passThroughOptions().helpOption(false).action((passed) => {
11895
12107
  const problem = harnessProblem();
11896
12108
  if (problem) {
@@ -11934,6 +12146,7 @@ function createCli() {
11934
12146
  register20(program);
11935
12147
  register2(program);
11936
12148
  register21(program);
12149
+ register22(program);
11937
12150
  return program;
11938
12151
  }
11939
12152