@orkestrel/terminal 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -228,7 +228,7 @@ var TerminalError = class extends Error {
228
228
  super(message);
229
229
  this.name = "TerminalError";
230
230
  this.code = code;
231
- this.context = context;
231
+ if (context !== void 0) this.context = context;
232
232
  }
233
233
  };
234
234
  /**
@@ -250,6 +250,33 @@ function isTerminalError(value) {
250
250
  return value instanceof TerminalError;
251
251
  }
252
252
  //#endregion
253
+ //#region src/core/validators.ts
254
+ /** Narrow an unknown value to a {@link PromptType} — one of the six prompt forms. */
255
+ var isPromptType = literalOf("input", "password", "confirm", "select", "checkbox", "editor");
256
+ /** Narrow an unknown value to a {@link PendingPromptStatus}. */
257
+ var isPendingPromptStatus = literalOf("pending", "answered", "expired");
258
+ /**
259
+ * Narrow an unknown wire value to a {@link PendingPrompt} before dispatching it.
260
+ *
261
+ * @param value - The decoded wire value to inspect
262
+ * @returns Whether `value` is a complete pending-prompt record
263
+ */
264
+ var isPendingPrompt = recordOf({
265
+ id: isNonEmptyString,
266
+ form: isPromptType,
267
+ message: isString,
268
+ options: isRecord,
269
+ status: isPendingPromptStatus,
270
+ time: isNumber,
271
+ from: isString,
272
+ to: isString
273
+ }, ["from", "to"]);
274
+ /** Narrow an unknown value to a {@link TerminalSnapshot}. */
275
+ var isTerminalSnapshot = recordOf({
276
+ id: isNonEmptyString,
277
+ timeout: isNumber
278
+ }, ["timeout"]);
279
+ //#endregion
253
280
  //#region src/core/helpers.ts
254
281
  /**
255
282
  * Decode one keypress's bytes into a {@link KeyEvent} — total, never throws. A `Uint8Array` is
@@ -513,6 +540,7 @@ function inputReduce(state, key) {
513
540
  view: inputView(state),
514
541
  status: "cancel"
515
542
  };
543
+ const { error: _error, ...clear } = state;
516
544
  if (key.name === "return") {
517
545
  const answer = state.value.length > 0 ? state.value : state.default;
518
546
  const result = state.validator(answer);
@@ -529,9 +557,8 @@ function inputReduce(state, key) {
529
557
  }
530
558
  return {
531
559
  state: {
532
- ...state,
533
- value: answer,
534
- error: void 0
560
+ ...clear,
561
+ value: answer
535
562
  },
536
563
  view: `${submitHeader(state.styler, state.message)} ${state.styler.dim(answer)}`,
537
564
  status: "submit",
@@ -545,9 +572,8 @@ function inputReduce(state, key) {
545
572
  status: "active"
546
573
  };
547
574
  const next = {
548
- ...state,
549
- value,
550
- error: void 0
575
+ ...clear,
576
+ value
551
577
  };
552
578
  return {
553
579
  state: next,
@@ -583,6 +609,7 @@ function passwordReduce(state, key) {
583
609
  view: passwordView(state),
584
610
  status: "cancel"
585
611
  };
612
+ const { error: _error, ...clear } = state;
586
613
  if (key.name === "return") {
587
614
  const result = state.validator(state.value);
588
615
  if (result !== true) {
@@ -597,10 +624,7 @@ function passwordReduce(state, key) {
597
624
  };
598
625
  }
599
626
  return {
600
- state: {
601
- ...state,
602
- error: void 0
603
- },
627
+ state: clear,
604
628
  view: `${submitHeader(state.styler, state.message)} ${state.styler.dim(state.mask.repeat(state.value.length))}`,
605
629
  status: "submit",
606
630
  value: state.value
@@ -613,9 +637,8 @@ function passwordReduce(state, key) {
613
637
  status: "active"
614
638
  };
615
639
  const next = {
616
- ...state,
617
- value,
618
- error: void 0
640
+ ...clear,
641
+ value
619
642
  };
620
643
  return {
621
644
  state: next,
@@ -752,8 +775,8 @@ function createCheckboxState(options) {
752
775
  styler: options.styler ?? createStyler(),
753
776
  focused: 0,
754
777
  checked,
755
- min: options.min,
756
- max: options.max
778
+ ...options.min !== void 0 ? { min: options.min } : {},
779
+ ...options.max !== void 0 ? { max: options.max } : {}
757
780
  };
758
781
  }
759
782
  /** Render a {@link CheckboxState} as a MULTI-LINE styled view — the header, one box per choice (focused + checked marked), a count, and any error. */
@@ -785,11 +808,11 @@ function checkboxReduce(state, key) {
785
808
  status: "cancel"
786
809
  };
787
810
  const count = state.choices.length;
811
+ const { error: _error, ...clear } = state;
788
812
  if ((key.name === "up" || key.name === "k") && count > 0) {
789
813
  const next = {
790
- ...state,
791
- focused: (state.focused - 1 + count) % count,
792
- error: void 0
814
+ ...clear,
815
+ focused: (state.focused - 1 + count) % count
793
816
  };
794
817
  return {
795
818
  state: next,
@@ -799,9 +822,8 @@ function checkboxReduce(state, key) {
799
822
  }
800
823
  if ((key.name === "down" || key.name === "j") && count > 0) {
801
824
  const next = {
802
- ...state,
803
- focused: (state.focused + 1) % count,
804
- error: void 0
825
+ ...clear,
826
+ focused: (state.focused + 1) % count
805
827
  };
806
828
  return {
807
829
  state: next,
@@ -812,9 +834,8 @@ function checkboxReduce(state, key) {
812
834
  if (key.name === "space" && count > 0) {
813
835
  const checked = toggleIndex(state.checked, state.focused);
814
836
  const next = {
815
- ...state,
816
- checked,
817
- error: void 0
837
+ ...clear,
838
+ checked
818
839
  };
819
840
  return {
820
841
  state: next,
@@ -839,10 +860,7 @@ function checkboxReduce(state, key) {
839
860
  const values = ordered.map((index) => state.choices[index]?.value).filter((value) => value !== void 0);
840
861
  const summary = ordered.map((index) => state.choices[index]?.name).filter((name) => name !== void 0).join(", ");
841
862
  return {
842
- state: {
843
- ...state,
844
- error: void 0
845
- },
863
+ state: clear,
846
864
  view: `${submitHeader(state.styler, state.message)} ${state.styler.dim(summary)}`,
847
865
  status: "submit",
848
866
  value: values
@@ -892,6 +910,7 @@ function editorReduce(state, key) {
892
910
  view: editorView(state),
893
911
  status: "cancel"
894
912
  };
913
+ const { error: _error, ...clear } = state;
895
914
  if (key.ctrl && key.name === "d") {
896
915
  const lines = state.current.length > 0 ? [...state.lines, state.current] : state.lines;
897
916
  const joined = lines.join("\n");
@@ -909,10 +928,7 @@ function editorReduce(state, key) {
909
928
  };
910
929
  }
911
930
  return {
912
- state: {
913
- ...state,
914
- error: void 0
915
- },
931
+ state: clear,
916
932
  view: `${submitHeader(state.styler, state.message)} ${state.styler.dim(`${String(lines.length)} line${lines.length === 1 ? "" : "s"}`)}`,
917
933
  status: "submit",
918
934
  value: answer
@@ -920,10 +936,9 @@ function editorReduce(state, key) {
920
936
  }
921
937
  if (key.name === "return") {
922
938
  const next = {
923
- ...state,
939
+ ...clear,
924
940
  lines: [...state.lines, state.current],
925
- current: "",
926
- error: void 0
941
+ current: ""
927
942
  };
928
943
  return {
929
944
  state: next,
@@ -938,9 +953,8 @@ function editorReduce(state, key) {
938
953
  status: "active"
939
954
  };
940
955
  const next = {
941
- ...state,
942
- current,
943
- error: void 0
956
+ ...clear,
957
+ current
944
958
  };
945
959
  return {
946
960
  state: next,
@@ -1020,24 +1034,6 @@ function serializeChoices(choices) {
1020
1034
  return normalized;
1021
1035
  });
1022
1036
  }
1023
- /** Narrow an unknown value to a {@link PromptType} — one of the six prompt forms. */
1024
- var isPromptType = literalOf("input", "password", "confirm", "select", "checkbox", "editor");
1025
- /** Narrow an unknown value to a {@link PendingPromptStatus}. */
1026
- var isPendingPromptStatus = literalOf("pending", "answered", "expired");
1027
- /**
1028
- * Narrow an unknown wire value to a {@link PendingPrompt} — the §14 guard a {@link PromptClient}
1029
- * applies to each decoded SSE `pending` payload before dispatching it (never an `as`).
1030
- */
1031
- var isPendingPrompt = recordOf({
1032
- id: isNonEmptyString,
1033
- form: isPromptType,
1034
- message: isString,
1035
- options: isRecord,
1036
- status: isPendingPromptStatus,
1037
- time: isNumber,
1038
- from: isString,
1039
- to: isString
1040
- }, ["from", "to"]);
1041
1037
  /**
1042
1038
  * Rebuild a wire-decoded `validate` payload into a {@link ValidationRules} bag — the inverse of
1043
1039
  * {@link serializeValidationRules}. Keeps only the primitive rule values (`boolean` / `number` /
@@ -1103,11 +1099,10 @@ function resolveChoices(options, guard) {
1103
1099
  function sanitizeChoiceLabels(choices) {
1104
1100
  return choices.map((choice) => {
1105
1101
  if (isString(choice)) return stripControls(choice);
1106
- const description = choice.description === void 0 ? void 0 : stripControls(choice.description);
1107
1102
  return {
1108
1103
  ...choice,
1109
1104
  name: stripControls(choice.name),
1110
- description
1105
+ ...choice.description !== void 0 ? { description: stripControls(choice.description) } : {}
1111
1106
  };
1112
1107
  });
1113
1108
  }
@@ -1131,42 +1126,49 @@ function dispatchPendingPrompt(terminal, pending) {
1131
1126
  const value = resolveOption(options, "default", isString);
1132
1127
  return terminal.input({
1133
1128
  message,
1134
- default: value === void 0 ? void 0 : stripControls(value),
1135
- validate
1129
+ ...value !== void 0 ? { default: stripControls(value) } : {},
1130
+ ...validate !== void 0 ? { validate } : {}
1136
1131
  });
1137
1132
  }
1138
1133
  case "password": {
1139
1134
  const value = resolveOption(options, "mask", isString);
1140
1135
  return terminal.password({
1141
1136
  message,
1142
- mask: value === void 0 ? void 0 : stripControls(value),
1143
- validate
1137
+ ...value !== void 0 ? { mask: stripControls(value) } : {},
1138
+ ...validate !== void 0 ? { validate } : {}
1139
+ });
1140
+ }
1141
+ case "confirm": {
1142
+ const value = resolveOption(options, "default", isBoolean);
1143
+ return terminal.confirm({
1144
+ message,
1145
+ ...value !== void 0 ? { default: value } : {}
1144
1146
  });
1145
1147
  }
1146
- case "confirm": return terminal.confirm({
1147
- message,
1148
- default: resolveOption(options, "default", isBoolean)
1149
- });
1150
1148
  case "select": {
1151
1149
  const value = resolveOption(options, "default", isString);
1152
1150
  return terminal.select({
1153
1151
  message,
1154
1152
  choices: sanitizeChoiceLabels(resolveChoices(options, isPromptChoice)),
1155
- default: value === void 0 ? void 0 : stripControls(value)
1153
+ ...value !== void 0 ? { default: stripControls(value) } : {}
1154
+ });
1155
+ }
1156
+ case "checkbox": {
1157
+ const min = resolveOption(options, "min", isNumber);
1158
+ const max = resolveOption(options, "max", isNumber);
1159
+ return terminal.checkbox({
1160
+ message,
1161
+ choices: sanitizeChoiceLabels(resolveChoices(options, isCheckboxChoice)),
1162
+ ...min !== void 0 ? { min } : {},
1163
+ ...max !== void 0 ? { max } : {}
1156
1164
  });
1157
1165
  }
1158
- case "checkbox": return terminal.checkbox({
1159
- message,
1160
- choices: sanitizeChoiceLabels(resolveChoices(options, isCheckboxChoice)),
1161
- min: resolveOption(options, "min", isNumber),
1162
- max: resolveOption(options, "max", isNumber)
1163
- });
1164
1166
  case "editor": {
1165
1167
  const value = resolveOption(options, "default", isString);
1166
1168
  return terminal.editor({
1167
1169
  message,
1168
- default: value === void 0 ? void 0 : stripControls(value),
1169
- validate
1170
+ ...value !== void 0 ? { default: stripControls(value) } : {},
1171
+ ...validate !== void 0 ? { validate } : {}
1170
1172
  });
1171
1173
  }
1172
1174
  }
@@ -1261,11 +1263,6 @@ function serializeShutdown() {
1261
1263
  function isAnswerPayload(value) {
1262
1264
  return isRecord(value) && isNonEmptyString(value.id) && "value" in value;
1263
1265
  }
1264
- /** Narrow an unknown value to a {@link TerminalSnapshot} — a non-empty `id` plus an optional numeric `timeout`. */
1265
- var isTerminalSnapshot = recordOf({
1266
- id: isNonEmptyString,
1267
- timeout: isNumber
1268
- }, ["timeout"]);
1269
1266
  //#endregion
1270
1267
  //#region src/core/Prompt.ts
1271
1268
  /**
@@ -1314,8 +1311,8 @@ var Prompt = class {
1314
1311
  this.#timer = options?.timer ?? defaultTimer;
1315
1312
  this.#cap = options?.cap;
1316
1313
  this.#emitter = new Emitter({
1317
- on: options?.on,
1318
- error: options?.error
1314
+ ...options?.on !== void 0 ? { on: options.on } : {},
1315
+ ...options?.error !== void 0 ? { error: options.error } : {}
1319
1316
  });
1320
1317
  }
1321
1318
  get emitter() {
@@ -1402,23 +1399,8 @@ var Prompt = class {
1402
1399
  id,
1403
1400
  value: new Promise((resolve, reject) => {
1404
1401
  const cancel = this.#timer(() => this.#expire(id), this.#timeout);
1405
- const respond = (answer) => {
1406
- const accepted = gate(answer);
1407
- if (accepted === void 0) return void 0;
1408
- const current = this.#parked.get(id);
1409
- if (current !== void 0) this.#parked.set(id, {
1410
- ...current,
1411
- prompt: {
1412
- ...current.prompt,
1413
- status: "answered"
1414
- }
1415
- });
1416
- resolve(accepted);
1417
- return accepted;
1418
- };
1419
- const expire = () => {
1420
- reject(new TerminalError("EXPIRE", `prompt ${id} expired`, { id }));
1421
- };
1402
+ const respond = this.#createRespond(id, gate, resolve);
1403
+ const expire = this.#createExpire(id, reject);
1422
1404
  this.#parked.set(id, {
1423
1405
  prompt,
1424
1406
  respond,
@@ -1429,6 +1411,25 @@ var Prompt = class {
1429
1411
  })
1430
1412
  };
1431
1413
  }
1414
+ #createRespond(id, gate, resolve) {
1415
+ return (answer) => {
1416
+ const accepted = gate(answer);
1417
+ if (accepted === void 0) return void 0;
1418
+ const current = this.#parked.get(id);
1419
+ if (current !== void 0) this.#parked.set(id, {
1420
+ ...current,
1421
+ prompt: {
1422
+ ...current.prompt,
1423
+ status: "answered"
1424
+ }
1425
+ });
1426
+ resolve(accepted);
1427
+ return accepted;
1428
+ };
1429
+ }
1430
+ #createExpire(id, reject) {
1431
+ return () => reject(new TerminalError("EXPIRE", `prompt ${id} expired`, { id }));
1432
+ }
1432
1433
  #gate(form, options) {
1433
1434
  switch (form) {
1434
1435
  case "input":
@@ -1554,8 +1555,8 @@ var PromptClient = class {
1554
1555
  this.#fetch = options.fetch ?? globalFetch;
1555
1556
  this.#timer = options.timer ?? defaultTimer;
1556
1557
  this.#emitter = new Emitter({
1557
- on: options.on,
1558
- error: options.error
1558
+ ...options.on !== void 0 ? { on: options.on } : {},
1559
+ ...options.error !== void 0 ? { error: options.error } : {}
1559
1560
  });
1560
1561
  }
1561
1562
  get emitter() {
@@ -1679,17 +1680,18 @@ var PromptClient = class {
1679
1680
  }
1680
1681
  #wait(ms) {
1681
1682
  return new Promise((resolve) => {
1682
- const settle = () => {
1683
- this.#backoff = void 0;
1684
- this.#wake = void 0;
1685
- resolve();
1686
- };
1683
+ const settle = this.#createSettler(resolve);
1687
1684
  this.#wake = settle;
1688
- this.#backoff = this.#timer(() => {
1689
- settle();
1690
- }, ms);
1685
+ this.#backoff = this.#timer(settle, ms);
1691
1686
  });
1692
1687
  }
1688
+ #createSettler(resolve) {
1689
+ return () => {
1690
+ this.#backoff = void 0;
1691
+ this.#wake = void 0;
1692
+ resolve();
1693
+ };
1694
+ }
1693
1695
  };
1694
1696
  //#endregion
1695
1697
  //#region src/core/TerminalManager.ts
@@ -1742,8 +1744,8 @@ var TerminalManager = class {
1742
1744
  this.#timer = options?.timer;
1743
1745
  this.#cap = options?.cap;
1744
1746
  this.#emitter = new Emitter({
1745
- on: options?.on,
1746
- error: options?.error
1747
+ ...options?.on !== void 0 ? { on: options.on } : {},
1748
+ ...options?.error !== void 0 ? { error: options.error } : {}
1747
1749
  });
1748
1750
  }
1749
1751
  get emitter() {
@@ -1771,9 +1773,9 @@ var TerminalManager = class {
1771
1773
  ...cap !== void 0 ? { cap } : {}
1772
1774
  });
1773
1775
  const listeners = {
1774
- pending: (prompt) => this.#emitter.emit("pending", prompt),
1775
- answer: (id, value) => this.#emitter.emit("answer", name, id, value),
1776
- expire: (id) => this.#emitter.emit("expire", name, id)
1776
+ pending: this.#createPendingListener(),
1777
+ answer: this.#createAnswerListener(name),
1778
+ expire: this.#createExpireListener(name)
1777
1779
  };
1778
1780
  broker.emitter.on("pending", listeners.pending);
1779
1781
  broker.emitter.on("answer", listeners.answer);
@@ -1809,9 +1811,7 @@ var TerminalManager = class {
1809
1811
  from,
1810
1812
  to
1811
1813
  });
1812
- const clear = () => {
1813
- this.#edges.delete(ticket.id);
1814
- };
1814
+ const clear = this.#createEdgeClear(ticket.id);
1815
1815
  ticket.value.then(clear, clear);
1816
1816
  }
1817
1817
  return ticket.value;
@@ -1871,6 +1871,20 @@ var TerminalManager = class {
1871
1871
  this.clear();
1872
1872
  this.#emitter.destroy();
1873
1873
  }
1874
+ #createPendingListener() {
1875
+ return (prompt) => this.#emitter.emit("pending", prompt);
1876
+ }
1877
+ #createAnswerListener(name) {
1878
+ return (id, value) => this.#emitter.emit("answer", name, id, value);
1879
+ }
1880
+ #createExpireListener(name) {
1881
+ return (id) => this.#emitter.emit("expire", name, id);
1882
+ }
1883
+ #createEdgeClear(id) {
1884
+ return () => {
1885
+ this.#edges.delete(id);
1886
+ };
1887
+ }
1874
1888
  #removeOne(name) {
1875
1889
  const broker = this.#terminals.get(name);
1876
1890
  if (broker === void 0) return false;
@@ -1982,7 +1996,7 @@ var MemoryTerminalStore = class {
1982
1996
  * - **`set(snapshot)` upserts under the snapshot's OWN `id`** (no separate id param) — it writes
1983
1997
  * the row `{ id: snapshot.id, snapshot }`.
1984
1998
  * - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to
1985
- * a {@link TerminalSnapshot} ({@link import('./helpers.js').isTerminalSnapshot} — the AGENTS §14
1999
+ * a {@link TerminalSnapshot} ({@link import('./validators.js').isTerminalSnapshot} — the AGENTS §14
1986
2000
  * boundary narrow for an untrusted storage read), or `undefined` if none is stored.
1987
2001
  * - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
1988
2002
  *