@chances-ai/tui 14.0.0 → 16.0.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.
Files changed (45) hide show
  1. package/dist/api-key-prompt.d.ts +8 -15
  2. package/dist/api-key-prompt.d.ts.map +1 -1
  3. package/dist/api-key-prompt.js +18 -30
  4. package/dist/api-key-prompt.js.map +1 -1
  5. package/dist/app.d.ts +5 -8
  6. package/dist/app.d.ts.map +1 -1
  7. package/dist/app.js +167 -100
  8. package/dist/app.js.map +1 -1
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/modal.d.ts +3 -2
  14. package/dist/modal.d.ts.map +1 -1
  15. package/dist/modal.js +3 -2
  16. package/dist/modal.js.map +1 -1
  17. package/dist/model-picker.d.ts +7 -39
  18. package/dist/model-picker.d.ts.map +1 -1
  19. package/dist/model-picker.js +14 -67
  20. package/dist/model-picker.js.map +1 -1
  21. package/dist/select-multi.d.ts +36 -0
  22. package/dist/select-multi.d.ts.map +1 -0
  23. package/dist/select-multi.js +104 -0
  24. package/dist/select-multi.js.map +1 -0
  25. package/dist/select.d.ts +63 -0
  26. package/dist/select.d.ts.map +1 -0
  27. package/dist/select.js +122 -0
  28. package/dist/select.js.map +1 -0
  29. package/dist/session-picker.d.ts +7 -15
  30. package/dist/session-picker.d.ts.map +1 -1
  31. package/dist/session-picker.js +14 -42
  32. package/dist/session-picker.js.map +1 -1
  33. package/dist/slash-typeahead.d.ts +34 -0
  34. package/dist/slash-typeahead.d.ts.map +1 -0
  35. package/dist/slash-typeahead.js +78 -0
  36. package/dist/slash-typeahead.js.map +1 -0
  37. package/dist/user-question.d.ts +26 -0
  38. package/dist/user-question.d.ts.map +1 -0
  39. package/dist/user-question.js +140 -0
  40. package/dist/user-question.js.map +1 -0
  41. package/dist/view-model.d.ts +9 -2
  42. package/dist/view-model.d.ts.map +1 -1
  43. package/dist/view-model.js +41 -13
  44. package/dist/view-model.js.map +1 -1
  45. package/package.json +6 -5
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EACL,OAAO,EACP,oBAAoB,EACpB,uBAAuB,GAGxB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,eAAe,EAAkB,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAsB,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,aAAa,EAAwB,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,wEAAwE;AACxE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAiC,MAAM,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAczC;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CACxB,EAAiB,EACjB,QAA2C,EAC3C,OAAyC,EACzC,KAAuB,EACvB,sBAA6D,EAC7D,kBAA4D,EAC5D,OAA2B;IAE3B,MAAM,QAAQ,GAAG,MAAM,CACrB,KAAC,OAAO,IACN,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,EACZ,sBAAsB,EAAE,sBAAsB,EAC9C,kBAAkB,EAAE,kBAAkB,EACtC,YAAY,EAAE,OAAO,EAAE,YAAY,EACnC,eAAe,EAAE,OAAO,EAAE,eAAe,GACzC,CACH,CAAC;IACF,OAAO;QACL,EAAE;QACF,mEAAmE;QACnE,yEAAyE;QACzE,sEAAsE;QACtE,aAAa,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;QACnE,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE;KAClC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EACL,OAAO,EACP,oBAAoB,EACpB,uBAAuB,GAGxB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAkB,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAsB,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,aAAa,EAAwB,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,oEAAoE;AACpE,OAAO,EACL,MAAM,EAGN,aAAa,EACb,UAAU,EACV,UAAU,EACV,aAAa,EACb,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,wEAAwE;AACxE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAiC,MAAM,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAczC;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CACxB,EAAiB,EACjB,QAA2C,EAC3C,OAAyC,EACzC,KAAuB,EACvB,sBAA6D,EAC7D,kBAA4D,EAC5D,OAA2B;IAE3B,MAAM,QAAQ,GAAG,MAAM,CACrB,KAAC,OAAO,IACN,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,EACZ,sBAAsB,EAAE,sBAAsB,EAC9C,kBAAkB,EAAE,kBAAkB,EACtC,YAAY,EAAE,OAAO,EAAE,YAAY,EACnC,eAAe,EAAE,OAAO,EAAE,eAAe,GACzC,CACH,CAAC;IACF,OAAO;QACL,EAAE;QACF,mEAAmE;QACnE,yEAAyE;QACzE,sEAAsE;QACtE,aAAa,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;QACnE,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE;KAClC,CAAC;AACJ,CAAC"}
package/dist/modal.d.ts CHANGED
@@ -24,8 +24,9 @@ export declare class ModalController {
24
24
  */
25
25
  attach(setter: (node: ReactNode) => void): () => void;
26
26
  open: <T>(render: (resolve: (value: T | null) => void) => unknown) => Promise<T | null>;
27
- /** True when a modal is currently displayed. Used by ChatApp.useInput to
28
- * suppress chat input handling while the modal owns the keystrokes. */
27
+ /** True when a modal is currently displayed. (Since 5.10 the modal's own
28
+ * `Select`/`Modal` keybinding context masks chat input structurally, so this
29
+ * is no longer needed to gate input — it remains a small status accessor.) */
29
30
  isActive(): boolean;
30
31
  }
31
32
  export type { OpenModal };
@@ -1 +1 @@
1
- {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../src/modal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAA4C;IAC3D,OAAO,CAAC,MAAM,CAAqD;IAEnE;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI;IAcrD,IAAI,GAAI,CAAC,EAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,KAAK,OAAO,KAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAmBrF;IAEF;2EACuE;IACvE,QAAQ,IAAI,OAAO;CAGpB;AAED,YAAY,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../src/modal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAA4C;IAC3D,OAAO,CAAC,MAAM,CAAqD;IAEnE;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI;IAcrD,IAAI,GAAI,CAAC,EAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,KAAK,OAAO,KAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAmBrF;IAEF;;kFAE8E;IAC9E,QAAQ,IAAI,OAAO;CAGpB;AAED,YAAY,EAAE,SAAS,EAAE,CAAC"}
package/dist/modal.js CHANGED
@@ -54,8 +54,9 @@ export class ModalController {
54
54
  this.setNode?.(node);
55
55
  });
56
56
  };
57
- /** True when a modal is currently displayed. Used by ChatApp.useInput to
58
- * suppress chat input handling while the modal owns the keystrokes. */
57
+ /** True when a modal is currently displayed. (Since 5.10 the modal's own
58
+ * `Select`/`Modal` keybinding context masks chat input structurally, so this
59
+ * is no longer needed to gate input — it remains a small status accessor.) */
59
60
  isActive() {
60
61
  return this.active !== null;
61
62
  }
package/dist/modal.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"modal.js","sourceRoot":"","sources":["../src/modal.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,eAAe;IAClB,OAAO,GAAuC,IAAI,CAAC;IACnD,MAAM,GAAgD,IAAI,CAAC;IAEnE;;;OAGG;IACH,MAAM,CAAC,MAAiC;QACtC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,OAAO,GAAG,EAAE;YACV,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM;gBAAE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACjD,gEAAgE;YAChE,mEAAmE;YACnE,6BAA6B;YAC7B,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBAChC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACrB,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAK,MAAuD,EAAqB,EAAE;QACxF,OAAO,IAAI,OAAO,CAAW,CAAC,cAAc,EAAE,EAAE;YAC9C,uEAAuE;YACvE,IAAI,IAAI,CAAC,MAAM;gBAAE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAEjD,MAAM,WAAW,GAAG,CAAC,KAAe,EAAQ,EAAE;gBAC5C,iEAAiE;gBACjE,+DAA+D;gBAC/D,gDAAgD;gBAChD,IAAI,IAAI,CAAC,MAAM,EAAE,aAAa,KAAM,WAAiC,EAAE,CAAC;oBACtE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACrB,CAAC;gBACD,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;gBACrB,cAAc,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC,CAAC;YACF,IAAI,CAAC,MAAM,GAAG,EAAE,aAAa,EAAE,WAAgC,EAAE,CAAC;YAClE,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAc,CAAC;YAC9C,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF;2EACuE;IACvE,QAAQ;QACN,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;IAC9B,CAAC;CACF"}
1
+ {"version":3,"file":"modal.js","sourceRoot":"","sources":["../src/modal.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,eAAe;IAClB,OAAO,GAAuC,IAAI,CAAC;IACnD,MAAM,GAAgD,IAAI,CAAC;IAEnE;;;OAGG;IACH,MAAM,CAAC,MAAiC;QACtC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,OAAO,GAAG,EAAE;YACV,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM;gBAAE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACjD,gEAAgE;YAChE,mEAAmE;YACnE,6BAA6B;YAC7B,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBAChC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACrB,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAK,MAAuD,EAAqB,EAAE;QACxF,OAAO,IAAI,OAAO,CAAW,CAAC,cAAc,EAAE,EAAE;YAC9C,uEAAuE;YACvE,IAAI,IAAI,CAAC,MAAM;gBAAE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAEjD,MAAM,WAAW,GAAG,CAAC,KAAe,EAAQ,EAAE;gBAC5C,iEAAiE;gBACjE,+DAA+D;gBAC/D,gDAAgD;gBAChD,IAAI,IAAI,CAAC,MAAM,EAAE,aAAa,KAAM,WAAiC,EAAE,CAAC;oBACtE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACrB,CAAC;gBACD,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;gBACrB,cAAc,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC,CAAC;YACF,IAAI,CAAC,MAAM,GAAG,EAAE,aAAa,EAAE,WAAgC,EAAE,CAAC;YAClE,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAc,CAAC;YAC9C,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF;;kFAE8E;IAC9E,QAAQ;QACN,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;IAC9B,CAAC;CACF"}
@@ -1,4 +1,4 @@
1
- import { type JSX } from "react";
1
+ import type { JSX } from "react";
2
2
  export interface PickableModel {
3
3
  /** Display id, e.g. "claude-opus-4-7". */
4
4
  id: string;
@@ -7,45 +7,13 @@ export interface PickableModel {
7
7
  /** True for the currently-active model, rendered with a marker. */
8
8
  current?: boolean;
9
9
  }
10
- export interface ModelPickerState {
11
- filter: string;
12
- cursor: number;
13
- }
14
- /** Subset of Ink's Key shape that the reducer cares about. Defined locally so
15
- * the reducer has no dependency on Ink at all — unit tests can drive it as
16
- * plain data without needing a TTY. */
17
- export interface PickerKey {
18
- return?: boolean;
19
- escape?: boolean;
20
- upArrow?: boolean;
21
- downArrow?: boolean;
22
- backspace?: boolean;
23
- delete?: boolean;
24
- ctrl?: boolean;
25
- meta?: boolean;
26
- }
27
- /** Subset of the active match list the reducer needs — kept as a generic
28
- * function over PickableModel for ergonomics. */
29
- export declare function filterModels(models: PickableModel[], filter: string): PickableModel[];
30
- /**
31
- * Pure reducer for the ModelPicker. Returns the next state plus an optional
32
- * `resolved` value: a `PickableModel` when the user picked, `null` when they
33
- * cancelled (Esc or Enter on empty matches), `undefined` when the keystroke
34
- * was a navigation/filter edit and the picker should stay open.
35
- *
36
- * The component just plumbs Ink's useInput into this function — extracted so
37
- * the keystroke logic is testable without ink-testing-library's stdin
38
- * limitations.
39
- */
40
- export declare function reduceModelPicker(state: ModelPickerState, char: string, key: PickerKey, models: PickableModel[]): {
41
- state: ModelPickerState;
42
- resolved?: PickableModel | null;
43
- };
44
10
  /**
45
- * Fuzzy-filter model picker modeled on oh-my-pi's `model-selector.ts`. Arrow
46
- * keys move the cursor, Enter selects, Esc cancels (resolves null). All
47
- * keystroke logic lives in {@link reduceModelPicker} so it's testable as a
48
- * pure function this component is just glue to Ink's useInput.
11
+ * (5.10 A4) Fuzzy-filter model picker now a thin wrapper over the generic
12
+ * {@link Select} (which owns all keystroke logic via the ink-ext `Select`
13
+ * context). The old per-component `useInput` + `reduceModelPicker` reducer are
14
+ * gone; their behavior (filter, ↑↓ clamp, Enter/Esc) is the Select's, covered by
15
+ * `select.test`. Signature is unchanged so `apps/cli/src/slash/model.ts` still
16
+ * renders `<ModelPicker models onChoose/>` verbatim.
49
17
  */
50
18
  export declare function ModelPicker({ models, onChoose, }: {
51
19
  models: PickableModel[];
@@ -1 +1 @@
1
- {"version":3,"file":"model-picker.d.ts","sourceRoot":"","sources":["../src/model-picker.tsx"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AAG3C,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;uCAEuC;AACvC,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;iDACiD;AACjD,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAIrF;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,aAAa,EAAE,GACtB;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,EAAE,aAAa,GAAG,IAAI,CAAA;CAAE,CAsB9D;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,KAAK,IAAI,CAAC;CACjD,GAAG,GAAG,CAAC,OAAO,CA6Cd"}
1
+ {"version":3,"file":"model-picker.d.ts","sourceRoot":"","sources":["../src/model-picker.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAGjC,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,KAAK,IAAI,CAAC;CACjD,GAAG,GAAG,CAAC,OAAO,CAed"}
@@ -1,72 +1,19 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text, useInput } from "ink";
3
- import { useState } from "react";
4
- import { useTheme } from "./theme-context.js";
5
- /** Subset of the active match list the reducer needs — kept as a generic
6
- * function over PickableModel for ergonomics. */
7
- export function filterModels(models, filter) {
8
- const lower = filter.toLowerCase();
9
- if (!lower)
10
- return models;
11
- return models.filter((m) => `${m.provider}/${m.id}`.toLowerCase().includes(lower));
12
- }
13
- /**
14
- * Pure reducer for the ModelPicker. Returns the next state plus an optional
15
- * `resolved` value: a `PickableModel` when the user picked, `null` when they
16
- * cancelled (Esc or Enter on empty matches), `undefined` when the keystroke
17
- * was a navigation/filter edit and the picker should stay open.
18
- *
19
- * The component just plumbs Ink's useInput into this function — extracted so
20
- * the keystroke logic is testable without ink-testing-library's stdin
21
- * limitations.
22
- */
23
- export function reduceModelPicker(state, char, key, models) {
24
- const matches = filterModels(models, state.filter);
25
- const safeCursor = Math.min(state.cursor, Math.max(0, matches.length - 1));
26
- if (key.escape)
27
- return { state, resolved: null };
28
- if (key.return) {
29
- const choice = matches[safeCursor];
30
- return { state, resolved: choice ?? null };
31
- }
32
- if (key.upArrow) {
33
- return { state: { ...state, cursor: Math.max(0, safeCursor - 1) } };
34
- }
35
- if (key.downArrow) {
36
- return { state: { ...state, cursor: Math.min(matches.length - 1, safeCursor + 1) } };
37
- }
38
- if (key.backspace || key.delete) {
39
- return { state: { filter: state.filter.slice(0, -1), cursor: 0 } };
40
- }
41
- if (char && !key.ctrl && !key.meta) {
42
- return { state: { filter: state.filter + char, cursor: 0 } };
43
- }
44
- return { state };
45
- }
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Select } from "./select.js";
46
3
  /**
47
- * Fuzzy-filter model picker modeled on oh-my-pi's `model-selector.ts`. Arrow
48
- * keys move the cursor, Enter selects, Esc cancels (resolves null). All
49
- * keystroke logic lives in {@link reduceModelPicker} so it's testable as a
50
- * pure function this component is just glue to Ink's useInput.
4
+ * (5.10 A4) Fuzzy-filter model picker now a thin wrapper over the generic
5
+ * {@link Select} (which owns all keystroke logic via the ink-ext `Select`
6
+ * context). The old per-component `useInput` + `reduceModelPicker` reducer are
7
+ * gone; their behavior (filter, ↑↓ clamp, Enter/Esc) is the Select's, covered by
8
+ * `select.test`. Signature is unchanged so `apps/cli/src/slash/model.ts` still
9
+ * renders `<ModelPicker models onChoose/>` verbatim.
51
10
  */
52
11
  export function ModelPicker({ models, onChoose, }) {
53
- const { theme } = useTheme();
54
- const [state, setState] = useState({ filter: "", cursor: 0 });
55
- useInput((char, key) => {
56
- const { state: next, resolved } = reduceModelPicker(state, char, key, models);
57
- if (resolved !== undefined) {
58
- onChoose(resolved);
59
- return;
60
- }
61
- if (next !== state)
62
- setState(next);
63
- });
64
- const matches = filterModels(models, state.filter);
65
- const safeCursor = Math.min(state.cursor, Math.max(0, matches.length - 1));
66
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.permission, paddingX: 1, children: [_jsx(Text, { color: theme.permission, children: "Pick a model \u00B7 type to filter \u00B7 \u2191\u2193 \u00B7 Enter \u00B7 Esc" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "filter: " }), _jsx(Text, { color: theme.warning, children: state.filter || "(any)" })] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [matches.length === 0 ? (_jsx(Text, { dimColor: true, children: "no models match" })) : (matches.slice(0, 10).map((m, i) => {
67
- const selected = i === safeCursor;
68
- const marker = m.current ? "●" : " ";
69
- return (_jsxs(Text, { color: selected ? theme.permission : undefined, children: [selected ? "▸ " : " ", marker, " ", _jsxs(Text, { dimColor: true, children: [m.provider, "/"] }), m.id] }, `${m.provider}/${m.id}`));
70
- })), matches.length > 10 ? (_jsxs(Text, { dimColor: true, children: ["\u2026 ", matches.length - 10, " more (type to narrow)"] })) : null] })] }));
12
+ const options = models.map((m) => ({
13
+ label: `${m.provider}/${m.id}`,
14
+ value: m,
15
+ current: m.current,
16
+ }));
17
+ return (_jsx(Select, { heading: "Pick a model \u00B7 type to filter \u00B7 \u2191\u2193 \u00B7 Enter \u00B7 Esc", options: options, filterable: true, onSelect: onChoose, onCancel: () => onChoose(null) }));
71
18
  }
72
19
  //# sourceMappingURL=model-picker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"model-picker.js","sourceRoot":"","sources":["../src/model-picker.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"model-picker.js","sourceRoot":"","sources":["../src/model-picker.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAqB,MAAM,aAAa,CAAC;AAWxD;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,EAC1B,MAAM,EACN,QAAQ,GAIT;IACC,MAAM,OAAO,GAAkC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChE,KAAK,EAAE,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,EAAE,EAAE;QAC9B,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,CAAC,CAAC,OAAO;KACnB,CAAC,CAAC,CAAC;IACJ,OAAO,CACL,KAAC,MAAM,IACL,OAAO,EAAC,gFAAkD,EAC1D,OAAO,EAAE,OAAO,EAChB,UAAU,QACV,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,GAC9B,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { type JSX, type ReactNode } from "react";
2
+ import { type SelectOption } from "./select.js";
3
+ /**
4
+ * (5.10b A2) The ONE generic multi-select primitive — `[ ]`/`[✓]` rows + a
5
+ * trailing Submit row. The single source of truth for the selection lives in
6
+ * this component as data (a `T[]`), NOT in hand-rolled `"☑ label"` strings — the
7
+ * exact anti-pattern oh-my-pi's `ask.ts` fell into (it concatenated checkbox
8
+ * glyphs into option strings and parsed them back out). Navigation comes from
9
+ * the shared `Select` keybinding context (ink-ext A1); this component owns no
10
+ * `useInput`. The inline free-text "Other" row is the consumer's job
11
+ * (`QuestionView` injects an option + a text field) — keeping this primitive a
12
+ * pure checkbox list.
13
+ */
14
+ /** Toggle a value in the selection (XOR): add if absent, remove if present. */
15
+ export declare function toggleSelected<T>(selected: readonly T[], value: T): T[];
16
+ /** Move the cursor over `options` PLUS a trailing Submit row (index =
17
+ * `options.length`), clamped to `[0, options.length]` (no wrap), skipping
18
+ * disabled options in the direction of travel. The Submit row is always
19
+ * selectable. Returns the original cursor when there's nowhere to go. */
20
+ export declare function moveMultiCursor<T>(options: SelectOption<T>[], cursor: number, delta: number): number;
21
+ export interface SelectMultiProps<T> {
22
+ options: SelectOption<T>[];
23
+ /** Called with the chosen values when the user accepts the Submit row. */
24
+ onSubmit: (values: T[]) => void;
25
+ onCancel: () => void;
26
+ heading?: ReactNode;
27
+ visibleRows?: number;
28
+ active?: boolean;
29
+ /** Controlled selection — `QuestionView` owns it so it survives tab switches.
30
+ * Uncontrolled (internal state) when omitted. */
31
+ selected?: T[];
32
+ onChange?: (values: T[]) => void;
33
+ submitLabel?: string;
34
+ }
35
+ export declare function SelectMulti<T>({ options, onSubmit, onCancel, heading, visibleRows, active, selected: controlledSelected, onChange, submitLabel, }: SelectMultiProps<T>): JSX.Element;
36
+ //# sourceMappingURL=select-multi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select-multi.d.ts","sourceRoot":"","sources":["../src/select-multi.tsx"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,GAAG,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG3D,OAAO,EAAE,KAAK,YAAY,EAAiB,MAAM,aAAa,CAAC;AAE/D;;;;;;;;;;GAUG;AAIH,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAEvE;AAED;;;0EAG0E;AAC1E,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAWpG;AAID,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3B,0EAA0E;IAC1E,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;sDACkD;IAClD,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,EAC7B,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,WAAgB,EAChB,MAAa,EACb,QAAQ,EAAE,kBAAkB,EAC5B,QAAQ,EACR,WAAsB,GACvB,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAkFnC"}
@@ -0,0 +1,104 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { useState } from "react";
4
+ import { useKeybinding, useKeybindingContext, useTextInput } from "@chances-ai/ink-ext";
5
+ import { useTheme } from "./theme-context.js";
6
+ import { visibleWindow } from "./select.js";
7
+ /**
8
+ * (5.10b A2) The ONE generic multi-select primitive — `[ ]`/`[✓]` rows + a
9
+ * trailing Submit row. The single source of truth for the selection lives in
10
+ * this component as data (a `T[]`), NOT in hand-rolled `"☑ label"` strings — the
11
+ * exact anti-pattern oh-my-pi's `ask.ts` fell into (it concatenated checkbox
12
+ * glyphs into option strings and parsed them back out). Navigation comes from
13
+ * the shared `Select` keybinding context (ink-ext A1); this component owns no
14
+ * `useInput`. The inline free-text "Other" row is the consumer's job
15
+ * (`QuestionView` injects an option + a text field) — keeping this primitive a
16
+ * pure checkbox list.
17
+ */
18
+ // -- pure helpers (unit-tested without a TTY, the reduceModelPicker convention) --
19
+ /** Toggle a value in the selection (XOR): add if absent, remove if present. */
20
+ export function toggleSelected(selected, value) {
21
+ return selected.includes(value) ? selected.filter((v) => v !== value) : [...selected, value];
22
+ }
23
+ /** Move the cursor over `options` PLUS a trailing Submit row (index =
24
+ * `options.length`), clamped to `[0, options.length]` (no wrap), skipping
25
+ * disabled options in the direction of travel. The Submit row is always
26
+ * selectable. Returns the original cursor when there's nowhere to go. */
27
+ export function moveMultiCursor(options, cursor, delta) {
28
+ const submitIndex = options.length;
29
+ const step = delta < 0 ? -1 : 1;
30
+ let next = cursor;
31
+ for (let i = 0; i <= options.length + 1; i++) {
32
+ next += step;
33
+ if (next < 0 || next > submitIndex)
34
+ return cursor; // hit an edge → stay
35
+ if (next === submitIndex)
36
+ return next; // Submit row, always selectable
37
+ if (!options[next]?.disabled)
38
+ return next;
39
+ }
40
+ return cursor;
41
+ }
42
+ export function SelectMulti({ options, onSubmit, onCancel, heading, visibleRows = 10, active = true, selected: controlledSelected, onChange, submitLabel = "Submit", }) {
43
+ const { theme } = useTheme();
44
+ const [cursor, setCursor] = useState(0);
45
+ const [internalSelected, setInternalSelected] = useState([]);
46
+ const selected = controlledSelected ?? internalSelected;
47
+ const setSelected = (next) => {
48
+ if (onChange)
49
+ onChange(next);
50
+ else
51
+ setInternalSelected(next);
52
+ };
53
+ // Multi-select is a MODAL context: while it's up, chat below receives nothing.
54
+ useKeybindingContext("Select", active);
55
+ const submitIndex = options.length;
56
+ const onSubmitRow = cursor === submitIndex;
57
+ const toggle = (idx) => {
58
+ const opt = options[idx];
59
+ if (!opt || opt.disabled)
60
+ return;
61
+ setSelected(toggleSelected(selected, opt.value));
62
+ };
63
+ useKeybinding("select:previous", () => setCursor((c) => moveMultiCursor(options, c, -1)), {
64
+ context: "Select",
65
+ active,
66
+ });
67
+ useKeybinding("select:next", () => setCursor((c) => moveMultiCursor(options, c, 1)), {
68
+ context: "Select",
69
+ active,
70
+ });
71
+ // space toggles the focused option (no-op on the Submit row).
72
+ useKeybinding("select:toggle", () => {
73
+ if (!onSubmitRow)
74
+ toggle(cursor);
75
+ }, { context: "Select", active });
76
+ // enter on the Submit row submits; on an option it toggles (so you can pick
77
+ // with Enter then Enter on Submit to finish).
78
+ useKeybinding("select:accept", () => {
79
+ if (onSubmitRow)
80
+ onSubmit(selected);
81
+ else
82
+ toggle(cursor);
83
+ }, { context: "Select", active });
84
+ useKeybinding("select:cancel", onCancel, { context: "Select", active });
85
+ // Numeric 1-9 jumps to + toggles that option (claude-code parity).
86
+ useTextInput("Select", (input, key) => {
87
+ if (key.ctrl || key.meta)
88
+ return;
89
+ const n = Number.parseInt(input, 10);
90
+ if (Number.isInteger(n) && n >= 1 && n <= options.length) {
91
+ setCursor(n - 1);
92
+ toggle(n - 1);
93
+ }
94
+ }, { active });
95
+ const windowCursor = options.length > 0 ? Math.min(cursor, options.length - 1) : 0;
96
+ const { slice, offset } = visibleWindow(options, windowCursor, visibleRows);
97
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.permission, paddingX: 1, children: [heading ? _jsx(Text, { color: theme.permission, children: heading }) : null, _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [options.length === 0 ? _jsx(Text, { dimColor: true, children: "no options" }) : null, slice.map((o, i) => {
98
+ const idx = offset + i;
99
+ const focused = idx === cursor;
100
+ const checked = selected.includes(o.value);
101
+ return (_jsxs(Text, { color: focused ? theme.permission : undefined, dimColor: o.disabled, children: [focused ? "❯ " : " ", "[", checked ? "✓" : " ", "] ", o.label, o.description ? _jsxs(Text, { dimColor: true, children: [" \u2014 ", o.description] }) : null] }, `${idx}-${o.label}`));
102
+ }), options.length > slice.length ? _jsxs(Text, { dimColor: true, children: ["\u2026 ", options.length - slice.length, " more"] }) : null, _jsxs(Text, { color: onSubmitRow ? theme.permission : undefined, bold: onSubmitRow, children: [onSubmitRow ? "❯ " : " ", submitLabel, " (", selected.length, ")"] })] })] }));
103
+ }
104
+ //# sourceMappingURL=select-multi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select-multi.js","sourceRoot":"","sources":["../src/select-multi.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { type JSX, type ReactNode } from "react";
2
+ /**
3
+ * (5.10 A2) The ONE generic single-select primitive. Generalizes the
4
+ * `reduceModelPicker` pure-reducer pattern so `/model`, `/resume`, and (v16)
5
+ * `AskUserQuestion` all drive the same list UI with identical keys/marks — the
6
+ * mistake pi and oh-my-pi both made was letting each call site re-roll its own
7
+ * list loop. Navigation comes entirely from the `Select` keybinding context
8
+ * (ink-ext A1); this component owns no `useInput`.
9
+ *
10
+ * `SelectMulti` (checkbox multi-select) lands in v16 alongside its only consumer,
11
+ * `AskUserQuestion` — building it now would be speculative.
12
+ */
13
+ export interface SelectOption<T> {
14
+ /** Row text. */
15
+ label: string;
16
+ /** The value handed to `onSelect`. */
17
+ value: T;
18
+ /** Optional dim trailing description. */
19
+ description?: string;
20
+ /** Non-selectable row (skipped by cursor movement, dimmed). */
21
+ disabled?: boolean;
22
+ /** Marks the currently-active choice (rendered with a `●`). */
23
+ current?: boolean;
24
+ }
25
+ /** Case-insensitive substring filter over `label`+`description`. Empty filter
26
+ * returns the list unchanged. */
27
+ export declare function filterOptions<T>(options: SelectOption<T>[], filter: string): SelectOption<T>[];
28
+ /** Move the cursor by `delta`, clamped to `[0, count-1]` (no wrap — matches
29
+ * claude-code's Select + our model-picker), skipping `disabled` rows in the
30
+ * direction of travel. Returns the original cursor when there's nowhere to go. */
31
+ export declare function moveCursor<T>(options: SelectOption<T>[], cursor: number, delta: number): number;
32
+ /** Page the cursor by a window, clamped to range, then settle onto the nearest
33
+ * enabled row (search in the travel direction first, else the other way). */
34
+ export declare function pageCursor<T>(options: SelectOption<T>[], cursor: number, dir: -1 | 1, visibleRows: number): number;
35
+ /** The slice of options visible given the cursor + window, centered like the
36
+ * model picker. Returns `{ slice, offset }` so the renderer can mark the cursor
37
+ * and show "N more" affordances. */
38
+ export declare function visibleWindow<T>(options: SelectOption<T>[], cursor: number, visibleRows: number): {
39
+ slice: SelectOption<T>[];
40
+ offset: number;
41
+ };
42
+ /** Clamp a cursor into range against a (possibly re-filtered) list. */
43
+ export declare function clampCursor<T>(options: SelectOption<T>[], cursor: number): number;
44
+ export interface SelectProps<T> {
45
+ options: SelectOption<T>[];
46
+ onSelect: (value: T) => void;
47
+ onCancel: () => void;
48
+ /** Header line above the list. */
49
+ heading?: ReactNode;
50
+ /** Max rows shown at once (default 10). */
51
+ visibleRows?: number;
52
+ /** When true, typing narrows the list (model picker); otherwise typed chars
53
+ * are ignored (session picker). Default false. */
54
+ filterable?: boolean;
55
+ /** Whether this select owns input right now (pushes the `Select` context). */
56
+ active?: boolean;
57
+ /** (5.10b) Notified with the focused option's value as the cursor moves, so a
58
+ * parent (QuestionView) can render a side-by-side preview. Best with a
59
+ * primitive `T` (the effect dep compares by identity). */
60
+ onFocusChange?: (value: T) => void;
61
+ }
62
+ export declare function Select<T>({ options, onSelect, onCancel, heading, visibleRows, filterable, active, onFocusChange, }: SelectProps<T>): JSX.Element;
63
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../src/select.tsx"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,GAAG,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAItE;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,KAAK,EAAE,CAAC,CAAC;IACT,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAID;kCACkC;AAClC,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,EAAE,CAI9F;AAED;;mFAEmF;AACnF,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAU/F;AAED;8EAC8E;AAC9E,wBAAgB,UAAU,CAAC,CAAC,EAC1B,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,EAC1B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,EACX,WAAW,EAAE,MAAM,GAClB,MAAM,CAOR;AAED;;qCAEqC;AACrC,wBAAgB,aAAa,CAAC,CAAC,EAC7B,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,EAC1B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB;IAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAM9C;AAED,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjF;AAID,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3B,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,kCAAkC;IAClC,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;uDACmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;+DAE2D;IAC3D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;CACpC;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,EACxB,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,WAAgB,EAChB,UAAkB,EAClB,MAAa,EACb,aAAa,GACd,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAoG9B"}
package/dist/select.js ADDED
@@ -0,0 +1,122 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { useEffect, useState } from "react";
4
+ import { useKeybinding, useKeybindingContext, useTextInput } from "@chances-ai/ink-ext";
5
+ import { useTheme } from "./theme-context.js";
6
+ // -- pure helpers (unit-tested without a TTY, the reduceModelPicker convention) --
7
+ /** Case-insensitive substring filter over `label`+`description`. Empty filter
8
+ * returns the list unchanged. */
9
+ export function filterOptions(options, filter) {
10
+ const lower = filter.trim().toLowerCase();
11
+ if (!lower)
12
+ return options;
13
+ return options.filter((o) => `${o.label} ${o.description ?? ""}`.toLowerCase().includes(lower));
14
+ }
15
+ /** Move the cursor by `delta`, clamped to `[0, count-1]` (no wrap — matches
16
+ * claude-code's Select + our model-picker), skipping `disabled` rows in the
17
+ * direction of travel. Returns the original cursor when there's nowhere to go. */
18
+ export function moveCursor(options, cursor, delta) {
19
+ if (options.length === 0)
20
+ return 0;
21
+ const step = delta < 0 ? -1 : 1;
22
+ let next = cursor;
23
+ for (let i = 0; i < options.length; i++) {
24
+ next += step;
25
+ if (next < 0 || next > options.length - 1)
26
+ return cursor; // hit an edge → stay
27
+ if (!options[next]?.disabled)
28
+ return next;
29
+ }
30
+ return cursor;
31
+ }
32
+ /** Page the cursor by a window, clamped to range, then settle onto the nearest
33
+ * enabled row (search in the travel direction first, else the other way). */
34
+ export function pageCursor(options, cursor, dir, visibleRows) {
35
+ if (options.length === 0)
36
+ return 0;
37
+ const target = Math.min(options.length - 1, Math.max(0, cursor + dir * visibleRows));
38
+ if (!options[target]?.disabled)
39
+ return target;
40
+ const towards = moveCursor(options, target, dir);
41
+ if (towards !== target)
42
+ return towards;
43
+ return moveCursor(options, target, dir === 1 ? -1 : 1);
44
+ }
45
+ /** The slice of options visible given the cursor + window, centered like the
46
+ * model picker. Returns `{ slice, offset }` so the renderer can mark the cursor
47
+ * and show "N more" affordances. */
48
+ export function visibleWindow(options, cursor, visibleRows) {
49
+ if (options.length <= visibleRows)
50
+ return { slice: options, offset: 0 };
51
+ const half = Math.floor(visibleRows / 2);
52
+ let offset = cursor - half;
53
+ offset = Math.max(0, Math.min(offset, options.length - visibleRows));
54
+ return { slice: options.slice(offset, offset + visibleRows), offset };
55
+ }
56
+ /** Clamp a cursor into range against a (possibly re-filtered) list. */
57
+ export function clampCursor(options, cursor) {
58
+ return Math.min(Math.max(0, cursor), Math.max(0, options.length - 1));
59
+ }
60
+ export function Select({ options, onSelect, onCancel, heading, visibleRows = 10, filterable = false, active = true, onFocusChange, }) {
61
+ const { theme } = useTheme();
62
+ const [filter, setFilter] = useState("");
63
+ const [cursor, setCursor] = useState(0);
64
+ // Select is a MODAL context: while it's up, the chat below receives nothing.
65
+ useKeybindingContext("Select", active);
66
+ const matches = filterOptions(options, filter);
67
+ const safeCursor = clampCursor(matches, cursor);
68
+ const focusedValue = matches[safeCursor]?.value;
69
+ useEffect(() => {
70
+ if (onFocusChange && focusedValue !== undefined)
71
+ onFocusChange(focusedValue);
72
+ }, [onFocusChange, focusedValue]);
73
+ // Functional updates (clamping `c` against the live list) so several keypresses
74
+ // coalesced into one input flush each advance the cursor — a closure over
75
+ // `safeCursor` would re-apply the same start every time (5.10b).
76
+ useKeybinding("select:previous", () => setCursor((c) => moveCursor(matches, clampCursor(matches, c), -1)), {
77
+ context: "Select",
78
+ active,
79
+ });
80
+ useKeybinding("select:next", () => setCursor((c) => moveCursor(matches, clampCursor(matches, c), 1)), {
81
+ context: "Select",
82
+ active,
83
+ });
84
+ useKeybinding("select:pageUp", () => setCursor((c) => pageCursor(matches, clampCursor(matches, c), -1, visibleRows)), {
85
+ context: "Select",
86
+ active,
87
+ });
88
+ useKeybinding("select:pageDown", () => setCursor((c) => pageCursor(matches, clampCursor(matches, c), 1, visibleRows)), {
89
+ context: "Select",
90
+ active,
91
+ });
92
+ useKeybinding("select:accept", () => {
93
+ const choice = matches[safeCursor];
94
+ if (choice && !choice.disabled)
95
+ onSelect(choice.value);
96
+ else
97
+ onCancel(); // accept on empty/disabled = cancel, matching reduceModelPicker
98
+ }, { context: "Select", active });
99
+ useKeybinding("select:cancel", onCancel, { context: "Select", active });
100
+ // Free-text: narrows the filter when filterable; backspace edits it. Typed
101
+ // chars are otherwise ignored (no Other row in v15 — that's v16/AskUserQuestion).
102
+ useTextInput("Select", (input, key) => {
103
+ if (!filterable)
104
+ return;
105
+ if (key.backspace || key.delete) {
106
+ setFilter((f) => f.slice(0, -1));
107
+ setCursor(0);
108
+ }
109
+ else if (input && !key.ctrl && !key.meta) {
110
+ setFilter((f) => f + input);
111
+ setCursor(0);
112
+ }
113
+ }, { active });
114
+ const { slice, offset } = visibleWindow(matches, safeCursor, visibleRows);
115
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.permission, paddingX: 1, children: [heading ? _jsx(Text, { color: theme.permission, children: heading }) : null, filterable ? (_jsxs(Box, { marginTop: heading ? 1 : 0, children: [_jsx(Text, { children: "filter: " }), _jsx(Text, { color: theme.warning, children: filter || "(any)" })] })) : null, _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [matches.length === 0 ? (_jsx(Text, { dimColor: true, children: "no matches" })) : (slice.map((o, i) => {
116
+ const idx = offset + i;
117
+ const selected = idx === safeCursor;
118
+ const marker = o.current ? "●" : " ";
119
+ return (_jsxs(Text, { color: selected ? theme.permission : undefined, dimColor: o.disabled, children: [selected ? "❯ " : " ", marker, " ", o.label, o.description ? _jsxs(Text, { dimColor: true, children: [" \u2014 ", o.description] }) : null] }, `${idx}-${o.label}`));
120
+ })), matches.length > slice.length ? (_jsxs(Text, { dimColor: true, children: ["\u2026 ", matches.length - slice.length, " more"] })) : null] })] }));
121
+ }
122
+ //# sourceMappingURL=select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../src/select.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,4 @@
1
- import { type JSX } from "react";
2
- import type { PickerKey } from "./model-picker.js";
1
+ import type { JSX } from "react";
3
2
  export interface PickableSession {
4
3
  id: string;
5
4
  title: string;
@@ -10,19 +9,12 @@ export interface PickableSession {
10
9
  preview?: string;
11
10
  }
12
11
  /**
13
- * Pure reducer for the SessionPicker. Same convention as the model picker —
14
- * returns `resolved: PickableSession | null` when the user picked or
15
- * cancelled; `undefined` when the keystroke was navigation.
16
- */
17
- export declare function reduceSessionPicker(cursor: number, key: PickerKey, sessions: PickableSession[]): {
18
- cursor: number;
19
- resolved?: PickableSession | null;
20
- };
21
- /**
22
- * Vertical list of recent sessions with arrow-key cursor + Enter to resume.
23
- * Modeled on oh-my-pi's `session-selector.ts`; simplified — no fuzzy filter
24
- * yet (defer until lists exceed ~20 entries in practice). All keystroke
25
- * logic in {@link reduceSessionPicker} for testability.
12
+ * (5.10 A4) Recent-session picker a thin wrapper over the generic
13
+ * {@link Select}. The old `useInput` + `reduceSessionPicker` reducer are gone;
14
+ * the ↑↓/Enter/Esc behavior is the Select's (see `select.test`). Not filterable
15
+ * (lists are short — last ~10 by updatedAt). Signature unchanged so
16
+ * `apps/cli/src/slash/resume.ts` renders `<SessionPicker sessions onChoose/>`
17
+ * verbatim.
26
18
  */
27
19
  export declare function SessionPicker({ sessions, onChoose, }: {
28
20
  sessions: PickableSession[];