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