ryugi-artifacts 1.63.0 → 1.65.0

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/cli.cjs CHANGED
@@ -84,10 +84,10 @@ function normalize(command, result2, options, policy) {
84
84
  const keys = [.../* @__PURE__ */ new Set([...options.fields, ...policy.requiredFields ?? []])];
85
85
  if (policy.collection && data && typeof data === "object") {
86
86
  const record = data;
87
- const rows = record[policy.collection];
87
+ const rows2 = record[policy.collection];
88
88
  data = {
89
89
  ...record,
90
- [policy.collection]: Array.isArray(rows) ? rows.map((row, i) => project(row, keys, `data.${policy.collection}.${i}`, omitted)) : rows
90
+ [policy.collection]: Array.isArray(rows2) ? rows2.map((row, i) => project(row, keys, `data.${policy.collection}.${i}`, omitted)) : rows2
91
91
  };
92
92
  } else data = project(data, keys, "data", omitted);
93
93
  }
@@ -275,9 +275,9 @@ function wantsHelp(args) {
275
275
  const end = args.indexOf("--");
276
276
  return (end === -1 ? args : args.slice(0, end)).some((arg) => arg === "--help" || arg === "-h");
277
277
  }
278
- function table(rows) {
279
- const width = Math.max(...rows.map(([left]) => left.length)) + 2;
280
- return rows.map(
278
+ function table(rows2) {
279
+ const width = Math.max(...rows2.map(([left]) => left.length)) + 2;
280
+ return rows2.map(
281
281
  ([left, right]) => ` ${left.padEnd(width)}${right.replaceAll("\n", `
282
282
  ${" ".repeat(width)}`)}`.trimEnd()
283
283
  ).join("\n");
@@ -290,7 +290,7 @@ function optionRows(options) {
290
290
  }
291
291
  function commandHelp(command) {
292
292
  const usage = (command.usage ?? ["[options]"]).map(
293
- (line, index) => `${index ? " " : "Usage: "}${BIN} ${command.name} ${line}`
293
+ (line2, index) => `${index ? " " : "Usage: "}${BIN} ${command.name} ${line2}`
294
294
  );
295
295
  return [
296
296
  usage.join("\n"),
@@ -494,19 +494,33 @@ var EffectJournal = class {
494
494
  this.entries.push({ operation, target, state: "unknown" });
495
495
  return this.entries.length - 1;
496
496
  }
497
- confirm(id, details) {
498
- this.set(id, "confirmed", details);
497
+ confirm(id2, details) {
498
+ this.set(id2, "confirmed", details);
499
499
  }
500
- set(id, state, details) {
501
- const entry = this.entries[id];
500
+ set(id2, state, details) {
501
+ const entry = this.entries[id2];
502
502
  if (!entry) throw new Error("Unknown effect");
503
- this.entries[id] = { ...entry, state, ...details === void 0 ? {} : { details } };
503
+ this.entries[id2] = { ...entry, state, ...details === void 0 ? {} : { details } };
504
504
  }
505
505
  snapshot() {
506
506
  return this.entries.map((entry) => ({ ...entry }));
507
507
  }
508
508
  };
509
509
 
510
+ // src/cli/output/time.ts
511
+ var ISO = /^\d{4}-\d\d-\d\dT\d\d:\d\d(?::\d\d(?:\.\d+)?)?(?:Z|[+-]\d\d:?\d\d)$/;
512
+ var two = (n) => String(n).padStart(2, "0");
513
+ var isIsoTime = (value3) => typeof value3 === "string" && ISO.test(value3) && !Number.isNaN(Date.parse(value3));
514
+ function formatTime(iso, now = /* @__PURE__ */ new Date()) {
515
+ const at = new Date(iso);
516
+ if (Number.isNaN(at.getTime())) return iso;
517
+ const clock = `${two(at.getHours())}:${two(at.getMinutes())}`;
518
+ if (at.getFullYear() !== now.getFullYear())
519
+ return `${at.getFullYear()}-${two(at.getMonth() + 1)}-${two(at.getDate())}`;
520
+ if (at.getMonth() === now.getMonth() && at.getDate() === now.getDate()) return clock;
521
+ return `${two(at.getMonth() + 1)}-${two(at.getDate())} ${clock}`;
522
+ }
523
+
510
524
  // src/cli/output/view.ts
511
525
  function recoveryActions(error, subject) {
512
526
  const details = error.details;
@@ -526,13 +540,57 @@ var fact = (label, binding, required = false) => ({
526
540
  });
527
541
 
528
542
  // src/cli/output/render.ts
529
- var clean = (text) => text.replaceAll(new RegExp("\\p{Cc}", "gu"), " ");
530
- function clip(text, size = 160) {
531
- const segments = [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text)];
532
- return segments.length > size ? `${segments.slice(0, size).map((s) => s.segment).join("")}\u2026` : text;
543
+ var clean = (text2) => text2.replaceAll(new RegExp("\\p{Cc}", "gu"), " ");
544
+ function clip(text2, size = 160) {
545
+ const segments = [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text2)];
546
+ return segments.length > size ? `${segments.slice(0, size).map((s) => s.segment).join("")}\u2026` : text2;
547
+ }
548
+ function flatten(value3, nested = false) {
549
+ if (value3 === null || value3 === void 0) return "";
550
+ if (Array.isArray(value3)) return value3.map((item) => flatten(item, true)).join(", ");
551
+ if (typeof value3 === "object") {
552
+ const text2 = Object.entries(value3).filter(([, item]) => !absent(item)).map(([key, item]) => `${key} ${flatten(item, true)}`).join(nested ? ", " : " \xB7 ");
553
+ return nested ? `(${text2})` : text2;
554
+ }
555
+ return isIsoTime(value3) ? formatTime(value3) : String(value3);
556
+ }
557
+ var scalar = (value3, path18, loose) => {
558
+ if (value3 !== null && typeof value3 === "object") {
559
+ if (!loose)
560
+ throw new Error(`Brief cannot print the object at ${path18}; flatten it in the view.`);
561
+ return flatten(value3);
562
+ }
563
+ return flatten(value3);
564
+ };
565
+ var absent = (value3, fallback) => value3 === void 0 || value3 === null || value3 === "" || value3 === false || fallback !== void 0 && value3 === fallback;
566
+ var EXIT_WORDS = {
567
+ 1: "FAILED",
568
+ 2: "NEEDS YOU",
569
+ 3: "ISSUES",
570
+ 4: "INCOMPLETE",
571
+ 5: "PARTIAL",
572
+ 130: "INTERRUPTED",
573
+ 143: "INTERRUPTED"
574
+ };
575
+ var RECOVERY = {
576
+ E_ARTIFACT_NOT_FOUND: "check --artifact and --project-dir; artifacts live in <project>/.ryugi/artifacts/<type>/<id>/",
577
+ E_ARGUMENT_INVALID: "run the command with --help for its options",
578
+ E_CURSOR_EXPIRED: "run the read again without --cursor or --snapshot",
579
+ E_CURSOR_MISMATCH: "run the read again without --cursor or --snapshot"
580
+ };
581
+ var statusWord = (status, code) => code ? EXIT_WORDS[code] ?? "FAILED" : clean(status).replaceAll("_", " ").toUpperCase();
582
+ var commandName = (id2) => {
583
+ const words2 = id2.replaceAll(/[._]/g, " ").trim();
584
+ return words2 ? words2[0].toUpperCase() + words2.slice(1) : "Command";
585
+ };
586
+ function pageText(page) {
587
+ const offset = page.offset ?? 0;
588
+ if (!page.hasMore && offset === 0) return null;
589
+ const of = page.total === null ? "" : ` of ${page.total}`;
590
+ if (page.offset === void 0)
591
+ return `${page.returned}${of} shown${page.total === null && page.hasMore ? " \xB7 more available" : ""}`;
592
+ return `${page.returned ? `${offset + 1}\u2013${offset + page.returned}` : "none"}${of}`;
533
593
  }
534
- var scalar = (value3) => value3 === null ? "none" : typeof value3 === "object" ? JSON.stringify(value3) : String(value3);
535
- var scopeText = (page) => `${clean(page.scope)} \xB7 ${page.returned} shown${page.total === null ? "" : ` / ${page.total} matched`}${page.hasMore ? " \xB7 more available" : ""}`;
536
594
  var quote = (value3) => /^[\w./:@=+-]+$/.test(value3) ? value3 : `'${value3.replaceAll("'", "'\\''")}'`;
537
595
  function actionText(action) {
538
596
  return action.kind === "command" ? action.argv.map(quote).join(" ") : action.url;
@@ -540,38 +598,72 @@ function actionText(action) {
540
598
  var effectLines = (effects) => effects.map(
541
599
  (effect) => `Effect: ${clean(effect.operation)} ${clean(effect.target)} \xB7 ${effect.state}`
542
600
  );
543
- function renderText(view, wire, options, collection, requiredFields = []) {
544
- const lines = [
545
- `${clean(view.heading)}: ${clean(view.status).toUpperCase()}${view.subject ? ` \xB7 ${clean(view.subject)}` : ""}`
546
- ];
601
+ var lastSegment = (path18) => path18.split(".").filter(Boolean).at(-1) ?? path18;
602
+ function renderText(view, wire, options, collection, requiredFields = [], code = 0) {
603
+ const head = [
604
+ statusWord(view.status, code),
605
+ view.subject && clean(view.subject),
606
+ view.summary && clean(view.summary)
607
+ ].filter(Boolean);
608
+ const lines = [head.length ? `${clean(view.heading)}: ${head.join(" \xB7 ")}` : clean(view.heading)];
547
609
  const omissions = [...wire.omitted ?? []];
548
610
  const selected = (key, required = false) => required || requiredFields.includes(key) || !options.fields || options.fields.includes(key);
549
- const value3 = (input, path18) => {
550
- const text = clean(scalar(input));
551
- const shown = options.detail ? text : clip(text);
552
- if (shown !== text) omissions.push({ path: path18, reason: "preview" });
611
+ const value3 = (input, path18, loose = false) => {
612
+ const text2 = clean(scalar(input, path18, loose));
613
+ const shown = options.detail ? text2 : clip(text2);
614
+ if (shown !== text2) omissions.push({ path: path18, reason: "preview" });
553
615
  return shown;
554
616
  };
555
- const facts = (items) => {
556
- for (const item of items)
557
- if (collection || selected(item.binding.key, item.required))
558
- lines.push(
559
- `${clean(item.label)}: ${value3(item.binding.value, `data.${item.binding.key}`)}`
560
- );
617
+ const cell = (label, key, input, path18, positional2, fallback, loose = false) => {
618
+ if (absent(input, fallback)) return null;
619
+ const name = clean(label ?? key);
620
+ if (input === true) return name;
621
+ const text2 = value3(input, path18, loose);
622
+ return positional2 ? text2 : `${name} ${text2}`;
623
+ };
624
+ const facts = (items, loose = false) => {
625
+ for (const item of items) {
626
+ if (!collection && !selected(item.binding.key, item.required)) continue;
627
+ if (absent(item.binding.value)) continue;
628
+ const text2 = item.binding.value === true ? "yes" : value3(item.binding.value, `data.${item.binding.key}`, loose);
629
+ lines.push(`${clean(item.label)}: ${text2}`);
630
+ }
631
+ };
632
+ const metadata = (items, path18) => items.map(
633
+ (item) => cell(
634
+ item.label,
635
+ item.binding.key,
636
+ item.binding.value,
637
+ `${path18}.${item.binding.key}`,
638
+ item.positional
639
+ )
640
+ ).filter((text2) => text2 !== null).join(" \xB7 ");
641
+ const title2 = (text2, summary) => {
642
+ if (text2) lines.push(summary ? `${clean(text2)} \xB7 ${clean(summary)}` : clean(text2));
643
+ };
644
+ const pageLine = (page) => {
645
+ const text2 = page && pageText(page);
646
+ if (text2) lines.push(text2);
561
647
  };
562
648
  facts(view.facts);
563
649
  const errorActions = wire.error ? recoveryActions(wire.error) : [];
564
650
  if (wire.error) {
565
651
  lines.push(`${clean(wire.error.code)}: ${clean(wire.error.message)}`);
566
- if (wire.error.httpStatus !== void 0) lines.push(`HTTP: ${wire.error.httpStatus}`);
567
- const details = wire.error.details;
568
- if (details && typeof details === "object" && !Array.isArray(details)) {
569
- for (const [key, detail] of Object.entries(details)) {
652
+ if (wire.error.httpStatus !== void 0 && wire.error.httpStatus >= 500)
653
+ lines.push(`HTTP: ${wire.error.httpStatus}`);
654
+ const hint = RECOVERY[wire.error.code];
655
+ if (hint) lines.push(`Next: ${hint}`);
656
+ const details2 = wire.error.details;
657
+ if (details2 && typeof details2 === "object" && !Array.isArray(details2)) {
658
+ for (const [key, detail] of Object.entries(details2)) {
570
659
  if (!errorActions.some((action) => action.label === key))
571
- facts([{ label: key, binding: { key, value: detail }, required: true }]);
660
+ facts([{ label: key, binding: { key, value: detail }, required: true }], true);
572
661
  }
573
- } else if (details !== void 0)
574
- facts([{ label: "Details", binding: { key: "details", value: details }, required: true }]);
662
+ } else if (details2 !== void 0)
663
+ facts(
664
+ [{ label: "Details", binding: { key: "details", value: details2 }, required: true }],
665
+ true
666
+ );
575
667
  }
576
668
  if (!collection && options.fields && wire.data && typeof wire.data === "object") {
577
669
  const bound = /* @__PURE__ */ new Set([
@@ -582,7 +674,8 @@ function renderText(view, wire, options, collection, requiredFields = []) {
582
674
  ]);
583
675
  const data = wire.data;
584
676
  facts(
585
- [.../* @__PURE__ */ new Set([...options.fields, ...requiredFields])].filter((key) => !bound.has(key) && Object.hasOwn(data, key)).map((key) => ({ label: key, binding: { key, value: data[key] } }))
677
+ [.../* @__PURE__ */ new Set([...options.fields, ...requiredFields])].filter((key) => !bound.has(key) && Object.hasOwn(data, key)).map((key) => ({ label: key, binding: { key, value: data[key] } })),
678
+ true
586
679
  );
587
680
  }
588
681
  for (const section of view.sections) {
@@ -591,59 +684,66 @@ function renderText(view, wire, options, collection, requiredFields = []) {
591
684
  continue;
592
685
  }
593
686
  if (!collection && !selected(section.key)) continue;
594
- if (section.page)
595
- lines.push(
596
- `${clean("title" in section && section.title || section.key)}: ${section.status ? `${clean(section.status)} \xB7 ` : ""}${scopeText(section.page)}`
597
- );
687
+ const named = "title" in section && section.title ? section.title : void 0;
598
688
  if (section.kind === "records") {
599
- if (section.title && !section.page) lines.push(clean(section.title));
689
+ title2(named ?? (section.page ? section.key : void 0), section.summary);
600
690
  const declared = new Set(section.columns.map((column) => column.key));
601
- const extra = section.key === collection ? [.../* @__PURE__ */ new Set([...options.fields ?? [], ...requiredFields])].filter((key) => !declared.has(key)).map((key) => ({ key, label: key })) : [];
602
- const columns2 = [...section.columns, ...extra].filter(
691
+ const extra = section.key === collection ? [.../* @__PURE__ */ new Set([...options.fields ?? [], ...options.fields ? requiredFields : []])].filter((key) => !declared.has(key)).map((key) => ({ key, label: key })) : [];
692
+ const loose = new Set(extra.map((column) => column.key));
693
+ const columns = [...section.columns, ...extra].filter(
603
694
  (column) => section.key !== collection || selected(column.key, column.required)
604
695
  );
605
- const rows = section.rows.map(
606
- (row, i) => columns2.map(
607
- (column) => value3(
608
- row[column.key] === void 0 ? "" : row[column.key],
609
- `data.${section.key}.${i}.${column.key}`
610
- )
611
- )
612
- );
613
696
  if (options.format === "table") {
614
- const labels = columns2.map((column) => column.label);
697
+ const labels = columns.map((column) => column.label ?? column.key);
698
+ const rows2 = section.rows.map(
699
+ (row, i) => columns.map(
700
+ (column) => absent(row[column.key], column.default) ? "" : value3(
701
+ row[column.key],
702
+ `data.${section.key}.${i}.${column.key}`,
703
+ loose.has(column.key)
704
+ )
705
+ )
706
+ );
615
707
  const widths = labels.map(
616
- (label, i) => Math.max(label.length, ...rows.map((row) => row[i]?.length ?? 0))
708
+ (label, i) => Math.max(label.length, ...rows2.map((row) => row[i]?.length ?? 0))
617
709
  );
618
710
  lines.push(
619
- ...[labels, ...rows].map(
620
- (row) => row.map((cell, i) => cell.padEnd(widths[i] ?? cell.length)).join(" ").trimEnd()
711
+ ...[labels, ...rows2].map(
712
+ (row) => row.map((text2, i) => text2.padEnd(widths[i] ?? text2.length)).join(" ").trimEnd()
621
713
  )
622
714
  );
623
715
  } else
624
- for (const row of rows)
716
+ for (const [i, row] of section.rows.entries())
625
717
  lines.push(
626
- ` ${columns2.flatMap((column, i) => {
627
- const cell = row[i];
628
- return cell === void 0 || cell === "" ? [] : [`${clean(column.label)}=${cell}`];
629
- }).join(" \xB7 ")}`
718
+ ` ${columns.map(
719
+ (column) => cell(
720
+ column.label,
721
+ column.key,
722
+ row[column.key],
723
+ `data.${section.key}.${i}.${column.key}`,
724
+ column.positional,
725
+ column.default,
726
+ loose.has(column.key)
727
+ )
728
+ ).filter((text2) => text2 !== null).join(" \xB7 ")}`
630
729
  );
631
- if (!rows.length && !section.page) lines.push("No records.");
730
+ if (!section.rows.length && !named && !section.page) lines.push("No records.");
731
+ pageLine(section.page);
632
732
  } else if (section.kind === "messages") {
633
- if (section.title && !section.page) lines.push(`${clean(section.title)}:`);
634
- const metadata = (items) => items.map((item) => `${clean(item.label)}=${clean(scalar(item.binding.value))}`).join(" \xB7 ");
635
- for (const group of section.groups) {
636
- lines.push(` ${metadata(group.facts)}`);
637
- for (const message of group.messages) {
638
- lines.push(` ${metadata(message.facts)}`);
733
+ title2(named, section.summary);
734
+ for (const [g, group] of section.groups.entries()) {
735
+ lines.push(` ${metadata(group.facts, `data.${section.key}.${g}`)}`);
736
+ for (const [m, message] of group.messages.entries()) {
737
+ lines.push(` ${metadata(message.facts, `data.${section.key}.${g}.messages.${m}`)}`);
639
738
  lines.push(
640
739
  ...message.text.split("\n").map(
641
- (line) => ` ${line.replaceAll(new RegExp("\\p{Cc}", "gu"), (char) => char === " " ? char : "")}`
740
+ (line2) => ` ${line2.replaceAll(new RegExp("\\p{Cc}", "gu"), (char) => char === " " ? char : "")}`
642
741
  )
643
742
  );
644
743
  }
645
744
  }
646
- if (!section.groups.length && !section.page) lines.push("No messages.");
745
+ if (!section.groups.length && !named && !section.page) lines.push("No messages.");
746
+ pageLine(section.page);
647
747
  } else if (section.kind === "diagnostics") {
648
748
  const limit = options.detail ? section.items.length : section.preview ?? 3;
649
749
  for (const item of section.items.slice(0, limit))
@@ -657,34 +757,50 @@ function renderText(view, wire, options, collection, requiredFields = []) {
657
757
  count: section.items.length - limit
658
758
  });
659
759
  } else {
660
- if (section.title && !section.page) lines.push(`${clean(section.title)}:`);
760
+ title2(named && `${named}:`, section.summary);
661
761
  lines.push(
662
762
  ...section.text.trimEnd().split("\n").map(
663
- (line) => ` ${line.replaceAll(new RegExp("\\p{Cc}", "gu"), (char) => char === " " || char === "\n" ? char : "")}`
763
+ (line2) => ` ${line2.replaceAll(new RegExp("\\p{Cc}", "gu"), (char) => char === " " || char === "\n" ? char : "")}`
664
764
  )
665
765
  );
666
766
  }
667
767
  }
668
- if (wire.page) lines.push(`Scope: ${scopeText(wire.page)}`);
669
- lines.push(...effectLines(wire.effects ?? []));
768
+ if (wire.page) pageLine(wire.page);
769
+ if (code || (wire.effects ?? []).some((effect) => effect.state !== "confirmed"))
770
+ lines.push(...effectLines(wire.effects ?? []));
670
771
  for (const warning of wire.warnings ?? []) lines.push(`Warning: ${clean(warning.message)}`);
671
- if (omissions.length)
672
- lines.push(
673
- `Omitted: ${omissions.map((item) => `${item.count === void 0 ? "" : `${item.count} `}${item.path} (${item.reason})`).join(", ")}`
674
- );
675
- for (const action of [...view.actions ?? wire.next ?? [], ...errorActions])
676
- lines.push(`${clean(action.label ?? "Next")}: ${actionText(action)}`);
772
+ const actions = [...view.actions ?? wire.next ?? [], ...errorActions];
773
+ const used = /* @__PURE__ */ new Set();
774
+ const details = actions.find((action) => action.label === "Details");
775
+ for (const omission of omissions) {
776
+ if (omission.count === void 0) continue;
777
+ const more = `${omission.count} more ${lastSegment(omission.path)}`;
778
+ if (details && !used.has(details)) {
779
+ used.add(details);
780
+ lines.push(`${more} \xB7 Details: ${actionText(details)}`);
781
+ } else lines.push(more);
782
+ }
783
+ if (!details && !options.detail && omissions.some((item) => item.reason === "preview" && item.count === void 0))
784
+ lines.push("Some values are clipped; add --detail to see them in full.");
785
+ for (const action of actions)
786
+ if (!used.has(action))
787
+ lines.push(
788
+ `${clean(action.label ?? "Next")}: ${action.reason ? `${clean(action.reason)} \xB7 ` : ""}${actionText(action)}`
789
+ );
677
790
  return `${lines.join("\n")}
678
791
  `;
679
792
  }
680
- function renderFailure(wire, options) {
793
+ function renderFailure(wire, options, code = 1) {
681
794
  return renderText(
682
- { heading: wire.command, status: wire.outcome, facts: [], sections: [] },
795
+ { heading: commandName(wire.command), status: "", facts: [], sections: [] },
683
796
  wire,
684
797
  {
685
798
  ...options,
686
799
  fields: void 0
687
- }
800
+ },
801
+ void 0,
802
+ [],
803
+ code
688
804
  );
689
805
  }
690
806
 
@@ -725,8 +841,8 @@ async function runCommand(definition, input, options, writer, signal) {
725
841
  return `${JSON.stringify({ schema: "ryugi.cli/v1", ...value3, seq: seq++, kind })}
726
842
  `;
727
843
  }
728
- function enqueue(channel, text) {
729
- queue = queue.then(() => writer.write(channel, text)).catch((err) => {
844
+ function enqueue(channel, text2) {
845
+ queue = queue.then(() => writer.write(channel, text2)).catch((err) => {
730
846
  queueError = err;
731
847
  abort.abort(err);
732
848
  });
@@ -736,14 +852,14 @@ async function runCommand(definition, input, options, writer, signal) {
736
852
  if (queueError) throw queueError;
737
853
  }
738
854
  const maxBytes = 8 * 1024 * 1024;
739
- async function write(channel, text) {
740
- if ((typeof text === "string" ? new TextEncoder().encode(text).byteLength : text.byteLength) > maxBytes)
855
+ async function write(channel, text2) {
856
+ if ((typeof text2 === "string" ? new TextEncoder().encode(text2).byteLength : text2.byteLength) > maxBytes)
741
857
  throw new OutputError({
742
858
  code: "E_RESULT_TOO_LARGE",
743
859
  message: "Result exceeds 8 MiB; read one object or save it to a file."
744
860
  });
745
861
  await drain();
746
- await writer.write(channel, text);
862
+ await writer.write(channel, text2);
747
863
  }
748
864
  async function output(value3, kind = "result") {
749
865
  const full = {
@@ -759,9 +875,9 @@ async function runCommand(definition, input, options, writer, signal) {
759
875
  const key = definition.output.collection;
760
876
  if (key && kind === "result") {
761
877
  const record = wire.data;
762
- const rows = record[key];
763
- if (Array.isArray(rows)) {
764
- for (const item of rows)
878
+ const rows2 = record[key];
879
+ if (Array.isArray(rows2)) {
880
+ for (const item of rows2)
765
881
  await write("stdout", eventLine("item", { collection: key, data: item }));
766
882
  const data = { ...record };
767
883
  delete data[key];
@@ -793,7 +909,8 @@ async function runCommand(definition, input, options, writer, signal) {
793
909
  wire,
794
910
  options,
795
911
  definition.output.collection,
796
- definition.output.requiredFields
912
+ definition.output.requiredFields,
913
+ completionState(full.completion).code
797
914
  )
798
915
  );
799
916
  return completionState(full.completion).code;
@@ -877,7 +994,11 @@ async function runCommand(definition, input, options, writer, signal) {
877
994
  );
878
995
  else if (options.format === "raw" && ctx.payloads.raw !== void 0 && error.code !== "E_RESULT_TOO_LARGE" && Buffer.byteLength(ctx.payloads.raw) <= maxBytes)
879
996
  await writer.write("stdout", ctx.payloads.raw);
880
- else await writer.write("stderr", renderFailure(wire, options));
997
+ else
998
+ await writer.write(
999
+ "stderr",
1000
+ renderFailure(wire, options, completionState(failure.completion).code)
1001
+ );
881
1002
  if (options.verbose && err instanceof Error && err.stack)
882
1003
  await writer.write("stderr", `${err.stack}
883
1004
  `);
@@ -952,8 +1073,8 @@ function commandLine(pid) {
952
1073
  if (direct.status === 0 && direct.stdout.trim()) return direct.stdout.trim().split(/\s+/);
953
1074
  const full = (0, import_node_child_process.spawnSync)("ps", ["-f", "-p", String(pid)], { encoding: "utf8" });
954
1075
  if (full.status !== 0) return null;
955
- const line = full.stdout.split("\n").slice(1).join(" ").trim();
956
- return line ? line.split(/\s+/) : null;
1076
+ const line2 = full.stdout.split("\n").slice(1).join(" ").trim();
1077
+ return line2 ? line2.split(/\s+/) : null;
957
1078
  }
958
1079
  var isNodeImage = (tasklistLine) => /^"node\.exe"/i.test(tasklistLine.trim());
959
1080
  function windowsNodeAt(pid) {
@@ -963,10 +1084,10 @@ function windowsNodeAt(pid) {
963
1084
  });
964
1085
  return result2.status === 0 && isNodeImage(result2.stdout);
965
1086
  }
966
- function hasServerId(pid, id) {
1087
+ function hasServerId(pid, id2) {
967
1088
  if (!alive(pid)) return false;
968
1089
  if (process.platform === "win32") return windowsNodeAt(pid);
969
- return commandLine(pid)?.includes(`--artifacts-server-id=${id}`) ?? false;
1090
+ return commandLine(pid)?.includes(`--artifacts-server-id=${id2}`) ?? false;
970
1091
  }
971
1092
  async function waitForExit(pid, ms) {
972
1093
  for (let waited = 0; waited < ms && alive(pid); waited += 100) await sleep(100);
@@ -1046,24 +1167,263 @@ function serverClient(projectDir) {
1046
1167
  };
1047
1168
  }
1048
1169
 
1170
+ // src/cli/output/configuration.ts
1171
+ var import_node_fs8 = __toESM(require("node:fs"), 1);
1172
+ var import_node_os3 = __toESM(require("node:os"), 1);
1173
+ var import_node_path7 = __toESM(require("node:path"), 1);
1174
+
1175
+ // src/server/config.ts
1176
+ var import_node_fs5 = __toESM(require("node:fs"), 1);
1177
+ var import_node_path4 = __toESM(require("node:path"), 1);
1178
+
1179
+ // src/shared/cloud.ts
1180
+ var MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
1181
+ var isVisibility = (value3) => value3 === "private" || value3 === "public";
1182
+
1183
+ // src/server/state.ts
1184
+ var import_node_fs4 = __toESM(require("node:fs"), 1);
1185
+ var import_node_path3 = __toESM(require("node:path"), 1);
1186
+
1187
+ // src/shared/types.ts
1188
+ var ARTIFACT_TYPES = ["artboard", "document", "html", "whiteboard"];
1189
+
1190
+ // src/shared/validate.ts
1191
+ var BOARD_NUMBERS = ["x", "y", "w", "h"];
1192
+ var BOARD_REQUIRED = ["file", "page", ...BOARD_NUMBERS];
1193
+ var MEDIA_REQUIRED = ["src", "page", ...BOARD_NUMBERS];
1194
+
1195
+ // src/server/state.ts
1196
+ function writeJsonAtomic(file, value3) {
1197
+ import_node_fs4.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
1198
+ const tmp = `${file}.${process.pid}.tmp`;
1199
+ import_node_fs4.default.writeFileSync(tmp, `${JSON.stringify(value3, null, 2)}
1200
+ `, { mode: 384 });
1201
+ import_node_fs4.default.renameSync(tmp, file);
1202
+ }
1203
+
1204
+ // src/server/config.ts
1205
+ var defaults = () => ({ version: 1, artifacts: {} });
1206
+ function isCloudRecord(value3) {
1207
+ if (typeof value3 !== "object" || value3 === null) return false;
1208
+ const { id: id2, url, publishedAt, visibility } = value3;
1209
+ return typeof id2 === "string" && typeof url === "string" && (typeof publishedAt === "string" || publishedAt === null) && isVisibility(visibility);
1210
+ }
1211
+ function cloudRecord({ id: id2, url, publishedAt, visibility, fingerprint }) {
1212
+ return {
1213
+ id: id2,
1214
+ url,
1215
+ publishedAt,
1216
+ visibility,
1217
+ ...typeof fingerprint === "string" ? { fingerprint } : {}
1218
+ };
1219
+ }
1220
+ function normalize2(raw) {
1221
+ const config = defaults();
1222
+ if (typeof raw !== "object" || raw === null) return config;
1223
+ const output = raw.output;
1224
+ if (output && typeof output === "object" && "format" in output && ["brief", "json", "jsonl"].includes(String(output.format)))
1225
+ config.output = { format: output.format };
1226
+ const { autoPublish } = raw;
1227
+ if (typeof autoPublish === "boolean") config.autoPublish = autoPublish;
1228
+ const artifacts = raw.artifacts;
1229
+ if (typeof artifacts !== "object" || artifacts === null) return config;
1230
+ for (const [key, value3] of Object.entries(artifacts)) {
1231
+ if (typeof value3 !== "object" || value3 === null) continue;
1232
+ const { visibility, cloud, autoPublish: autoPublish2 } = value3;
1233
+ if (visibility !== void 0 && !isVisibility(visibility)) continue;
1234
+ const entry = {};
1235
+ if (visibility) entry.visibility = visibility;
1236
+ if (isCloudRecord(cloud)) entry.cloud = cloudRecord(cloud);
1237
+ if (autoPublish2 === false) entry.autoPublish = false;
1238
+ config.artifacts[key] = entry;
1239
+ }
1240
+ return config;
1241
+ }
1242
+ function readConfig(file) {
1243
+ let text2;
1244
+ try {
1245
+ text2 = import_node_fs5.default.readFileSync(file, "utf8");
1246
+ } catch {
1247
+ return { config: defaults() };
1248
+ }
1249
+ try {
1250
+ return { config: normalize2(JSON.parse(text2)) };
1251
+ } catch (err) {
1252
+ return {
1253
+ config: defaults(),
1254
+ warning: `config.json is not valid JSON: ${err.message}`
1255
+ };
1256
+ }
1257
+ }
1258
+ var IGNORED = ["config.json", "artifacts/*", "!artifacts/document/", "artifacts/document/*/.artifact-id"];
1259
+ function ensureGitignore(dir) {
1260
+ const file = import_node_path4.default.join(dir, ".gitignore");
1261
+ let current = "";
1262
+ try {
1263
+ current = import_node_fs5.default.readFileSync(file, "utf8");
1264
+ } catch {
1265
+ }
1266
+ const present = new Set(current.split(/\r?\n/).map((line2) => line2.trim()));
1267
+ const missing = IGNORED.filter((line2) => !present.has(line2));
1268
+ if (missing.length === 0) return;
1269
+ import_node_fs5.default.writeFileSync(file, `${current}${current === "" || current.endsWith("\n") ? "" : "\n"}${missing.join("\n")}
1270
+ `);
1271
+ }
1272
+ function ensureConfig(file) {
1273
+ import_node_fs5.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
1274
+ ensureGitignore(import_node_path4.default.dirname(file));
1275
+ if (!import_node_fs5.default.existsSync(file)) writeJsonAtomic(file, defaults());
1276
+ }
1277
+ function setProjectAutoPublish(file, value3) {
1278
+ const { config, warning } = readConfig(file);
1279
+ if (warning) throw new Error(warning);
1280
+ if (value3 === null) delete config.autoPublish;
1281
+ else config.autoPublish = value3;
1282
+ import_node_fs5.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
1283
+ writeJsonAtomic(file, config);
1284
+ }
1285
+
1286
+ // src/server/credentials.ts
1287
+ var import_node_fs6 = __toESM(require("node:fs"), 1);
1288
+ var import_node_path5 = __toESM(require("node:path"), 1);
1289
+ function credentialsFile(env, platform, home) {
1290
+ if (platform === "win32" && env.APPDATA)
1291
+ return import_node_path5.default.join(env.APPDATA, "ryugi", "credentials.json");
1292
+ return import_node_path5.default.join(env.XDG_CONFIG_HOME || import_node_path5.default.join(home, ".config"), "ryugi", "credentials.json");
1293
+ }
1294
+ function readCredentials(file, origin) {
1295
+ let raw;
1296
+ try {
1297
+ raw = JSON.parse(import_node_fs6.default.readFileSync(file, "utf8"));
1298
+ } catch {
1299
+ return null;
1300
+ }
1301
+ if (typeof raw !== "object" || raw === null) return null;
1302
+ const { origin: savedOrigin, token, login } = raw;
1303
+ if (typeof savedOrigin !== "string" || typeof token !== "string" || typeof login !== "string")
1304
+ return null;
1305
+ if (savedOrigin !== origin) return null;
1306
+ return { origin: savedOrigin, token, login };
1307
+ }
1308
+
1309
+ // src/server/settings.ts
1310
+ var import_node_fs7 = __toESM(require("node:fs"), 1);
1311
+ var import_node_path6 = __toESM(require("node:path"), 1);
1312
+ var settingsFileFor = (credentialsFile2) => import_node_path6.default.join(import_node_path6.default.dirname(credentialsFile2), "config.json");
1313
+ function readSettings(file) {
1314
+ try {
1315
+ const raw = JSON.parse(import_node_fs7.default.readFileSync(file, "utf8"));
1316
+ return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
1317
+ } catch {
1318
+ return {};
1319
+ }
1320
+ }
1321
+ function readGlobalAutoPublish(file) {
1322
+ const value3 = readSettings(file).autoPublish;
1323
+ return typeof value3 === "boolean" ? value3 : void 0;
1324
+ }
1325
+ function writeGlobalAutoPublish(file, value3) {
1326
+ import_node_fs7.default.mkdirSync(import_node_path6.default.dirname(file), { recursive: true, mode: 448 });
1327
+ writeJsonAtomic(file, { ...readSettings(file), autoPublish: value3 });
1328
+ }
1329
+
1330
+ // src/cli/output/configuration.ts
1331
+ var PERSISTED_FORMATS = ["brief", "json", "jsonl"];
1332
+ function outputFiles(projectDir) {
1333
+ return {
1334
+ global: settingsFileFor(credentialsFile(process.env, process.platform, import_node_os3.default.homedir())),
1335
+ project: import_node_path7.default.join(import_node_path7.default.dirname(projectPaths(projectDir).root), "config.json")
1336
+ };
1337
+ }
1338
+ function read(file) {
1339
+ try {
1340
+ const raw = JSON.parse(import_node_fs8.default.readFileSync(file, "utf8"));
1341
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Expected an object");
1342
+ return raw;
1343
+ } catch (error) {
1344
+ if (error.code === "ENOENT") return {};
1345
+ throw new OutputError({
1346
+ code: "E_CONFIG_INVALID",
1347
+ message: `Cannot read ${file}: ${error.message}`
1348
+ });
1349
+ }
1350
+ }
1351
+ function outputValues(projectDir) {
1352
+ const files = outputFiles(projectDir);
1353
+ const get = (file) => {
1354
+ const output = read(file).output;
1355
+ if (output?.format !== void 0 && !PERSISTED_FORMATS.includes(output.format))
1356
+ throw new OutputError({
1357
+ code: "E_FORMAT_UNSUPPORTED",
1358
+ message: `Unsupported output format in ${file}: ${String(output.format)}`
1359
+ });
1360
+ return output && PERSISTED_FORMATS.includes(output.format) ? output.format : null;
1361
+ };
1362
+ const global = get(files.global), project2 = get(files.project);
1363
+ return {
1364
+ global,
1365
+ project: project2,
1366
+ effective: project2 ?? global ?? "brief",
1367
+ source: project2 ? "project" : global ? "global" : "default"
1368
+ };
1369
+ }
1370
+ function writeOutputFormat(projectDir, isProject, value3) {
1371
+ if (value3 === "inherit" && !isProject || value3 !== "inherit" && !PERSISTED_FORMATS.includes(value3))
1372
+ throw new OutputError({
1373
+ code: "E_ARGUMENT_INVALID",
1374
+ message: "--output-format takes brief, json, jsonl, or inherit with --project."
1375
+ });
1376
+ const files = outputFiles(projectDir), file = isProject ? files.project : files.global, raw = read(file);
1377
+ if (value3 === "inherit") delete raw.output;
1378
+ else raw.output = { format: value3 };
1379
+ if (isProject) ensureConfig(file);
1380
+ else import_node_fs8.default.mkdirSync(import_node_path7.default.dirname(file), { recursive: true, mode: 448 });
1381
+ writeJsonAtomic(file, raw);
1382
+ }
1383
+ function configuredEnvironment(argv, specs, env = process.env) {
1384
+ if (env.RYUGI_ARTIFACTS_FORMAT !== void 0) return env;
1385
+ const values = scanOptions(argv, specs);
1386
+ if (["format", "json", "quiet", "source"].some((key) => values[key] !== void 0)) return env;
1387
+ let project2 = typeof values["project-dir"] === "string" ? values["project-dir"] : process.cwd();
1388
+ if (!values["project-dir"] && argv[0] && !argv[0].startsWith("-")) {
1389
+ try {
1390
+ if (import_node_fs8.default.statSync(argv[0]).isDirectory()) project2 = argv[0];
1391
+ } catch {
1392
+ }
1393
+ }
1394
+ const config = outputValues(project2);
1395
+ return { ...env, RYUGI_ARTIFACTS_FORMAT: config.effective };
1396
+ }
1397
+
1049
1398
  // src/cli/semantic/actions.ts
1399
+ var words = (command) => [...command.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/g)].map((m) => m[1] ?? m[2] ?? m[3]);
1400
+ function prefix() {
1401
+ const command = process.env.RYUGI_ARTIFACTS_COMMAND?.trim();
1402
+ return command ? words(command) : [process.execPath, process.argv[1]];
1403
+ }
1404
+ function configuredFormat(args) {
1405
+ if (process.env.RYUGI_ARTIFACTS_FORMAT) return process.env.RYUGI_ARTIFACTS_FORMAT;
1406
+ const at = args.indexOf("--project-dir");
1407
+ try {
1408
+ return outputValues(at >= 0 && args[at + 1] ? args[at + 1] : process.cwd()).effective;
1409
+ } catch {
1410
+ return "brief";
1411
+ }
1412
+ }
1050
1413
  function commandAction(args, label = "Details") {
1051
1414
  const explicit = args.some((arg) => ["--format", "--source", "--quiet"].includes(arg));
1052
- return {
1053
- kind: "command",
1054
- argv: [process.execPath, process.argv[1], ...args, ...explicit ? [] : ["--format", "brief"]],
1055
- label
1056
- };
1415
+ const format = explicit || configuredFormat(args) === "brief" ? [] : ["--format", "brief"];
1416
+ return { kind: "command", argv: [...prefix(), ...args, ...format], label };
1057
1417
  }
1058
1418
 
1059
1419
  // src/cli/semantic/files.ts
1060
- var import_node_fs4 = __toESM(require("node:fs"), 1);
1061
- var import_node_path3 = __toESM(require("node:path"), 1);
1420
+ var import_node_fs9 = __toESM(require("node:fs"), 1);
1421
+ var import_node_path8 = __toESM(require("node:path"), 1);
1062
1422
  function saveExclusive(target, value3) {
1063
- const absolute = import_node_path3.default.resolve(target);
1423
+ const absolute = import_node_path8.default.resolve(target);
1064
1424
  let fd;
1065
1425
  try {
1066
- fd = import_node_fs4.default.openSync(absolute, "wx", 384);
1426
+ fd = import_node_fs9.default.openSync(absolute, "wx", 384);
1067
1427
  } catch (error) {
1068
1428
  throw new OutputError({
1069
1429
  code: error.code === "EEXIST" ? "E_FILE_EXISTS" : "E_FILE_WRITE_FAILED",
@@ -1071,33 +1431,281 @@ function saveExclusive(target, value3) {
1071
1431
  });
1072
1432
  }
1073
1433
  try {
1074
- import_node_fs4.default.writeFileSync(fd, value3);
1434
+ import_node_fs9.default.writeFileSync(fd, value3);
1075
1435
  } catch (error) {
1076
- import_node_fs4.default.closeSync(fd);
1077
- import_node_fs4.default.unlinkSync(absolute);
1436
+ import_node_fs9.default.closeSync(fd);
1437
+ import_node_fs9.default.unlinkSync(absolute);
1078
1438
  throw new OutputError({
1079
1439
  code: "E_FILE_WRITE_FAILED",
1080
1440
  message: `Could not save ${absolute}: ${error.message}`
1081
1441
  });
1082
1442
  }
1083
- import_node_fs4.default.closeSync(fd);
1443
+ import_node_fs9.default.closeSync(fd);
1084
1444
  return {
1085
1445
  file: absolute,
1086
1446
  bytes: typeof value3 === "string" ? Buffer.byteLength(value3) : value3.byteLength
1087
1447
  };
1088
1448
  }
1089
1449
 
1090
- // src/cli/semantic/review.ts
1091
- var value = (input, key) => typeof input.values[key] === "string" ? input.values[key] : void 0;
1092
- var formats = ["brief", "table", "json", "jsonl", "raw"];
1093
- function artifactOf(input, documentOnly = false) {
1094
- const artifact = value(input, "artifact");
1095
- if (!artifact || !(documentOnly ? /^document\/[a-z0-9][a-z0-9-]*$/ : /^(document|artboard|whiteboard)\/[a-z0-9][a-z0-9-]*$/).test(artifact))
1096
- throw new OutputError({
1097
- code: "E_ARGUMENT_INVALID",
1098
- message: documentOnly ? "--artifact must be document/<id>." : "--artifact must be document/<id>, artboard/<id> or whiteboard/<id>."
1099
- });
1100
- return artifact;
1450
+ // src/cli/semantic/review-view.ts
1451
+ var text = (value3) => typeof value3 === "string" ? value3 : "";
1452
+ var flat = (value3) => value3.replaceAll(/\s*\n\s*/g, " / ").trim();
1453
+ var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
1454
+ var title = (name) => name[0].toUpperCase() + name.slice(1);
1455
+ var counts = (summary, skip = []) => Object.entries(summary ?? {}).filter(
1456
+ ([key, n]) => key !== "waitingOnAgent" && !skip.includes(key) && typeof n === "number" && n > 0
1457
+ ).map(([key, n]) => `${n} ${key}`).join(", ");
1458
+ var bracket = (text2) => text2 ? ` (${text2})` : "";
1459
+ var ACTION = {
1460
+ asks: { word: "to apply", none: "nothing to apply", listed: ["submitted"] },
1461
+ groups: { word: "to act on", none: "nothing to act on", listed: [] },
1462
+ rounds: { word: "open or submitted", none: "nothing open", listed: ["open", "submitted"] }
1463
+ };
1464
+ function sectionSummary(section, report2, page, narrowed = false) {
1465
+ const total = page?.total ?? report2.items.length;
1466
+ const summary = report2.summary;
1467
+ if (narrowed) {
1468
+ const head2 = `${total} matched`;
1469
+ return { head: head2, full: `${head2}${bracket(counts(summary))}`, count: total };
1470
+ }
1471
+ if (section === "threads") {
1472
+ const waiting = Number(summary?.waitingOnAgent ?? 0);
1473
+ const head2 = total ? `${total} unread${waiting ? `, ${waiting} waiting on you` : ""}` : "nothing unread";
1474
+ const resolved = Number(summary?.resolved ?? 0);
1475
+ return {
1476
+ head: head2,
1477
+ full: `${head2}${resolved ? ` (${resolved} resolved)` : ""}`,
1478
+ count: total
1479
+ };
1480
+ }
1481
+ const words2 = ACTION[section];
1482
+ const head = total ? `${total} ${words2.word}` : words2.none;
1483
+ const others = section === "groups" ? Object.entries(summary ?? {}).reduce(
1484
+ (sum, [key, n]) => key === "waitingOnAgent" || typeof n !== "number" ? sum : sum + n,
1485
+ 0
1486
+ ) - total : 0;
1487
+ const rest = section === "groups" ? others > 0 ? plural(others, "other") : "" : counts(summary, words2.listed);
1488
+ return { head, full: `${head}${bracket(rest)}`, count: total };
1489
+ }
1490
+ function location(anchor) {
1491
+ if (!anchor || typeof anchor !== "object") return "";
1492
+ const a = anchor;
1493
+ if (typeof a.blockId === "string") {
1494
+ const blocks = Array.isArray(a.blockIds) && a.blockIds.length > 1 ? a.blockIds : [a.blockId];
1495
+ return `${blocks.length > 1 ? "blocks" : "block"} ${blocks.join(", ")}${text(a.quote) ? ` "${flat(text(a.quote))}"` : ""}`;
1496
+ }
1497
+ if (Array.isArray(a.elements) && a.elements.length) return `element ${a.elements.join(", ")}`;
1498
+ const page = text(a.page);
1499
+ if (typeof a.board === "string") return `${page ? `page ${page} ` : ""}board ${a.board}`;
1500
+ const point = typeof a.x === "number" && typeof a.y === "number" ? ` ${Math.round(a.x)},${Math.round(a.y)}` : "";
1501
+ return `page ${page || "main"}${point}`;
1502
+ }
1503
+ var id = { key: "id", positional: true, required: true };
1504
+ var line = { key: "line", positional: true, required: true };
1505
+ var tail = { key: "tail", positional: true, required: true };
1506
+ var picked = (item) => item.choice ? ` \u2192 ${item.choice}${item.byRecommendation ? " (recommended)" : ""}` : "";
1507
+ function rows(section, items) {
1508
+ if (section === "asks")
1509
+ return {
1510
+ columns: [
1511
+ id,
1512
+ line,
1513
+ { key: "note", label: "note" },
1514
+ { key: "staleChoice", label: "stale choice" }
1515
+ ],
1516
+ rows: items.map((item) => ({
1517
+ ...item,
1518
+ line: `${text(item.state)}${item.choice ? picked(item) : text(item.text) ? ` \xB7 "${flat(text(item.text))}"` : "choice" in item || "text" in item ? " \xB7 no answer" : ""}`,
1519
+ note: text(item.note) && `"${flat(text(item.note))}"`
1520
+ }))
1521
+ };
1522
+ if (section === "threads")
1523
+ return {
1524
+ columns: [
1525
+ id,
1526
+ { key: "status", positional: true, required: true },
1527
+ { key: "waiting", positional: true },
1528
+ { key: "where", positional: true },
1529
+ { key: "stale", label: "stale" },
1530
+ { key: "orphan", label: "orphan" },
1531
+ { key: "draft", label: "draft" },
1532
+ { key: "messageCount", positional: true },
1533
+ { key: "preview", positional: true }
1534
+ ],
1535
+ rows: items.map((item) => ({
1536
+ ...threadFacts(item),
1537
+ messageCount: typeof item.messageCount === "number" && item.messageCount ? plural(item.messageCount, "message") : "",
1538
+ preview: text(item.preview) && `"${flat(text(item.preview))}"`
1539
+ }))
1540
+ };
1541
+ if (section === "groups")
1542
+ return {
1543
+ columns: [
1544
+ id,
1545
+ { key: "kind", positional: true, required: true },
1546
+ line,
1547
+ { key: "evidenceUpdated", label: "evidence updated" },
1548
+ { key: "rejects", positional: true },
1549
+ { key: "threadCount", positional: true },
1550
+ tail
1551
+ ],
1552
+ rows: items.map((item) => ({
1553
+ ...item,
1554
+ line: item.kind === "verify" ? text(item.verdict) : `${item.round ? `round ${item.round}` : ""}${item.choice ? picked(item) : " \xB7 no pick"}`.replace(
1555
+ /^ · /,
1556
+ ""
1557
+ ),
1558
+ rejects: typeof item.rejectThreads === "number" && item.rejectThreads ? plural(item.rejectThreads, "reject thread") : "",
1559
+ threadCount: typeof item.threads === "number" && item.threads ? plural(item.threads, "thread") : "",
1560
+ tail: text(item.title)
1561
+ }))
1562
+ };
1563
+ return {
1564
+ columns: [
1565
+ id,
1566
+ { key: "state", positional: true, required: true },
1567
+ { key: "groupList", positional: true },
1568
+ tail
1569
+ ],
1570
+ rows: items.map((item) => ({
1571
+ ...item,
1572
+ groupList: Array.isArray(item.groups) && item.groups.length ? `groups ${item.groups.join(", ")}` : "",
1573
+ tail: text(item.title)
1574
+ }))
1575
+ };
1576
+ }
1577
+ function threadFacts(item) {
1578
+ return {
1579
+ id: item.id,
1580
+ status: item.status,
1581
+ waiting: item.waitingOn ? `waiting on ${item.waitingOn}` : "",
1582
+ where: location(
1583
+ item.anchor && typeof item.anchor === "object" && typeof item.page === "string" ? { page: item.page, ...item.anchor } : item.anchor
1584
+ ),
1585
+ stale: item.stale === true,
1586
+ orphan: item.orphan === true,
1587
+ draft: item.draft === true
1588
+ };
1589
+ }
1590
+ var positional = (key, value3) => ({
1591
+ label: key,
1592
+ binding: { key, value: value3 },
1593
+ positional: true
1594
+ });
1595
+ function threadMessages(items, key = "items") {
1596
+ return {
1597
+ kind: "messages",
1598
+ key,
1599
+ groups: items.map((item) => {
1600
+ const facts = threadFacts(item);
1601
+ return {
1602
+ facts: [
1603
+ positional("id", facts.id),
1604
+ positional("status", facts.status),
1605
+ positional("waiting", facts.waiting),
1606
+ positional("where", facts.where),
1607
+ { label: "stale", binding: { key: "stale", value: facts.stale } },
1608
+ { label: "orphan", binding: { key: "orphan", value: facts.orphan } },
1609
+ { label: "draft", binding: { key: "draft", value: facts.draft } }
1610
+ ],
1611
+ messages: (Array.isArray(item.messages) ? item.messages : []).map(
1612
+ (message) => ({
1613
+ facts: [
1614
+ positional(
1615
+ "message",
1616
+ [
1617
+ message.id,
1618
+ `${text(message.name) || text(message.author)}${message.author === "agent" ? " (agent)" : ""}`,
1619
+ text(message.at) && formatTime(text(message.at)),
1620
+ message.deleted ? "(deleted)" : "",
1621
+ text(message.editedAt) && `edited ${formatTime(text(message.editedAt))}`
1622
+ ].filter(Boolean).join(" ")
1623
+ )
1624
+ ],
1625
+ text: typeof message.text === "string" ? message.text : ""
1626
+ })
1627
+ )
1628
+ };
1629
+ })
1630
+ };
1631
+ }
1632
+ function sectionView(name, report2, page, inBundle, narrowed) {
1633
+ const summary = sectionSummary(name, report2, page, narrowed);
1634
+ const base = { key: inBundle ? name : "items", title: title(name), summary: summary.full };
1635
+ if (name === "threads" && report2.evidence === "complete")
1636
+ return { ...threadMessages(report2.items), ...base, ...inBundle ? { page } : {} };
1637
+ return {
1638
+ kind: "records",
1639
+ ...rows(name, report2.items),
1640
+ ...base,
1641
+ ...inBundle ? { page } : {}
1642
+ };
1643
+ }
1644
+ var COLLECTIONS = ["threads", "asks", "groups", "rounds"];
1645
+ function summaryView(data, actions) {
1646
+ const summary = data.summary ?? {};
1647
+ const command = (name) => actions.find((action) => action.label === title(name));
1648
+ const listed = COLLECTIONS.filter((name) => command(name));
1649
+ return {
1650
+ heading: "Comment list",
1651
+ status: "",
1652
+ subject: data.artifact,
1653
+ facts: [],
1654
+ sections: listed.map((name) => {
1655
+ const value3 = summary[name];
1656
+ const counted = name === "threads" ? sectionSummary(
1657
+ "threads",
1658
+ { items: [], summary: { waitingOnAgent: summary.waitingOnAgent } },
1659
+ { total: Number(value3 ?? 0) }
1660
+ ).head : counts(value3) || "none";
1661
+ return {
1662
+ kind: "records",
1663
+ key: name,
1664
+ title: title(name),
1665
+ summary: `${counted} \xB7 ${actionText(command(name))}`,
1666
+ columns: [],
1667
+ rows: []
1668
+ };
1669
+ }),
1670
+ actions: actions.filter((action) => !listed.some((name) => title(name) === action.label))
1671
+ };
1672
+ }
1673
+ function presentRead({ data, actions, coverage }, heading = "Comment list", narrowed = false) {
1674
+ if (data.section === "summary") return summaryView(data, actions);
1675
+ const page = coverage.page;
1676
+ return {
1677
+ heading,
1678
+ status: "",
1679
+ subject: data.artifact,
1680
+ summary: sectionSummary(data.section, data, page, narrowed).head,
1681
+ facts: [],
1682
+ sections: [sectionView(data.section, data, page, false, narrowed)]
1683
+ };
1684
+ }
1685
+ function presentBundle({ data }, narrowed = false) {
1686
+ const entries = Object.entries(data.sections);
1687
+ const heads = entries.map(([name, entry]) => sectionSummary(name, entry, entry.page, narrowed)).filter((item) => item.count > 0).map((item) => item.head);
1688
+ return {
1689
+ heading: "Comment list",
1690
+ status: "",
1691
+ subject: data.artifact,
1692
+ summary: heads.join(" \xB7 ") || "nothing new",
1693
+ facts: [],
1694
+ sections: entries.map(([name, entry]) => sectionView(name, entry, entry.page, true, narrowed))
1695
+ };
1696
+ }
1697
+
1698
+ // src/cli/semantic/review.ts
1699
+ var value = (input, key) => typeof input.values[key] === "string" ? input.values[key] : void 0;
1700
+ var formats = ["brief", "table", "json", "jsonl", "raw"];
1701
+ function artifactOf(input, documentOnly = false) {
1702
+ const artifact = value(input, "artifact");
1703
+ if (!artifact || !(documentOnly ? /^document\/[a-z0-9][a-z0-9-]*$/ : /^(document|artboard|whiteboard)\/[a-z0-9][a-z0-9-]*$/).test(artifact))
1704
+ throw new OutputError({
1705
+ code: "E_ARGUMENT_INVALID",
1706
+ message: documentOnly ? "--artifact must be document/<id>." : "--artifact must be document/<id>, artboard/<id> or whiteboard/<id>."
1707
+ });
1708
+ return artifact;
1101
1709
  }
1102
1710
  function query(input) {
1103
1711
  const limit = value(input, "limit");
@@ -1161,83 +1769,11 @@ function ack(input, artifact, endpoint) {
1161
1769
  const response = await call(input, ctx, artifact, endpoint, { op: "ack", receipt });
1162
1770
  return result({ artifact, receipt, status: response.status });
1163
1771
  },
1164
- present({ data }) {
1165
- return {
1166
- heading: "Read acknowledgement",
1167
- status: data.status,
1168
- subject: data.artifact,
1169
- facts: [fact("Receipt", field(data, "receipt"), true)],
1170
- sections: []
1171
- };
1172
- }
1772
+ present: presentAck(endpoint === "review" ? "comment" : "changes")
1173
1773
  },
1174
1774
  input
1175
1775
  );
1176
1776
  }
1177
- function columns(section, items) {
1178
- const keys = section === "threads" ? [
1179
- "id",
1180
- "status",
1181
- "waitingOn",
1182
- "anchor",
1183
- "preview",
1184
- "messageCount",
1185
- "stale",
1186
- ...items.some((item) => "messages" in item) ? ["messages"] : []
1187
- ] : section === "asks" ? ["id", "state", "choice", "text", "byRecommendation", "staleChoice", "note"] : section === "groups" ? [
1188
- "id",
1189
- "kind",
1190
- "title",
1191
- "round",
1192
- "verdict",
1193
- "choice",
1194
- "byRecommendation",
1195
- "by",
1196
- "evidenceUpdated",
1197
- "rejectThreads",
1198
- "threads"
1199
- ] : ["id", "state", "title", "groups", "submitted"];
1200
- return keys.map((key) => ({
1201
- key,
1202
- label: key,
1203
- required: ["id", "status", "state", "verdict"].includes(key)
1204
- }));
1205
- }
1206
- function presentRead({ data }) {
1207
- return {
1208
- heading: "Review",
1209
- status: data.evidence,
1210
- subject: data.artifact,
1211
- facts: [
1212
- fact("Section", field(data, "section"), true),
1213
- ...data.summary ? [fact("Summary", field(data, "summary"))] : []
1214
- ],
1215
- sections: data.section === "summary" ? [] : data.evidence === "complete" ? [messageSection(data.items)] : [
1216
- {
1217
- kind: "records",
1218
- key: "items",
1219
- rows: data.items,
1220
- columns: columns(data.section, data.items)
1221
- }
1222
- ]
1223
- };
1224
- }
1225
- function messageSection(items) {
1226
- const metadata = (item, excluded) => Object.entries(item).filter(([key]) => key !== excluded).map(([key, value3]) => ({ label: key, binding: { key, value: value3 } }));
1227
- return {
1228
- kind: "messages",
1229
- key: "items",
1230
- groups: items.map((item) => ({
1231
- facts: metadata(item, "messages"),
1232
- messages: (Array.isArray(item.messages) ? item.messages : []).map(
1233
- (message) => ({
1234
- facts: metadata(message, "text"),
1235
- text: typeof message.text === "string" ? message.text : JSON.stringify(message.text ?? null)
1236
- })
1237
- )
1238
- }))
1239
- };
1240
- }
1241
1777
  function bundledReview(input, artifact, operation) {
1242
1778
  if (operation !== "list")
1243
1779
  throw new OutputError({
@@ -1268,9 +1804,6 @@ function bundledReview(input, artifact, operation) {
1268
1804
  output: { formats, detail: true },
1269
1805
  async run(_input, ctx) {
1270
1806
  const json = await call(input, ctx, artifact, "review", { op: "list", sections, ...read2 });
1271
- const data = {};
1272
- const actions = [];
1273
- const omitted = [];
1274
1807
  for (const name of sections) {
1275
1808
  const raw = json.sections?.[name];
1276
1809
  if (!raw || !Array.isArray(raw.items) || typeof raw.snapshot !== "string" || !raw.page || typeof raw.page.scope !== "string" || typeof raw.page.hasMore !== "boolean" || !Number.isInteger(raw.page.returned) || !(raw.page.total === null || Number.isInteger(raw.page.total)))
@@ -1283,41 +1816,69 @@ function bundledReview(input, artifact, operation) {
1283
1816
  code: "E_PROTOCOL_MISMATCH",
1284
1817
  message: "The server returned inconsistent combined review coverage."
1285
1818
  });
1286
- const report2 = readResult(input, artifact, name, raw, read2);
1287
- const { artifact: _artifact, section: _section, ...entry } = report2.data;
1288
- data[name] = { ...entry, page: raw.page };
1289
- actions.push(
1290
- ...report2.actions.map((action) => ({
1291
- ...action,
1292
- label: `${name} ${action.label ?? "Next"}`
1293
- }))
1294
- );
1295
- if (entry.evidence === "preview" && name === "threads")
1296
- omitted.push({ path: "data.sections.threads.items.messages", reason: "summary" });
1297
1819
  }
1298
- return result({ artifact, sections: data }, void 0, { actions, coverage: { omitted } });
1820
+ return bundleResult(input, artifact, sections, json, read2);
1299
1821
  },
1300
- present({ data }) {
1301
- return {
1302
- heading: "Review",
1303
- status: read2.detail ? "complete" : "preview",
1304
- subject: artifact,
1305
- facts: [],
1306
- sections: Object.entries(data.sections).flatMap(
1307
- ([name, entry]) => presentRead(result({ ...entry, artifact, section: name })).sections.map((section) => ({
1308
- ...section,
1309
- key: name,
1310
- title: name[0].toUpperCase() + name.slice(1),
1311
- page: entry.page,
1312
- status: entry.evidence
1313
- }))
1314
- )
1315
- };
1316
- }
1822
+ present: (value3) => presentBundle(value3, read2.all)
1317
1823
  },
1318
1824
  input
1319
1825
  );
1320
1826
  }
1827
+ var presentAck = (kind) => ({ data }) => ({
1828
+ heading: kind === "comment" ? "Comment ack" : "Changes ack",
1829
+ status: data.status,
1830
+ subject: data.artifact,
1831
+ facts: [],
1832
+ sections: []
1833
+ });
1834
+ function presentChanges({ data }) {
1835
+ const count = data.blocks.length;
1836
+ return {
1837
+ heading: "Changes",
1838
+ status: data.saved ? "saved" : "",
1839
+ subject: data.artifact,
1840
+ summary: data.empty ? "no human edits" : data.diff === void 0 ? `${count} block${count === 1 ? "" : "s"} changed` : "complete diff",
1841
+ facts: data.saved ? [fact("File", field(data, "saved"), true)] : [],
1842
+ sections: data.empty || data.saved ? [] : data.diff === void 0 ? [
1843
+ {
1844
+ kind: "records",
1845
+ key: "blocks",
1846
+ rows: data.blocks.map((block) => ({
1847
+ id: block.id,
1848
+ size: [
1849
+ block.additions && `+${block.additions}`,
1850
+ block.deletions && `-${block.deletions}`
1851
+ ].filter(Boolean).join(" "),
1852
+ preview: block.preview && `"${block.preview.replaceAll(/\s*\n\s*/g, " / ")}"`
1853
+ })),
1854
+ columns: [
1855
+ { key: "id", positional: true, required: true },
1856
+ { key: "size", positional: true },
1857
+ { key: "preview", positional: true }
1858
+ ]
1859
+ }
1860
+ ] : [{ kind: "diff", key: "diff", text: data.diff }]
1861
+ };
1862
+ }
1863
+ function bundleResult(input, artifact, sections, json, read2) {
1864
+ const data = {};
1865
+ const actions = [];
1866
+ const omitted = [];
1867
+ for (const name of sections) {
1868
+ const raw = json.sections[name];
1869
+ const report2 = readResult(input, artifact, name, raw, read2);
1870
+ const { artifact: _artifact, section: _section, ...entry } = report2.data;
1871
+ data[name] = { ...entry, page: raw.page };
1872
+ actions.push(
1873
+ ...report2.actions.map(
1874
+ (action) => action.label === "More" ? { ...action, label: `More (${name})` } : action
1875
+ )
1876
+ );
1877
+ if (entry.evidence === "preview" && name === "threads")
1878
+ omitted.push({ path: "data.sections.threads.items.messages", reason: "summary" });
1879
+ }
1880
+ return result({ artifact, sections: data }, void 0, { actions, coverage: { omitted } });
1881
+ }
1321
1882
  function readResult(input, artifact, section, json, read2, thread) {
1322
1883
  const page = json.page;
1323
1884
  const data = {
@@ -1373,14 +1934,18 @@ function readResult(input, artifact, section, json, read2, thread) {
1373
1934
  ...read2.all ? ["--all"] : [],
1374
1935
  ...value(input, "limit") ? ["--limit", value(input, "limit")] : []
1375
1936
  ],
1376
- "Review"
1937
+ "Review all"
1938
+ )
1939
+ );
1940
+ const collections = artifact.startsWith("document/") ? ["threads", "asks"] : artifact.startsWith("artboard/") ? ["threads", "groups", "rounds"] : ["threads"];
1941
+ actions.unshift(
1942
+ ...collections.map(
1943
+ (name) => commandAction(
1944
+ ["comment", "list", ...args, "--section", name, ...name === "asks" ? ["--detail"] : []],
1945
+ name[0].toUpperCase() + name.slice(1)
1946
+ )
1377
1947
  )
1378
1948
  );
1379
- actions.push(commandAction(["comment", "list", ...args, "--section", "threads"], "Threads"));
1380
- if (artifact.startsWith("document/"))
1381
- actions.push(
1382
- commandAction(["comment", "list", ...args, "--section", "asks", "--detail"], "Answers")
1383
- );
1384
1949
  } else if (section === "threads" && data.evidence === "preview" && data.items[0])
1385
1950
  actions.push(
1386
1951
  commandAction(
@@ -1397,6 +1962,16 @@ function readResult(input, artifact, section, json, read2, thread) {
1397
1962
  "Read"
1398
1963
  )
1399
1964
  );
1965
+ if (section === "asks" && data.items.some((item) => item.state === "submitted"))
1966
+ actions.unshift({
1967
+ ...commandAction(["comment", "note", ...args, "--ask", "<id>"], "Next"),
1968
+ reason: "apply each answer, then settle it"
1969
+ });
1970
+ if (section === "groups" && data.items.some((item) => item.kind === "choose" && item.choice || item.verdict === "reject"))
1971
+ actions.unshift({
1972
+ ...commandAction(["comment", "list", ...args, "--section", "threads", "--detail"], "Next"),
1973
+ reason: "read the threads on these groups too, then act on each"
1974
+ });
1400
1975
  return result(data, void 0, {
1401
1976
  actions,
1402
1977
  coverage: {
@@ -1455,7 +2030,11 @@ function reviewSemantic(input) {
1455
2030
  });
1456
2031
  return readResult(input, artifact, section, json, read2, thread);
1457
2032
  },
1458
- present: presentRead
2033
+ present: (report2) => presentRead(
2034
+ report2,
2035
+ operation === "read" ? "Comment read" : "Comment list",
2036
+ read2.all || !!value(input, "status") && !(section === "asks" && value(input, "status") === "submitted") || !!value(input, "choice-state")
2037
+ )
1459
2038
  },
1460
2039
  input
1461
2040
  );
@@ -1534,31 +2113,7 @@ function changesSemantic(input) {
1534
2113
  }
1535
2114
  });
1536
2115
  },
1537
- present({ data }) {
1538
- return {
1539
- heading: "Changes",
1540
- status: data.empty ? "unchanged" : data.saved ? "saved" : data.diff === void 0 ? "preview" : "complete",
1541
- subject: data.artifact,
1542
- facts: [
1543
- ...data.saved ? [fact("File", field(data, "saved"), true)] : [],
1544
- ...data.receipt ? [fact("Receipt", field(data, "receipt"), true)] : []
1545
- ],
1546
- sections: data.diff === void 0 ? [
1547
- {
1548
- kind: "records",
1549
- key: "blocks",
1550
- rows: data.blocks.map((block) => ({ ...block })),
1551
- columns: [
1552
- { key: "id", label: "ID", required: true },
1553
- { key: "kind", label: "Kind" },
1554
- { key: "additions", label: "+" },
1555
- { key: "deletions", label: "-" },
1556
- { key: "preview", label: "Preview" }
1557
- ]
1558
- }
1559
- ] : [{ kind: "diff", key: "diff", title: "Complete diff", text: data.diff }]
1560
- };
1561
- }
2116
+ present: presentChanges
1562
2117
  },
1563
2118
  input
1564
2119
  );
@@ -1630,15 +2185,15 @@ function analyzeCheck(artifact, evidence) {
1630
2185
  }));
1631
2186
  const pending = [...evidence.pending ?? []];
1632
2187
  if (evidence.merging) pending.push("merging");
1633
- for (const [id, state] of Object.entries(evidence.recordings ?? {})) {
2188
+ for (const [id2, state] of Object.entries(evidence.recordings ?? {})) {
1634
2189
  if (state.warning)
1635
2190
  issues.push({
1636
2191
  code: "W_RECORDING",
1637
2192
  severity: "warning",
1638
2193
  message: state.warning,
1639
- location: id
2194
+ location: id2
1640
2195
  });
1641
- if (state.status === "processing") pending.push(`recording:${id}`);
2196
+ if (state.status === "processing") pending.push(`recording:${id2}`);
1642
2197
  }
1643
2198
  const dynamic = Object.entries(evidence.dynamic ?? {});
1644
2199
  const required = evidence.evidence?.required ?? (artifact.startsWith("html/") || artifact.startsWith("artboard/") || dynamic.length > 0);
@@ -1732,26 +2287,28 @@ var checkCommand = {
1732
2287
  ];
1733
2288
  return value3;
1734
2289
  },
1735
- present({ data }) {
1736
- return {
1737
- heading: "Check",
1738
- status: data.status,
1739
- subject: data.artifact,
1740
- facts: [
1741
- fact("Static", field(data, "static")),
1742
- fact("Browser", field(data, "runtime")),
1743
- ...data.pending.length ? [fact("Pending", field(data, "pending"))] : []
1744
- ],
1745
- sections: [{ kind: "diagnostics", key: "issues", items: data.issues, preview: 3 }]
1746
- };
1747
- }
2290
+ present: presentCheck
1748
2291
  },
1749
2292
  input
1750
2293
  )
1751
2294
  };
2295
+ function presentCheck({ data }) {
2296
+ const count = data.issues.length;
2297
+ return {
2298
+ heading: "Check",
2299
+ status: data.status,
2300
+ subject: data.artifact,
2301
+ summary: count ? `${count} issue${count === 1 ? "" : "s"}` : "",
2302
+ facts: [
2303
+ ...data.runtime === "not_run" || data.runtime === "unknown" ? [fact("Browser", { key: "runtime", value: data.runtime.replaceAll("_", " ") })] : [],
2304
+ ...data.pending.length ? [fact("Pending", { key: "pending", value: data.pending.join(", ") })] : []
2305
+ ],
2306
+ sections: [{ kind: "diagnostics", key: "issues", items: data.issues, preview: 3 }]
2307
+ };
2308
+ }
1752
2309
 
1753
2310
  // src/cli/comment.ts
1754
- var import_node_fs5 = __toESM(require("node:fs"), 1);
2311
+ var import_node_fs10 = __toESM(require("node:fs"), 1);
1755
2312
 
1756
2313
  // src/cli/comment-asks.ts
1757
2314
  function askRequest(action, options, message, optionalText) {
@@ -1770,6 +2327,20 @@ function askRequest(action, options, message, optionalText) {
1770
2327
  }
1771
2328
 
1772
2329
  // src/cli/semantic/comment.ts
2330
+ function presentWrite({ data }) {
2331
+ const target = data.threadId ? `thread ${data.threadId}` : data.ask ? `ask ${data.ask}` : data.group ? `group ${data.group}${data.board ? ` \u2192 ${data.board}` : ""}` : data.round ? `round ${data.round}` : "";
2332
+ return {
2333
+ heading: `Comment ${data.operation}`,
2334
+ status: data.status,
2335
+ subject: data.artifact,
2336
+ summary: `${target}${data.operation === "answer" && data.choice ? ` \u2192 ${data.choice}` : ""}`,
2337
+ facts: [
2338
+ ...data.messageId ? [fact("Message", field(data, "messageId"))] : [],
2339
+ ...data.groups?.length ? [fact("Groups", { key: "groups", value: data.groups.join(", ") })] : []
2340
+ ],
2341
+ sections: []
2342
+ };
2343
+ }
1773
2344
  function commentOptions(input) {
1774
2345
  const options = {
1775
2346
  action: input.positionals[0],
@@ -1870,22 +2441,7 @@ function commentSemantic(input) {
1870
2441
  };
1871
2442
  return result(data, void 0, { effects: ctx.effects.snapshot() });
1872
2443
  },
1873
- present({ data }) {
1874
- return {
1875
- heading: data.operation === "pass" || data.operation === "reject" ? "Verdict" : "Review",
1876
- status: data.status,
1877
- subject: data.artifact,
1878
- facts: [
1879
- fact("Operation", field(data, "operation"), true),
1880
- fact("By", field(data, "by"), true),
1881
- ...data.threadId ? [fact("Thread", field(data, "threadId"), true)] : [],
1882
- ...data.messageId ? [fact("Message", field(data, "messageId"))] : [],
1883
- ...data.ask ? [fact("Question", field(data, "ask"), true)] : [],
1884
- ...data.group ? [fact("Group", field(data, "group"), true)] : []
1885
- ],
1886
- sections: []
1887
- };
1888
- }
2444
+ present: presentWrite
1889
2445
  },
1890
2446
  input
1891
2447
  );
@@ -1908,15 +2464,15 @@ var VERDICTS = /* @__PURE__ */ new Set(["pass", "reject"]);
1908
2464
  function readStdin() {
1909
2465
  if (process.stdin.isTTY) return "";
1910
2466
  try {
1911
- return import_node_fs5.default.readFileSync(0, "utf8");
2467
+ return import_node_fs10.default.readFileSync(0, "utf8");
1912
2468
  } catch {
1913
2469
  return "";
1914
2470
  }
1915
2471
  }
1916
2472
  function messageText(options) {
1917
- const text = (options.text ?? readStdin()).trim();
1918
- if (!text) throw new CliError("The message is empty: pass --text <text> or pipe it on stdin");
1919
- return text;
2473
+ const text2 = (options.text ?? readStdin()).trim();
2474
+ if (!text2) throw new CliError("The message is empty: pass --text <text> or pipe it on stdin");
2475
+ return text2;
1920
2476
  }
1921
2477
  function coordinate(value3, flag) {
1922
2478
  if (value3 === void 0) return void 0;
@@ -1937,12 +2493,12 @@ function requestOf(options, action, type) {
1937
2493
  throw new CliError(
1938
2494
  action === "pass" ? "comment pass needs --group <groupId>, or --all" : "comment reject needs --group <groupId>"
1939
2495
  );
1940
- const text = action === "reject" ? messageText(options) : options.text?.trim();
2496
+ const text2 = action === "reject" ? messageText(options) : options.text?.trim();
1941
2497
  return {
1942
2498
  op: "verdict",
1943
2499
  verdict: action,
1944
2500
  ...all ? { all: true } : { group: options.group },
1945
- ...text ? { text } : {}
2501
+ ...text2 ? { text: text2 } : {}
1946
2502
  };
1947
2503
  }
1948
2504
  if (action === "choose") {
@@ -1987,378 +2543,151 @@ function requestOf(options, action, type) {
1987
2543
  op: "add",
1988
2544
  blockId: options.block,
1989
2545
  ...options.quote ? { quote: options.quote } : {},
1990
- text: messageText(options)
1991
- };
1992
- }
1993
- if (!options.thread) throw new CliError(`comment ${action} needs --thread <threadId>`);
1994
- return action === "reply" ? { op: "reply", threadId: options.thread, text: messageText(options) } : { op: "resolve", threadId: options.thread };
1995
- }
1996
- function prepareComment(options) {
1997
- const action = COMMENT_ACTIONS.find((name) => name === options.action);
1998
- if (!action)
1999
- throw new CliError(
2000
- "comment needs an action: list, add, reply, resolve, choose, submit, answer, note, pass or reject"
2001
- );
2002
- if (!options.artifact || !ARTIFACT.test(options.artifact))
2003
- throw new CliError("--artifact must be document/<id>, artboard/<id> or whiteboard/<id>");
2004
- const [type, id] = options.artifact.split("/");
2005
- const request = requestOf(options, action, type);
2006
- return { action, type, id, request, artifact: options.artifact };
2007
- }
2008
- async function callComment(options, prepared, signal) {
2009
- const { type, id, request } = prepared;
2010
- const server = serverClient(options.projectDir);
2011
- const agent = detectAgent(process.env);
2012
- return server.call(
2013
- "POST",
2014
- `/api/agent/${type}/${id}/comments`,
2015
- {
2016
- ...request,
2017
- ...agent ? { agent } : {}
2018
- },
2019
- signal
2020
- );
2021
- }
2022
- var commentCommand = {
2023
- semantic: commentSemantic,
2024
- name: "comment",
2025
- summary: "Read and answer comments on a document, artboard or whiteboard (needs a running server)",
2026
- usage: [
2027
- "list --artifact <document|artboard|whiteboard>/<id> --sections all|<names> [--detail] [--all]",
2028
- "list --artifact <document|artboard|whiteboard>/<id> [--section threads|asks|groups|rounds] [--all]",
2029
- "read --artifact <document|artboard|whiteboard>/<id> --thread <threadId> [--snapshot <token>]",
2030
- "ack --artifact <document|artboard|whiteboard>/<id> --receipt <receipt>",
2031
- "add --artifact document/<id> --block <blockId> [--quote <text>] [--text <text>]",
2032
- "add --artifact artboard/<id> --board <boardId> [--x <px> --y <px>] [--text <text>]",
2033
- "add --artifact whiteboard/<id> (--element <elementId> | --x <px> --y <px> [--page <pageId>]) [--text <text>]",
2034
- "reply --artifact <document|artboard|whiteboard>/<id> --thread <threadId> [--text <text>]",
2035
- "resolve --artifact <document|artboard|whiteboard>/<id> --thread <threadId>",
2036
- "choose --artifact artboard/<id> --group <groupId> (--board <boardId> | --clear)",
2037
- "submit --artifact artboard/<id> --round <roundId> [--withdraw]",
2038
- "submit --artifact document/<id> [--withdraw]",
2039
- "answer --artifact document/<id> --ask <questionId> (--choice <value> | --text <answer> | --clear)",
2040
- "note --artifact document/<id> --ask <questionId> [--text <note>]",
2041
- "pass --artifact artboard/<id> (--group <groupId> | --all) [--text <text>]",
2042
- "reject --artifact artboard/<id> --group <groupId> [--text <text>]"
2043
- ],
2044
- details: "list returns a summary; select threads, asks, groups or rounds to read one collection. Previews do not consume evidence. read returns a complete thread and receipt; use ack after applying that evidence. asks, groups and rounds are independent state reads without receipts. Without --text, add, reply, note and reject read the message from stdin. answer records what your human partner said in the terminal; note settles a question once its answer is applied. pass and reject record a verdict on a verify group only when your human partner asks for it; the board shows the agent recorded it.",
2045
- options: {
2046
- "sections": {
2047
- type: "string",
2048
- value: "<all|threads,asks|threads,groups,rounds>",
2049
- help: "Read multiple bounded review collections in one request"
2050
- },
2051
- "section": {
2052
- type: "string",
2053
- value: "<threads|asks|groups|rounds>",
2054
- help: "Semantic list: select one review collection"
2055
- },
2056
- "limit": {
2057
- type: "string",
2058
- value: "<n>",
2059
- help: "Semantic reads: records per page (1\u2013100, default 10)"
2060
- },
2061
- "cursor": { type: "string", value: "<cursor>", help: "Continue a semantic snapshot page" },
2062
- "snapshot": { type: "string", value: "<snapshot>", help: "Read an existing semantic snapshot" },
2063
- "receipt": {
2064
- type: "string",
2065
- value: "<receipt>",
2066
- help: "ack: confirm exactly the complete evidence delivered"
2067
- },
2068
- "status": {
2069
- type: "string",
2070
- value: "<status>",
2071
- help: "Filter the selected semantic review collection"
2072
- },
2073
- "waiting-on": {
2074
- type: "string",
2075
- value: "<agent|human|any>",
2076
- help: "Filter unread threads by who should reply"
2077
- },
2078
- "kind": { type: "string", value: "<verify|choose>", help: "Filter review groups by kind" },
2079
- "choice-state": { type: "string", value: "<chosen|empty>", help: "Filter choice groups" },
2080
- "artifact": { type: "string", value: "<type>/<id>", help: "The document or artboard" },
2081
- "all": {
2082
- type: "boolean",
2083
- help: "list: every thread, not only what changed; pass: every verify group not yet passed"
2084
- },
2085
- "block": { type: "string", value: "<blockId>", help: "add: the document block to comment on" },
2086
- "quote": {
2087
- type: "string",
2088
- value: "<text>",
2089
- help: "add: the text in the block the comment is about"
2090
- },
2091
- "board": { type: "string", value: "<boardId>", help: "add: the artboard board to comment on" },
2092
- "element": {
2093
- type: "string",
2094
- value: "<elementId>",
2095
- help: "add: the whiteboard element to comment on"
2096
- },
2097
- "page": {
2098
- type: "string",
2099
- value: "<pageId>",
2100
- help: "add: the whiteboard page --x and --y are on (default main)"
2101
- },
2102
- "x": { type: "string", value: "<px>", help: "add: horizontal position on the board" },
2103
- "y": { type: "string", value: "<px>", help: "add: vertical position on the board" },
2104
- "thread": { type: "string", value: "<threadId>", help: "reply, resolve: the thread" },
2105
- "text": {
2106
- type: "string",
2107
- value: "<text>",
2108
- help: "add, reply, note: the message; answer: a written answer; reject: the reason; pass: an optional note"
2109
- },
2110
- "group": {
2111
- type: "string",
2112
- value: "<groupId>",
2113
- help: "choose: the choice group; pass, reject: the verify group"
2114
- },
2115
- "clear": { type: "boolean", help: "choose, answer: clear the choice or answer" },
2116
- "ask": { type: "string", value: "<questionId>", help: "answer, note: the question id" },
2117
- "choice": { type: "string", value: "<value>", help: "answer: the option value" },
2118
- "round": { type: "string", value: "<roundId>", help: "submit: the round" },
2119
- "withdraw": { type: "boolean", help: "submit: withdraw the submission" }
2120
- },
2121
- positionals: 1
2122
- };
2123
-
2124
- // src/cli/config.ts
2125
- var import_node_os4 = __toESM(require("node:os"), 1);
2126
- var import_node_path9 = __toESM(require("node:path"), 1);
2127
-
2128
- // src/server/config.ts
2129
- var import_node_fs7 = __toESM(require("node:fs"), 1);
2130
- var import_node_path5 = __toESM(require("node:path"), 1);
2131
-
2132
- // src/shared/cloud.ts
2133
- var MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
2134
- var isVisibility = (value3) => value3 === "private" || value3 === "public";
2135
-
2136
- // src/server/state.ts
2137
- var import_node_fs6 = __toESM(require("node:fs"), 1);
2138
- var import_node_path4 = __toESM(require("node:path"), 1);
2139
-
2140
- // src/shared/types.ts
2141
- var ARTIFACT_TYPES = ["artboard", "document", "html", "whiteboard"];
2142
-
2143
- // src/shared/validate.ts
2144
- var BOARD_NUMBERS = ["x", "y", "w", "h"];
2145
- var BOARD_REQUIRED = ["file", "page", ...BOARD_NUMBERS];
2146
- var MEDIA_REQUIRED = ["src", "page", ...BOARD_NUMBERS];
2147
-
2148
- // src/server/state.ts
2149
- function writeJsonAtomic(file, value3) {
2150
- import_node_fs6.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
2151
- const tmp = `${file}.${process.pid}.tmp`;
2152
- import_node_fs6.default.writeFileSync(tmp, `${JSON.stringify(value3, null, 2)}
2153
- `, { mode: 384 });
2154
- import_node_fs6.default.renameSync(tmp, file);
2155
- }
2156
-
2157
- // src/server/config.ts
2158
- var defaults = () => ({ version: 1, artifacts: {} });
2159
- function isCloudRecord(value3) {
2160
- if (typeof value3 !== "object" || value3 === null) return false;
2161
- const { id, url, publishedAt, visibility } = value3;
2162
- return typeof id === "string" && typeof url === "string" && (typeof publishedAt === "string" || publishedAt === null) && isVisibility(visibility);
2163
- }
2164
- function cloudRecord({ id, url, publishedAt, visibility, fingerprint }) {
2165
- return {
2166
- id,
2167
- url,
2168
- publishedAt,
2169
- visibility,
2170
- ...typeof fingerprint === "string" ? { fingerprint } : {}
2171
- };
2172
- }
2173
- function normalize2(raw) {
2174
- const config = defaults();
2175
- if (typeof raw !== "object" || raw === null) return config;
2176
- const output = raw.output;
2177
- if (output && typeof output === "object" && "format" in output && ["brief", "json", "jsonl"].includes(String(output.format)))
2178
- config.output = { format: output.format };
2179
- const { autoPublish } = raw;
2180
- if (typeof autoPublish === "boolean") config.autoPublish = autoPublish;
2181
- const artifacts = raw.artifacts;
2182
- if (typeof artifacts !== "object" || artifacts === null) return config;
2183
- for (const [key, value3] of Object.entries(artifacts)) {
2184
- if (typeof value3 !== "object" || value3 === null) continue;
2185
- const { visibility, cloud, autoPublish: autoPublish2 } = value3;
2186
- if (visibility !== void 0 && !isVisibility(visibility)) continue;
2187
- const entry = {};
2188
- if (visibility) entry.visibility = visibility;
2189
- if (isCloudRecord(cloud)) entry.cloud = cloudRecord(cloud);
2190
- if (autoPublish2 === false) entry.autoPublish = false;
2191
- config.artifacts[key] = entry;
2192
- }
2193
- return config;
2194
- }
2195
- function readConfig(file) {
2196
- let text;
2197
- try {
2198
- text = import_node_fs7.default.readFileSync(file, "utf8");
2199
- } catch {
2200
- return { config: defaults() };
2201
- }
2202
- try {
2203
- return { config: normalize2(JSON.parse(text)) };
2204
- } catch (err) {
2205
- return {
2206
- config: defaults(),
2207
- warning: `config.json is not valid JSON: ${err.message}`
2208
- };
2209
- }
2210
- }
2211
- var IGNORED = ["config.json", "artifacts/*", "!artifacts/document/", "artifacts/document/*/.artifact-id"];
2212
- function ensureGitignore(dir) {
2213
- const file = import_node_path5.default.join(dir, ".gitignore");
2214
- let current = "";
2215
- try {
2216
- current = import_node_fs7.default.readFileSync(file, "utf8");
2217
- } catch {
2218
- }
2219
- const present2 = new Set(current.split(/\r?\n/).map((line) => line.trim()));
2220
- const missing = IGNORED.filter((line) => !present2.has(line));
2221
- if (missing.length === 0) return;
2222
- import_node_fs7.default.writeFileSync(file, `${current}${current === "" || current.endsWith("\n") ? "" : "\n"}${missing.join("\n")}
2223
- `);
2224
- }
2225
- function ensureConfig(file) {
2226
- import_node_fs7.default.mkdirSync(import_node_path5.default.dirname(file), { recursive: true });
2227
- ensureGitignore(import_node_path5.default.dirname(file));
2228
- if (!import_node_fs7.default.existsSync(file)) writeJsonAtomic(file, defaults());
2229
- }
2230
- function setProjectAutoPublish(file, value3) {
2231
- const { config, warning } = readConfig(file);
2232
- if (warning) throw new Error(warning);
2233
- if (value3 === null) delete config.autoPublish;
2234
- else config.autoPublish = value3;
2235
- import_node_fs7.default.mkdirSync(import_node_path5.default.dirname(file), { recursive: true });
2236
- writeJsonAtomic(file, config);
2237
- }
2238
-
2239
- // src/server/credentials.ts
2240
- var import_node_fs8 = __toESM(require("node:fs"), 1);
2241
- var import_node_path6 = __toESM(require("node:path"), 1);
2242
- function credentialsFile(env, platform, home) {
2243
- if (platform === "win32" && env.APPDATA)
2244
- return import_node_path6.default.join(env.APPDATA, "ryugi", "credentials.json");
2245
- return import_node_path6.default.join(env.XDG_CONFIG_HOME || import_node_path6.default.join(home, ".config"), "ryugi", "credentials.json");
2246
- }
2247
- function readCredentials(file, origin) {
2248
- let raw;
2249
- try {
2250
- raw = JSON.parse(import_node_fs8.default.readFileSync(file, "utf8"));
2251
- } catch {
2252
- return null;
2253
- }
2254
- if (typeof raw !== "object" || raw === null) return null;
2255
- const { origin: savedOrigin, token, login } = raw;
2256
- if (typeof savedOrigin !== "string" || typeof token !== "string" || typeof login !== "string")
2257
- return null;
2258
- if (savedOrigin !== origin) return null;
2259
- return { origin: savedOrigin, token, login };
2260
- }
2261
-
2262
- // src/server/settings.ts
2263
- var import_node_fs9 = __toESM(require("node:fs"), 1);
2264
- var import_node_path7 = __toESM(require("node:path"), 1);
2265
- var settingsFileFor = (credentialsFile2) => import_node_path7.default.join(import_node_path7.default.dirname(credentialsFile2), "config.json");
2266
- function readSettings(file) {
2267
- try {
2268
- const raw = JSON.parse(import_node_fs9.default.readFileSync(file, "utf8"));
2269
- return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
2270
- } catch {
2271
- return {};
2272
- }
2273
- }
2274
- function readGlobalAutoPublish(file) {
2275
- const value3 = readSettings(file).autoPublish;
2276
- return typeof value3 === "boolean" ? value3 : void 0;
2277
- }
2278
- function writeGlobalAutoPublish(file, value3) {
2279
- import_node_fs9.default.mkdirSync(import_node_path7.default.dirname(file), { recursive: true, mode: 448 });
2280
- writeJsonAtomic(file, { ...readSettings(file), autoPublish: value3 });
2281
- }
2282
-
2283
- // src/cli/output/configuration.ts
2284
- var import_node_fs10 = __toESM(require("node:fs"), 1);
2285
- var import_node_os3 = __toESM(require("node:os"), 1);
2286
- var import_node_path8 = __toESM(require("node:path"), 1);
2287
- var PERSISTED_FORMATS = ["brief", "json", "jsonl"];
2288
- function outputFiles(projectDir) {
2289
- return {
2290
- global: settingsFileFor(credentialsFile(process.env, process.platform, import_node_os3.default.homedir())),
2291
- project: import_node_path8.default.join(import_node_path8.default.dirname(projectPaths(projectDir).root), "config.json")
2292
- };
2293
- }
2294
- function read(file) {
2295
- try {
2296
- const raw = JSON.parse(import_node_fs10.default.readFileSync(file, "utf8"));
2297
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Expected an object");
2298
- return raw;
2299
- } catch (error) {
2300
- if (error.code === "ENOENT") return {};
2301
- throw new OutputError({
2302
- code: "E_CONFIG_INVALID",
2303
- message: `Cannot read ${file}: ${error.message}`
2304
- });
2305
- }
2306
- }
2307
- function outputValues(projectDir) {
2308
- const files = outputFiles(projectDir);
2309
- const get = (file) => {
2310
- const output = read(file).output;
2311
- if (output?.format !== void 0 && !PERSISTED_FORMATS.includes(output.format))
2312
- throw new OutputError({
2313
- code: "E_FORMAT_UNSUPPORTED",
2314
- message: `Unsupported output format in ${file}: ${String(output.format)}`
2315
- });
2316
- return output && PERSISTED_FORMATS.includes(output.format) ? output.format : null;
2317
- };
2318
- const global = get(files.global), project2 = get(files.project);
2319
- return {
2320
- global,
2321
- project: project2,
2322
- effective: project2 ?? global ?? "brief",
2323
- source: project2 ? "project" : global ? "global" : "default"
2324
- };
2546
+ text: messageText(options)
2547
+ };
2548
+ }
2549
+ if (!options.thread) throw new CliError(`comment ${action} needs --thread <threadId>`);
2550
+ return action === "reply" ? { op: "reply", threadId: options.thread, text: messageText(options) } : { op: "resolve", threadId: options.thread };
2325
2551
  }
2326
- function writeOutputFormat(projectDir, isProject, value3) {
2327
- if (value3 === "inherit" && !isProject || value3 !== "inherit" && !PERSISTED_FORMATS.includes(value3))
2328
- throw new OutputError({
2329
- code: "E_ARGUMENT_INVALID",
2330
- message: "--output-format takes brief, json, jsonl, or inherit with --project."
2331
- });
2332
- const files = outputFiles(projectDir), file = isProject ? files.project : files.global, raw = read(file);
2333
- if (value3 === "inherit") delete raw.output;
2334
- else raw.output = { format: value3 };
2335
- if (isProject) ensureConfig(file);
2336
- else import_node_fs10.default.mkdirSync(import_node_path8.default.dirname(file), { recursive: true, mode: 448 });
2337
- writeJsonAtomic(file, raw);
2552
+ function prepareComment(options) {
2553
+ const action = COMMENT_ACTIONS.find((name) => name === options.action);
2554
+ if (!action)
2555
+ throw new CliError(
2556
+ "comment needs an action: list, add, reply, resolve, choose, submit, answer, note, pass or reject"
2557
+ );
2558
+ if (!options.artifact || !ARTIFACT.test(options.artifact))
2559
+ throw new CliError("--artifact must be document/<id>, artboard/<id> or whiteboard/<id>");
2560
+ const [type, id2] = options.artifact.split("/");
2561
+ const request = requestOf(options, action, type);
2562
+ return { action, type, id: id2, request, artifact: options.artifact };
2338
2563
  }
2339
- function configuredEnvironment(argv, specs, env = process.env) {
2340
- if (env.RYUGI_ARTIFACTS_FORMAT !== void 0) return env;
2341
- const values = scanOptions(argv, specs);
2342
- if (["format", "json", "quiet", "source"].some((key) => values[key] !== void 0)) return env;
2343
- let project2 = typeof values["project-dir"] === "string" ? values["project-dir"] : process.cwd();
2344
- if (!values["project-dir"] && argv[0] && !argv[0].startsWith("-")) {
2345
- try {
2346
- if (import_node_fs10.default.statSync(argv[0]).isDirectory()) project2 = argv[0];
2347
- } catch {
2348
- }
2349
- }
2350
- const config = outputValues(project2);
2351
- return { ...env, RYUGI_ARTIFACTS_FORMAT: config.effective };
2564
+ async function callComment(options, prepared, signal) {
2565
+ const { type, id: id2, request } = prepared;
2566
+ const server = serverClient(options.projectDir);
2567
+ const agent = detectAgent(process.env);
2568
+ return server.call(
2569
+ "POST",
2570
+ `/api/agent/${type}/${id2}/comments`,
2571
+ {
2572
+ ...request,
2573
+ ...agent ? { agent } : {}
2574
+ },
2575
+ signal
2576
+ );
2352
2577
  }
2578
+ var commentCommand = {
2579
+ semantic: commentSemantic,
2580
+ name: "comment",
2581
+ summary: "Read and answer comments on a document, artboard or whiteboard (needs a running server)",
2582
+ usage: [
2583
+ "list --artifact <document|artboard|whiteboard>/<id> --sections all|<names> [--detail] [--all]",
2584
+ "list --artifact <document|artboard|whiteboard>/<id> [--section threads|asks|groups|rounds] [--all]",
2585
+ "read --artifact <document|artboard|whiteboard>/<id> --thread <threadId> [--snapshot <token>]",
2586
+ "ack --artifact <document|artboard|whiteboard>/<id> --receipt <receipt>",
2587
+ "add --artifact document/<id> --block <blockId> [--quote <text>] [--text <text>]",
2588
+ "add --artifact artboard/<id> --board <boardId> [--x <px> --y <px>] [--text <text>]",
2589
+ "add --artifact whiteboard/<id> (--element <elementId> | --x <px> --y <px> [--page <pageId>]) [--text <text>]",
2590
+ "reply --artifact <document|artboard|whiteboard>/<id> --thread <threadId> [--text <text>]",
2591
+ "resolve --artifact <document|artboard|whiteboard>/<id> --thread <threadId>",
2592
+ "choose --artifact artboard/<id> --group <groupId> (--board <boardId> | --clear)",
2593
+ "submit --artifact artboard/<id> --round <roundId> [--withdraw]",
2594
+ "submit --artifact document/<id> [--withdraw]",
2595
+ "answer --artifact document/<id> --ask <questionId> (--choice <value> | --text <answer> | --clear)",
2596
+ "note --artifact document/<id> --ask <questionId> [--text <note>]",
2597
+ "pass --artifact artboard/<id> (--group <groupId> | --all) [--text <text>]",
2598
+ "reject --artifact artboard/<id> --group <groupId> [--text <text>]"
2599
+ ],
2600
+ details: "list returns a summary; select threads, asks, groups or rounds to read one collection. Previews do not consume evidence. read returns a complete thread and receipt; use ack after applying that evidence. asks, groups and rounds are independent state reads without receipts. Without --text, add, reply, note and reject read the message from stdin. answer records what your human partner said in the terminal; note settles a question once its answer is applied. pass and reject record a verdict on a verify group only when your human partner asks for it; the board shows the agent recorded it.",
2601
+ options: {
2602
+ "sections": {
2603
+ type: "string",
2604
+ value: "<all|threads,asks|threads,groups,rounds>",
2605
+ help: "Read multiple bounded review collections in one request"
2606
+ },
2607
+ "section": {
2608
+ type: "string",
2609
+ value: "<threads|asks|groups|rounds>",
2610
+ help: "Semantic list: select one review collection"
2611
+ },
2612
+ "limit": {
2613
+ type: "string",
2614
+ value: "<n>",
2615
+ help: "Semantic reads: records per page (1\u2013100, default 10)"
2616
+ },
2617
+ "cursor": { type: "string", value: "<cursor>", help: "Continue a semantic snapshot page" },
2618
+ "snapshot": { type: "string", value: "<snapshot>", help: "Read an existing semantic snapshot" },
2619
+ "receipt": {
2620
+ type: "string",
2621
+ value: "<receipt>",
2622
+ help: "ack: confirm exactly the complete evidence delivered"
2623
+ },
2624
+ "status": {
2625
+ type: "string",
2626
+ value: "<status>",
2627
+ help: "Filter the selected semantic review collection"
2628
+ },
2629
+ "waiting-on": {
2630
+ type: "string",
2631
+ value: "<agent|human|any>",
2632
+ help: "Filter unread threads by who should reply"
2633
+ },
2634
+ "kind": { type: "string", value: "<verify|choose>", help: "Filter review groups by kind" },
2635
+ "choice-state": { type: "string", value: "<chosen|empty>", help: "Filter choice groups" },
2636
+ "artifact": { type: "string", value: "<type>/<id>", help: "The document or artboard" },
2637
+ "all": {
2638
+ type: "boolean",
2639
+ help: "list: every thread, not only what changed; pass: every verify group not yet passed"
2640
+ },
2641
+ "block": { type: "string", value: "<blockId>", help: "add: the document block to comment on" },
2642
+ "quote": {
2643
+ type: "string",
2644
+ value: "<text>",
2645
+ help: "add: the text in the block the comment is about"
2646
+ },
2647
+ "board": { type: "string", value: "<boardId>", help: "add: the artboard board to comment on" },
2648
+ "element": {
2649
+ type: "string",
2650
+ value: "<elementId>",
2651
+ help: "add: the whiteboard element to comment on"
2652
+ },
2653
+ "page": {
2654
+ type: "string",
2655
+ value: "<pageId>",
2656
+ help: "add: the whiteboard page --x and --y are on (default main)"
2657
+ },
2658
+ "x": { type: "string", value: "<px>", help: "add: horizontal position on the board" },
2659
+ "y": { type: "string", value: "<px>", help: "add: vertical position on the board" },
2660
+ "thread": { type: "string", value: "<threadId>", help: "reply, resolve: the thread" },
2661
+ "text": {
2662
+ type: "string",
2663
+ value: "<text>",
2664
+ help: "add, reply, note: the message; answer: a written answer; reject: the reason; pass: an optional note"
2665
+ },
2666
+ "group": {
2667
+ type: "string",
2668
+ value: "<groupId>",
2669
+ help: "choose: the choice group; pass, reject: the verify group"
2670
+ },
2671
+ "clear": { type: "boolean", help: "choose, answer: clear the choice or answer" },
2672
+ "ask": { type: "string", value: "<questionId>", help: "answer, note: the question id" },
2673
+ "choice": { type: "string", value: "<value>", help: "answer: the option value" },
2674
+ "round": { type: "string", value: "<roundId>", help: "submit: the round" },
2675
+ "withdraw": { type: "boolean", help: "submit: withdraw the submission" }
2676
+ },
2677
+ positionals: 1
2678
+ };
2353
2679
 
2354
2680
  // src/cli/config.ts
2681
+ var import_node_os4 = __toESM(require("node:os"), 1);
2682
+ var import_node_path9 = __toESM(require("node:path"), 1);
2355
2683
  var VALUES = { on: true, off: false, inherit: null };
2356
2684
  function configure(options) {
2357
2685
  const globalFile = settingsFileFor(credentialsFile(process.env, process.platform, import_node_os4.default.homedir()));
2358
2686
  const projectFile = import_node_path9.default.join(import_node_path9.default.dirname(projectPaths(options.projectDir).root), "config.json");
2359
2687
  if (options.autoPublish !== void 0) {
2360
2688
  const value3 = VALUES[options.autoPublish];
2361
- if (value3 === void 0) throw new CliError("--auto-publish takes on, off, or (with --project) inherit");
2689
+ if (value3 === void 0)
2690
+ throw new CliError("--auto-publish takes on, off, or (with --project) inherit");
2362
2691
  if (options.project) {
2363
2692
  ensureConfig(projectFile);
2364
2693
  setProjectAutoPublish(projectFile, value3);
@@ -2386,7 +2715,7 @@ var configCommand = {
2386
2715
  value: "on|off|inherit",
2387
2716
  help: "Turn auto-publish on or off; inherit (with --project) drops the override"
2388
2717
  },
2389
- project: { type: "boolean", help: "Set it for this project only instead of every project" }
2718
+ "project": { type: "boolean", help: "Set it for this project only instead of every project" }
2390
2719
  },
2391
2720
  semantic: (input) => bindCommand(
2392
2721
  {
@@ -2400,7 +2729,11 @@ var configCommand = {
2400
2729
  if (input.values["auto-publish"] !== void 0 && input.values["output-format"] !== void 0)
2401
2730
  throw new CliError("Choose auto-publish or output-format.");
2402
2731
  if (typeof input.values["output-format"] === "string")
2403
- writeOutputFormat(input.projectDir, !!input.values.project, input.values["output-format"]);
2732
+ writeOutputFormat(
2733
+ input.projectDir,
2734
+ !!input.values.project,
2735
+ input.values["output-format"]
2736
+ );
2404
2737
  const values = configure({
2405
2738
  projectDir: input.projectDir,
2406
2739
  autoPublish: input.values["auto-publish"],
@@ -2414,24 +2747,29 @@ var configCommand = {
2414
2747
  applies: "supported_turn_end_hook"
2415
2748
  });
2416
2749
  },
2417
- present({ data }) {
2418
- return {
2419
- heading: input.values["output-format"] !== void 0 ? "Output" : "Auto-publish",
2420
- status: data.effective === null ? "undecided" : data.effective ? "on" : "off",
2421
- facts: [
2422
- fact("Output", field(data, "output"), true),
2423
- fact("Source", field(data, "source"), true),
2424
- fact("Global", field(data, "global")),
2425
- fact("Project", field(data, "project")),
2426
- fact("Applies", field(data, "applies"))
2427
- ],
2428
- sections: []
2429
- };
2430
- }
2750
+ present: presentConfig
2431
2751
  },
2432
2752
  input
2433
2753
  )
2434
2754
  };
2755
+ function presentConfig({ data }) {
2756
+ const on = data.effective === null ? "undecided" : data.effective ? "on" : "off";
2757
+ return {
2758
+ heading: "Config",
2759
+ status: "",
2760
+ facts: [
2761
+ fact("Auto-publish", {
2762
+ key: "autoPublish",
2763
+ value: data.source === "undecided" ? on : `${on} (${data.source})`
2764
+ }),
2765
+ fact("Output format", {
2766
+ key: "output",
2767
+ value: `${data.output.effective} (${data.output.source})`
2768
+ })
2769
+ ],
2770
+ sections: []
2771
+ };
2772
+ }
2435
2773
 
2436
2774
  // src/cli/semantic/get.ts
2437
2775
  var import_node_fs11 = __toESM(require("node:fs"), 1);
@@ -2458,6 +2796,39 @@ function stripRuntime(html) {
2458
2796
  }
2459
2797
 
2460
2798
  // src/cli/semantic/get.ts
2799
+ function presentGet({ data }) {
2800
+ return {
2801
+ heading: "Cloud artifact",
2802
+ status: data.status === "downloaded" || data.status === "partial" ? data.status : "",
2803
+ subject: data.id,
2804
+ summary: [data.type, `v${data.version}`, data.visibility].join(" \xB7 "),
2805
+ facts: [
2806
+ fact("Title", field(data, "title")),
2807
+ ...data.dir ? [fact("Downloaded", field(data, "dir"), true)] : data.files ? [fact("Files available", { key: "files", value: data.files.join(", ") })] : []
2808
+ ],
2809
+ sections: [
2810
+ ...data.outline ? [
2811
+ {
2812
+ kind: "records",
2813
+ key: "outline",
2814
+ rows: data.outline,
2815
+ columns: [
2816
+ { key: "id", positional: true, required: true },
2817
+ { key: "type", positional: true },
2818
+ { key: "preview", positional: true }
2819
+ ]
2820
+ }
2821
+ ] : [],
2822
+ ...data.content !== void 0 ? [{ kind: "content", key: "content", title: "LiteXML", text: data.content }] : [],
2823
+ ...data.comments ? [
2824
+ {
2825
+ ...threadMessages(data.comments.threads, "comments"),
2826
+ title: "Published snapshot comments"
2827
+ }
2828
+ ] : []
2829
+ ]
2830
+ };
2831
+ }
2461
2832
  var value2 = (input, key) => typeof input.values[key] === "string" ? input.values[key] : void 0;
2462
2833
  var safePath = (file) => !!file && !file.includes("\\") && !file.includes("\0") && !import_node_path10.default.posix.isAbsolute(file) && file.split("/").every((p) => !!p && p !== "." && p !== "..");
2463
2834
  async function downloadFiles(data, dir, ctx) {
@@ -2512,7 +2883,7 @@ function getSemantic(input) {
2512
2883
  code: "E_ARGUMENT_INVALID",
2513
2884
  message: "Give a Cloud link or artifact ID."
2514
2885
  });
2515
- const { origin, id } = parseTarget(
2886
+ const { origin, id: id2 } = parseTarget(
2516
2887
  target,
2517
2888
  (process.env.RYUGI_CLOUD_ORIGIN || "https://ryugi.innei.dev").replace(/\/+$/, "")
2518
2889
  );
@@ -2579,7 +2950,7 @@ function getSemantic(input) {
2579
2950
  credentialsFile(process.env, process.platform, import_node_os5.default.homedir()),
2580
2951
  origin
2581
2952
  );
2582
- const response = await fetch(`${origin}/api/a/${id}?${query2}`, {
2953
+ const response = await fetch(`${origin}/api/a/${id2}?${query2}`, {
2583
2954
  headers: credentials ? { Authorization: `Bearer ${credentials.token}` } : {},
2584
2955
  signal: ctx.signal
2585
2956
  });
@@ -2629,7 +3000,7 @@ function getSemantic(input) {
2629
3000
  process.env.XDG_CACHE_HOME || import_node_path10.default.join(import_node_os5.default.homedir(), ".cache"),
2630
3001
  "ryugi",
2631
3002
  "get",
2632
- id,
3003
+ id2,
2633
3004
  `v${data.version}`
2634
3005
  )
2635
3006
  );
@@ -2670,43 +3041,7 @@ function getSemantic(input) {
2670
3041
  ]
2671
3042
  });
2672
3043
  },
2673
- present({ data }) {
2674
- return {
2675
- heading: "Cloud artifact",
2676
- status: data.status,
2677
- subject: data.id,
2678
- facts: [
2679
- fact("Type", field(data, "type"), true),
2680
- fact("Title", field(data, "title")),
2681
- fact("Version", field(data, "version"), true),
2682
- fact("Visibility", field(data, "visibility"), true),
2683
- ...data.dir ? [fact("Downloaded", field(data, "dir"), true)] : data.files ? [fact("Files available", field(data, "files"))] : []
2684
- ],
2685
- sections: [
2686
- ...data.outline ? [
2687
- {
2688
- kind: "records",
2689
- key: "outline",
2690
- rows: data.outline,
2691
- columns: [
2692
- { key: "id", label: "ID", required: true },
2693
- { key: "type", label: "Type" },
2694
- { key: "preview", label: "Preview" }
2695
- ]
2696
- }
2697
- ] : [],
2698
- ...data.content !== void 0 ? [{ kind: "content", key: "content", title: "LiteXML", text: data.content }] : [],
2699
- ...data.comments ? [
2700
- {
2701
- kind: "content",
2702
- key: "comments",
2703
- title: "Published snapshot comments",
2704
- text: JSON.stringify(data.comments.threads)
2705
- }
2706
- ] : []
2707
- ]
2708
- };
2709
- }
3044
+ present: presentGet
2710
3045
  },
2711
3046
  input
2712
3047
  );
@@ -2724,10 +3059,10 @@ function parseTarget(target, defaultOrigin) {
2724
3059
  } catch {
2725
3060
  throw new CliError(TARGET_ERROR);
2726
3061
  }
2727
- const id = LINK_PATH.exec(url.pathname)?.[1];
2728
- if (!id || url.protocol !== "https:" && url.protocol !== "http:")
3062
+ const id2 = LINK_PATH.exec(url.pathname)?.[1];
3063
+ if (!id2 || url.protocol !== "https:" && url.protocol !== "http:")
2729
3064
  throw new CliError(TARGET_ERROR);
2730
- return { origin: url.origin, id };
3065
+ return { origin: url.origin, id: id2 };
2731
3066
  }
2732
3067
  var getCommand = {
2733
3068
  name: "get",
@@ -2781,6 +3116,74 @@ function acceptCloud(response) {
2781
3116
  }
2782
3117
 
2783
3118
  // src/cli/semantic/publish.ts
3119
+ function presentPublish({ data }) {
3120
+ return {
3121
+ heading: "Publish",
3122
+ status: data.status,
3123
+ subject: data.artifact,
3124
+ facts: [
3125
+ fact("ID", field(data, "id"), true),
3126
+ fact("URL", field(data, "url"), true),
3127
+ fact(
3128
+ "Visibility",
3129
+ { key: "visibility", value: `${data.visibility} (${data.visibilitySource})` },
3130
+ true
3131
+ ),
3132
+ ...data.version === void 0 ? [] : [fact("Version", field(data, "version"))],
3133
+ fact("Unpublished references", { key: "unpublished", value: data.unpublished.join(", ") }),
3134
+ fact("Private references", { key: "private", value: data.private.join(", ") })
3135
+ ],
3136
+ sections: data.references.length ? [
3137
+ {
3138
+ kind: "records",
3139
+ key: "references",
3140
+ title: "Referenced artifacts",
3141
+ rows: data.references.map((ref) => ({
3142
+ artifact: ref.artifact,
3143
+ status: ref.status,
3144
+ error: ref.error ? `${ref.error.code}: ${ref.error.message}` : "",
3145
+ recovery: ref.status === "unknown" ? "inspect the remote artifact before retrying" : ""
3146
+ })),
3147
+ columns: [
3148
+ { key: "artifact", positional: true, required: true },
3149
+ { key: "status", positional: true, required: true },
3150
+ { key: "error", positional: true },
3151
+ { key: "recovery", positional: true }
3152
+ ]
3153
+ }
3154
+ ] : []
3155
+ };
3156
+ }
3157
+ function presentCloudList({
3158
+ data,
3159
+ coverage
3160
+ }) {
3161
+ const total = coverage.page?.total ?? data.artifacts.length;
3162
+ return {
3163
+ heading: "Cloud artifacts",
3164
+ status: "",
3165
+ summary: `${total} on account`,
3166
+ facts: [],
3167
+ sections: [
3168
+ {
3169
+ kind: "records",
3170
+ key: "artifacts",
3171
+ rows: data.artifacts.map((row) => ({
3172
+ ...row,
3173
+ version: row.version === void 0 ? "" : `v${row.version}`
3174
+ })),
3175
+ columns: [
3176
+ { key: "id", positional: true, required: true },
3177
+ { key: "type", positional: true },
3178
+ { key: "visibility", positional: true, required: true },
3179
+ { key: "version", positional: true },
3180
+ { key: "updatedAt", positional: true },
3181
+ { key: "title", positional: true }
3182
+ ]
3183
+ }
3184
+ ]
3185
+ };
3186
+ }
2784
3187
  var formats2 = ["brief", "json", "jsonl", "quiet"];
2785
3188
  function optionsOf(input) {
2786
3189
  const { values } = input;
@@ -2841,8 +3244,8 @@ async function publishReport(input, ctx, artifact, visibility) {
2841
3244
  const published = await send(artifact, visibility);
2842
3245
  const boards = published.boards;
2843
3246
  const pending = [...boards?.unpublished ?? []];
2844
- const references = (boards?.published ?? []).map((id) => ({
2845
- artifact: `artboard/${id}`,
3247
+ const references = (boards?.published ?? []).map((id2) => ({
3248
+ artifact: `artboard/${id2}`,
2846
3249
  status: "published"
2847
3250
  }));
2848
3251
  let partial = false;
@@ -2858,10 +3261,10 @@ async function publishReport(input, ctx, artifact, visibility) {
2858
3261
  status: errorOf(error).httpStatus && errorOf(error).httpStatus < 500 ? "failed" : "unknown",
2859
3262
  error: errorOf(error)
2860
3263
  });
2861
- for (const id of pending.slice(i + 1)) {
2862
- const effect = ctx.effects.begin("publish", `artboard/${id}`);
3264
+ for (const id2 of pending.slice(i + 1)) {
3265
+ const effect = ctx.effects.begin("publish", `artboard/${id2}`);
2863
3266
  ctx.effects.set(effect, "not_attempted");
2864
- references.push({ artifact: `artboard/${id}`, status: "not_attempted" });
3267
+ references.push({ artifact: `artboard/${id2}`, status: "not_attempted" });
2865
3268
  }
2866
3269
  partial = true;
2867
3270
  break;
@@ -2880,7 +3283,7 @@ async function publishReport(input, ctx, artifact, visibility) {
2880
3283
  ...published.version === void 0 ? {} : { version: published.version },
2881
3284
  visibilitySource: visibility ? "requested" : "preserved",
2882
3285
  references,
2883
- unpublished: pending.filter((id) => !succeeded.has(`artboard/${id}`)),
3286
+ unpublished: pending.filter((id2) => !succeeded.has(`artboard/${id2}`)),
2884
3287
  private: boards?.private ?? []
2885
3288
  };
2886
3289
  return result(report2, partial ? { kind: "partial" } : { kind: "succeeded" }, {
@@ -2918,41 +3321,7 @@ function publishSemantic(input) {
2918
3321
  mode: "finite",
2919
3322
  output: { formats: formats2, scalar: (data) => data.id },
2920
3323
  run: (_input, ctx) => publishReport(input, ctx, artifact, visibility),
2921
- present({ data }) {
2922
- return {
2923
- heading: "Publish",
2924
- status: data.status,
2925
- subject: data.artifact,
2926
- facts: [
2927
- fact("ID", field(data, "id"), true),
2928
- fact("URL", field(data, "url"), true),
2929
- fact("Visibility", field(data, "visibility"), true),
2930
- fact("Visibility source", field(data, "visibilitySource")),
2931
- ...data.version === void 0 ? [] : [fact("Version", field(data, "version"))],
2932
- ...data.unpublished.length ? [fact("Unpublished references", field(data, "unpublished"))] : [],
2933
- ...data.private.length ? [fact("Private references", field(data, "private"))] : []
2934
- ],
2935
- sections: data.references.length ? [
2936
- {
2937
- kind: "records",
2938
- key: "references",
2939
- title: "Referenced artifacts",
2940
- rows: data.references.map((ref) => ({
2941
- artifact: ref.artifact,
2942
- status: ref.status,
2943
- error: ref.error ? `${ref.error.code}: ${ref.error.message}` : "",
2944
- recovery: ref.status === "unknown" ? "Inspect the remote artifact before retrying" : ""
2945
- })),
2946
- columns: [
2947
- { key: "artifact", label: "Artifact", required: true },
2948
- { key: "status", label: "Status", required: true },
2949
- { key: "error", label: "Reason" },
2950
- { key: "recovery", label: "Recovery" }
2951
- ]
2952
- }
2953
- ] : []
2954
- };
2955
- }
3324
+ present: presentPublish
2956
3325
  },
2957
3326
  input
2958
3327
  );
@@ -3012,27 +3381,7 @@ function publishSemantic(input) {
3012
3381
  ] : []
3013
3382
  });
3014
3383
  },
3015
- present({ data }) {
3016
- return {
3017
- heading: "Cloud artifacts",
3018
- status: "listed",
3019
- facts: [],
3020
- sections: [
3021
- {
3022
- kind: "records",
3023
- key: "artifacts",
3024
- rows: data.artifacts.map((row) => ({ ...row })),
3025
- columns: [
3026
- { key: "id", label: "ID", required: true },
3027
- { key: "type", label: "Type" },
3028
- { key: "title", label: "Title" },
3029
- { key: "visibility", label: "Visibility", required: true },
3030
- { key: "updatedAt", label: "Updated" }
3031
- ]
3032
- }
3033
- ]
3034
- };
3035
- }
3384
+ present: presentCloudList
3036
3385
  },
3037
3386
  input
3038
3387
  );
@@ -3228,26 +3577,27 @@ var sceneCommand = {
3228
3577
  }
3229
3578
  );
3230
3579
  },
3231
- present({ data }) {
3232
- return {
3233
- heading: "Scene",
3234
- status: data.problem ? "invalid" : "read",
3235
- subject: data.artifact,
3236
- facts: [
3237
- ...data.page ? [fact("Page", field(data, "page"))] : [],
3238
- fact("Version", field(data, "sceneVersion")),
3239
- fact("Render", field(data, "render")),
3240
- ...data.renderPath ? [fact("Image", field(data, "renderPath"))] : [],
3241
- ...data.problem ? [fact("Problem", field(data, "problem"))] : [],
3242
- ...data.conflicts.length ? [fact("Conflicts", field(data, "conflicts"))] : []
3243
- ],
3244
- sections: [{ kind: "content", key: "lines", text: data.lines.join("\n") }]
3245
- };
3246
- }
3580
+ present: presentScene
3247
3581
  },
3248
3582
  input
3249
3583
  )
3250
3584
  };
3585
+ function presentScene({ data }) {
3586
+ return {
3587
+ heading: "Scene",
3588
+ status: data.problem ? "invalid" : "",
3589
+ subject: data.artifact,
3590
+ facts: [
3591
+ ...data.page ? [fact("Page", field(data, "page"))] : [],
3592
+ fact("Version", field(data, "sceneVersion")),
3593
+ fact("Render", field(data, "render")),
3594
+ ...data.renderPath ? [fact("Image", field(data, "renderPath"))] : [],
3595
+ ...data.problem ? [fact("Problem", field(data, "problem"))] : [],
3596
+ fact("Conflicts", { key: "conflicts", value: data.conflicts.join(", ") })
3597
+ ],
3598
+ sections: [{ kind: "content", key: "lines", text: data.lines.join("\n") }]
3599
+ };
3600
+ }
3251
3601
 
3252
3602
  // src/cli/start.ts
3253
3603
  var import_node_child_process3 = require("node:child_process");
@@ -3271,48 +3621,48 @@ var MINT_LENGTH = 21;
3271
3621
  var MINTED_PATTERN = new RegExp(`^[a-z0-9]{${MINT_LENGTH}}$`, "i");
3272
3622
  var MINT_BYTE_LIMIT = 248;
3273
3623
  function mintArtifactId() {
3274
- let id = "";
3624
+ let id2 = "";
3275
3625
  const bytes = new Uint8Array(MINT_LENGTH * 2);
3276
- while (id.length < MINT_LENGTH) {
3626
+ while (id2.length < MINT_LENGTH) {
3277
3627
  crypto.getRandomValues(bytes);
3278
3628
  for (const byte of bytes) {
3279
3629
  if (byte >= MINT_BYTE_LIMIT) continue;
3280
- id += MINT_ALPHABET[byte % MINT_ALPHABET.length];
3281
- if (id.length === MINT_LENGTH) break;
3630
+ id2 += MINT_ALPHABET[byte % MINT_ALPHABET.length];
3631
+ if (id2.length === MINT_LENGTH) break;
3282
3632
  }
3283
3633
  }
3284
- return id;
3634
+ return id2;
3285
3635
  }
3286
- var isArtifactId = (id) => ID_PATTERN.test(id);
3287
- var isArtifactAddress = (id) => ADDRESS_PATTERN.test(id);
3636
+ var isArtifactId = (id2) => ID_PATTERN.test(id2);
3637
+ var isArtifactAddress = (id2) => ADDRESS_PATTERN.test(id2);
3288
3638
  var isArtifactType = (type) => ARTIFACT_TYPES.includes(type);
3289
3639
 
3290
3640
  // src/server/artifacts.ts
3291
3641
  function artifactKeyOf(relative, isId = isArtifactId) {
3292
- const [type, id] = relative.split("/");
3293
- if (!type || !id || !isArtifactType(type) || !isId(id)) return null;
3294
- return { type, id };
3642
+ const [type, id2] = relative.split("/");
3643
+ if (!type || !id2 || !isArtifactType(type) || !isId(id2)) return null;
3644
+ return { type, id: id2 };
3295
3645
  }
3296
3646
 
3297
3647
  // src/server/artifact-address.ts
3298
3648
  function artifactId(dir) {
3299
3649
  const file = import_node_path11.default.join(dir, ".artifact-id");
3300
- let id;
3650
+ let id2;
3301
3651
  try {
3302
- id = import_node_fs12.default.readFileSync(file, "utf8").trim();
3652
+ id2 = import_node_fs12.default.readFileSync(file, "utf8").trim();
3303
3653
  } catch (err) {
3304
3654
  if (err.code !== "ENOENT") throw err;
3305
- id = mintArtifactId();
3655
+ id2 = mintArtifactId();
3306
3656
  try {
3307
- import_node_fs12.default.writeFileSync(file, `${id}
3657
+ import_node_fs12.default.writeFileSync(file, `${id2}
3308
3658
  `, { flag: "wx" });
3309
3659
  } catch (writeError) {
3310
3660
  if (writeError.code !== "EEXIST") throw writeError;
3311
3661
  return artifactId(dir);
3312
3662
  }
3313
3663
  }
3314
- if (!isArtifactAddress(id)) throw new Error(`Invalid artifact ID in ${file}`);
3315
- return id;
3664
+ if (!isArtifactAddress(id2)) throw new Error(`Invalid artifact ID in ${file}`);
3665
+ return id2;
3316
3666
  }
3317
3667
  function artifactPath(root, type, slug) {
3318
3668
  if (!isArtifactType(type) || !isArtifactId(slug)) throw new Error("Invalid artifact locator");
@@ -3507,13 +3857,12 @@ function report(options, info) {
3507
3857
  cloud: info?.cloudLink ? "live_link_reserved" : "local_only"
3508
3858
  };
3509
3859
  }
3510
- function present({ data }) {
3860
+ function presentServer({ data }) {
3511
3861
  return {
3512
3862
  heading: "Server",
3513
3863
  status: data.status,
3514
3864
  subject: data.reused ? "reused" : void 0,
3515
3865
  facts: [
3516
- fact("Scope", field(data, "scope"), true),
3517
3866
  fact("Project", field(data, "project"), true),
3518
3867
  ...data.artifact ? [fact("Artifact", field(data, "artifact"), true)] : [],
3519
3868
  ...data.url ? [fact("Open", field(data, "url"), true)] : [],
@@ -3566,7 +3915,7 @@ async function* lifecycle(options, ctx) {
3566
3915
  failure(value3) {
3567
3916
  startupError = String(value3.error ?? "Server failed");
3568
3917
  },
3569
- log: (text) => ctx.diagnostic(text)
3918
+ log: (text2) => ctx.diagnostic(text2)
3570
3919
  }).then((code) => {
3571
3920
  if (ctx.signal.aborted) {
3572
3921
  const signal = ctx.signal.reason === "SIGINT" ? "SIGINT" : "SIGTERM";
@@ -3624,7 +3973,7 @@ function startSemantic(input, operation = "start") {
3624
3973
  mode: "live",
3625
3974
  output: { formats: ["brief", "jsonl"] },
3626
3975
  run: (_input, ctx) => lifecycle(options, ctx),
3627
- present
3976
+ present: presentServer
3628
3977
  },
3629
3978
  input
3630
3979
  );
@@ -3646,7 +3995,7 @@ function startSemantic(input, operation = "start") {
3646
3995
  failure(error) {
3647
3996
  failure = String(error.error ?? "Server failed");
3648
3997
  },
3649
- log: (text) => ctx.diagnostic(text)
3998
+ log: (text2) => ctx.diagnostic(text2)
3650
3999
  });
3651
4000
  if (code !== 0 || !value3)
3652
4001
  throw new OutputError({
@@ -3655,7 +4004,7 @@ function startSemantic(input, operation = "start") {
3655
4004
  });
3656
4005
  return started(options, value3);
3657
4006
  },
3658
- present
4007
+ present: presentServer
3659
4008
  },
3660
4009
  input
3661
4010
  );
@@ -3738,9 +4087,9 @@ async function acquireLock(lock) {
3738
4087
  }
3739
4088
  function runningPid(machine) {
3740
4089
  const pid = Number(readText(machine.pid));
3741
- const id = readText(machine.serverId);
3742
- if (!Number.isInteger(pid) || pid <= 0 || !id) return null;
3743
- return hasServerId(pid, id) ? pid : null;
4090
+ const id2 = readText(machine.serverId);
4091
+ if (!Number.isInteger(pid) || pid <= 0 || !id2) return null;
4092
+ return hasServerId(pid, id2) ? pid : null;
3744
4093
  }
3745
4094
  function readInfo(machine) {
3746
4095
  try {
@@ -3813,8 +4162,8 @@ function serverEnv(options) {
3813
4162
  delete env[key];
3814
4163
  return env;
3815
4164
  }
3816
- var startedLine = (text) => text.split("\n").find((line) => line.includes('"server-started"'));
3817
- var errorLine = (text) => text.split("\n").find((line) => line.includes('"server-error"'));
4165
+ var startedLine = (text2) => text2.split("\n").find((line2) => line2.includes('"server-started"'));
4166
+ var errorLine = (text2) => text2.split("\n").find((line2) => line2.includes('"server-error"'));
3818
4167
  async function register(info, paths, open) {
3819
4168
  const res = await fetch(apiUrl(info, "/api/hub/projects"), {
3820
4169
  method: "POST",
@@ -3841,7 +4190,7 @@ async function startServer(options, hooks) {
3841
4190
  import_node_fs15.default.mkdirSync(paths.state, { recursive: true });
3842
4191
  if (!import_node_fs15.default.existsSync(import_node_path15.default.join(paths.state, ".gitignore")))
3843
4192
  import_node_fs15.default.writeFileSync(import_node_path15.default.join(paths.state, ".gitignore"), "*\n");
3844
- const cliVersion = process.env.RYUGI_ARTIFACTS_VERSION || (true ? "1.63.0" : "");
4193
+ const cliVersion = process.env.RYUGI_ARTIFACTS_VERSION || (true ? "1.65.0" : "");
3845
4194
  const known = readProjects(machine.projects).some((entry) => entry.dir === paths.project);
3846
4195
  const finish = async (info, reused) => {
3847
4196
  recordAgent(paths);
@@ -3900,13 +4249,13 @@ async function startServer(options, hooks) {
3900
4249
  }
3901
4250
  await stopOldProjectServers();
3902
4251
  import_node_fs15.default.rmSync(machine.info, { force: true });
3903
- const id = import_node_crypto2.default.randomBytes(24).toString("hex");
3904
- import_node_fs15.default.writeFileSync(machine.serverId, `${id}
4252
+ const id2 = import_node_crypto2.default.randomBytes(24).toString("hex");
4253
+ import_node_fs15.default.writeFileSync(machine.serverId, `${id2}
3905
4254
  `);
3906
4255
  import_node_fs15.default.writeFileSync(machine.version, `${cliVersion}
3907
4256
  `);
3908
4257
  const server = import_node_path15.default.join(__dirname, "server.cjs");
3909
- const args = [server, `--artifacts-server-id=${id}`, ...options.port ? [`--port=${options.port}`] : []];
4258
+ const args = [server, `--artifacts-server-id=${id2}`, ...options.port ? [`--port=${options.port}`] : []];
3910
4259
  const env = serverEnv(options);
3911
4260
  const foreground = options.foreground || !options.background && !!process.env.CODEX_CI;
3912
4261
  if (foreground) {
@@ -3918,16 +4267,16 @@ async function startServer(options, hooks) {
3918
4267
  };
3919
4268
  if (hooks.signal) hooks.signal.addEventListener("abort", terminate, { once: true });
3920
4269
  else for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, terminate);
3921
- import_node_readline.default.createInterface({ input: child2.stderr }).on("line", (line) => hooks.log(line, "stderr"));
4270
+ import_node_readline.default.createInterface({ input: child2.stderr }).on("line", (line2) => hooks.log(line2, "stderr"));
3922
4271
  const exited = new Promise((resolve) => child2.on("close", (code) => resolve(code ?? 1)));
3923
4272
  let announced = false;
3924
4273
  let startup;
3925
- import_node_readline.default.createInterface({ input: child2.stdout }).on("line", (line) => {
4274
+ import_node_readline.default.createInterface({ input: child2.stdout }).on("line", (line2) => {
3926
4275
  let value3;
3927
4276
  try {
3928
- value3 = JSON.parse(line);
4277
+ value3 = JSON.parse(line2);
3929
4278
  } catch {
3930
- hooks.log(line, "stdout");
4279
+ hooks.log(line2, "stdout");
3931
4280
  return;
3932
4281
  }
3933
4282
  if (value3.type !== "server-started" || announced) {
@@ -3966,8 +4315,8 @@ async function startServer(options, hooks) {
3966
4315
  import_node_fs15.default.writeFileSync(machine.pid, `${child.pid}
3967
4316
  `);
3968
4317
  for (let waited = 0; waited < START_TIMEOUT_MS; waited += 100) {
3969
- const text = readText(machine.log) ?? "";
3970
- const started2 = startedLine(text);
4318
+ const text2 = readText(machine.log) ?? "";
4319
+ const started2 = startedLine(text2);
3971
4320
  if (started2) {
3972
4321
  for (let check = 0; check < 10; check++) {
3973
4322
  if (!alive(child.pid))
@@ -3980,7 +4329,7 @@ async function startServer(options, hooks) {
3980
4329
  await finish(JSON.parse(started2), false);
3981
4330
  return 0;
3982
4331
  }
3983
- const failed = errorLine(text);
4332
+ const failed = errorLine(text2);
3984
4333
  if (failed) {
3985
4334
  hooks.failure(JSON.parse(failed));
3986
4335
  return 1;
@@ -4037,6 +4386,15 @@ function clearCompatInfo(projectsFile) {
4037
4386
  for (const entry of readProjects(projectsFile))
4038
4387
  import_node_fs16.default.rmSync(import_node_path16.default.join(artifactsRootOf(entry.dir), ".state", "server-info"), { force: true });
4039
4388
  }
4389
+ function presentStop({ data }) {
4390
+ return {
4391
+ heading: "Server",
4392
+ status: data.status,
4393
+ summary: data.status === data.reason ? "" : data.reason.replaceAll("_", " "),
4394
+ facts: [fact("Artifacts", field(data, "artifacts"))],
4395
+ sections: []
4396
+ };
4397
+ }
4040
4398
  async function stopServer() {
4041
4399
  const machine = machinePaths();
4042
4400
  const markStopped = (reason) => {
@@ -4053,8 +4411,8 @@ async function stopServer() {
4053
4411
  return { status: "not_running" };
4054
4412
  }
4055
4413
  const pid = Number(readText(machine.pid));
4056
- const id = readText(machine.serverId) ?? "";
4057
- if (!Number.isInteger(pid) || pid <= 0 || !/^[\w-]{32,64}$/.test(id) || !hasServerId(pid, id)) {
4414
+ const id2 = readText(machine.serverId) ?? "";
4415
+ if (!Number.isInteger(pid) || pid <= 0 || !/^[\w-]{32,64}$/.test(id2) || !hasServerId(pid, id2)) {
4058
4416
  import_node_fs16.default.rmSync(machine.pid, { force: true });
4059
4417
  import_node_fs16.default.rmSync(machine.serverId, { force: true });
4060
4418
  markStopped("stale_pid");
@@ -4100,18 +4458,7 @@ var stopCommand = {
4100
4458
  state.status === "failed" ? { kind: "failed", error: { code: "E_SERVER_STOP_FAILED", message: state.error } } : { kind: "succeeded" }
4101
4459
  );
4102
4460
  },
4103
- present({ data }) {
4104
- return {
4105
- heading: "Server",
4106
- status: data.status,
4107
- facts: [
4108
- fact("Scope", field(data, "scope"), true),
4109
- fact("Reason", field(data, "reason")),
4110
- fact("Artifacts", field(data, "artifacts"))
4111
- ],
4112
- sections: []
4113
- };
4114
- }
4461
+ present: presentStop
4115
4462
  },
4116
4463
  input
4117
4464
  )
@@ -4185,7 +4532,9 @@ async function fetchLatest(registry) {
4185
4532
  if (res.ok && typeof version === "string" && /^\d+\.\d+\.\d+/.test(version)) return version;
4186
4533
  throw new Error(`registry returned ${res.status}`);
4187
4534
  } catch (err) {
4188
- throw new CliError(`Could not get the latest ${PACKAGE} version from ${base}: ${err.message}`);
4535
+ throw new CliError(
4536
+ `Could not get the latest ${PACKAGE} version from ${base}: ${err.message}`
4537
+ );
4189
4538
  }
4190
4539
  }
4191
4540
  function advice(install, latest) {
@@ -4235,8 +4584,10 @@ or run \`${PACKAGE} update --pm <${PMS.join("|")}>\`.`
4235
4584
  const result2 = windows ? (0, import_node_child_process4.spawnSync)(commandLine2(command), { stdio: [...stdio], env: options.env, shell: true }) : (0, import_node_child_process4.spawnSync)(command.cmd, command.args, { stdio: [...stdio], env: options.env });
4236
4585
  if (result2.stdout?.length) hooks.log?.(result2.stdout.toString());
4237
4586
  if (result2.stderr?.length) hooks.log?.(result2.stderr.toString());
4238
- if (result2.error) throw new CliError(`Could not run ${commandLine2(command)}: ${result2.error.message}`);
4239
- if (result2.status !== 0) throw new CliError(`${commandLine2(command)} exited with ${result2.status ?? result2.signal}`);
4587
+ if (result2.error)
4588
+ throw new CliError(`Could not run ${commandLine2(command)}: ${result2.error.message}`);
4589
+ if (result2.status !== 0)
4590
+ throw new CliError(`${commandLine2(command)} exited with ${result2.status ?? result2.signal}`);
4240
4591
  return {
4241
4592
  type: "updated",
4242
4593
  from: options.current,
@@ -4270,7 +4621,7 @@ var updateCommand = {
4270
4621
  try {
4271
4622
  report2 = await performUpdate(
4272
4623
  {
4273
- current: "1.63.0",
4624
+ current: "1.65.0",
4274
4625
  file: __filename,
4275
4626
  env: process.env,
4276
4627
  registry: process.env.npm_config_registry || "https://registry.npmjs.org/",
@@ -4286,7 +4637,10 @@ var updateCommand = {
4286
4637
  );
4287
4638
  } catch (error) {
4288
4639
  if (error instanceof InstallUnknownError)
4289
- throw new OutputError({ code: "E_INSTALL_UNKNOWN", message: error.message }, "action_required");
4640
+ throw new OutputError(
4641
+ { code: "E_INSTALL_UNKNOWN", message: error.message },
4642
+ "action_required"
4643
+ );
4290
4644
  throw error;
4291
4645
  }
4292
4646
  if (report2.type === "updated" && effect !== void 0)
@@ -4309,24 +4663,26 @@ var updateCommand = {
4309
4663
  };
4310
4664
  return result(data, void 0, { effects: ctx.effects.snapshot() });
4311
4665
  },
4312
- present({ data }) {
4313
- return {
4314
- heading: "Update",
4315
- status: data.status,
4316
- facts: [
4317
- fact("Current", field(data, "current"), true),
4318
- fact("Latest", field(data, "latest")),
4319
- fact("Install", field(data, "install")),
4320
- fact("Installed", field(data, "installed")),
4321
- ...data.message ? [fact("Advice", field(data, "message"))] : []
4322
- ],
4323
- sections: []
4324
- };
4325
- }
4666
+ present: presentUpdate
4326
4667
  },
4327
4668
  input
4328
4669
  )
4329
4670
  };
4671
+ function presentUpdate({
4672
+ data
4673
+ }) {
4674
+ return {
4675
+ heading: "Update",
4676
+ status: data.status,
4677
+ facts: [
4678
+ fact("Current", field(data, "current"), true),
4679
+ fact("Latest", field(data, "latest")),
4680
+ fact("Install", field(data, "install")),
4681
+ ...data.message ? [fact("Advice", field(data, "message"))] : []
4682
+ ],
4683
+ sections: []
4684
+ };
4685
+ }
4330
4686
 
4331
4687
  // src/cli/commands.ts
4332
4688
  var COMMANDS = [
@@ -4379,9 +4735,9 @@ async function semanticFailure(command, error, options, writer) {
4379
4735
  writer
4380
4736
  );
4381
4737
  }
4382
- function textDefinition(id, data) {
4738
+ function textDefinition(id2, data) {
4383
4739
  return {
4384
- id,
4740
+ id: id2,
4385
4741
  mode: "finite",
4386
4742
  output: {
4387
4743
  formats: ["brief", "json", "jsonl"],
@@ -4391,11 +4747,11 @@ function textDefinition(id, data) {
4391
4747
  return result(data);
4392
4748
  },
4393
4749
  present({ data: data2 }) {
4394
- return {
4395
- heading: id === "version" ? "Version" : "Help",
4396
- status: "ready",
4397
- facts: data2.version ? [fact("Version", field(data2, "version"), true)] : [],
4398
- sections: data2.version ? [] : [{ kind: "content", key: "text", text: data2.text }]
4750
+ return data2.version ? { heading: `ryugi-artifacts ${data2.version}`, status: "", facts: [], sections: [] } : {
4751
+ heading: "Help",
4752
+ status: "",
4753
+ facts: [],
4754
+ sections: [{ kind: "content", key: "text", text: data2.text }]
4399
4755
  };
4400
4756
  }
4401
4757
  };
@@ -4413,10 +4769,10 @@ async function dispatchSemantic(argv, commands, browseOptions, version, options,
4413
4769
  }
4414
4770
  if (name === "--help" || name === "-h" || command && wantsHelp(rest)) {
4415
4771
  if (!command) parse(rest, {}, 0);
4416
- const text = command ? commandHelp(command) : rootHelp(commands, browseOptions);
4772
+ const text2 = command ? commandHelp(command) : rootHelp(commands, browseOptions);
4417
4773
  return await bindCommand(
4418
4774
  textDefinition("help", {
4419
- text,
4775
+ text: text2,
4420
4776
  commands: commands.map(({ name: name2, summary }) => ({ name: name2, summary }))
4421
4777
  }),
4422
4778
  void 0
@@ -4474,7 +4830,7 @@ async function main(argv) {
4474
4830
  process.on("SIGINT", interrupt);
4475
4831
  process.on("SIGTERM", terminate);
4476
4832
  try {
4477
- return await dispatchSemantic(argv, COMMANDS, BROWSE_OPTIONS, "1.63.0", selection.options, writer, abort.signal);
4833
+ return await dispatchSemantic(argv, COMMANDS, BROWSE_OPTIONS, "1.65.0", selection.options, writer, abort.signal);
4478
4834
  } finally {
4479
4835
  process.removeListener("SIGINT", interrupt);
4480
4836
  process.removeListener("SIGTERM", terminate);