@zergai/ztc 0.2.6-dev.36101352252 → 0.2.7-dev.36236183737

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 (112) hide show
  1. package/README.md +43 -17
  2. package/bin/zmux-darwin-arm64 +0 -0
  3. package/bin/ztc-tui-darwin-arm64 +0 -0
  4. package/bin/zterm-darwin-arm64 +0 -0
  5. package/dist/App.js +19 -5
  6. package/dist/agent/agent.d.ts +5 -1
  7. package/dist/agent/agent.js +51 -8
  8. package/dist/agent/backends/account_pool.d.ts +5 -6
  9. package/dist/agent/backends/account_pool.js +39 -14
  10. package/dist/agent/backends/account_rotation.d.ts +3 -0
  11. package/dist/agent/backends/account_rotation.js +14 -3
  12. package/dist/agent/backends/codex_account_status.d.ts +42 -0
  13. package/dist/agent/backends/codex_account_status.js +196 -0
  14. package/dist/agent/backends/codex_app_server.d.ts +12 -2
  15. package/dist/agent/backends/codex_app_server.js +66 -6
  16. package/dist/agent/backends/index.d.ts +2 -0
  17. package/dist/agent/backends/index.js +13 -5
  18. package/dist/agent/backends/openai.d.ts +2 -2
  19. package/dist/agent/backends/openai.js +4 -3
  20. package/dist/agent/backends/openai_compatible.js +10 -10
  21. package/dist/agent/backends/openai_responses.d.ts +17 -5
  22. package/dist/agent/backends/openai_responses.js +2 -4
  23. package/dist/agent/backends/token_usage.js +1 -1
  24. package/dist/agent/backends/types.d.ts +3 -0
  25. package/dist/agent/commands/accounts.d.ts +7 -1
  26. package/dist/agent/commands/accounts.js +299 -101
  27. package/dist/agent/commands/config.js +11 -3
  28. package/dist/agent/commands/config_profile_switch.d.ts +2 -0
  29. package/dist/agent/commands/config_profile_switch.js +22 -0
  30. package/dist/agent/commands/context.js +4 -3
  31. package/dist/agent/commands/index.js +1 -2
  32. package/dist/agent/commands/input_mode.js +1 -1
  33. package/dist/agent/commands/model.js +7 -2
  34. package/dist/agent/commands/plan.d.ts +12 -0
  35. package/dist/agent/commands/plan.js +39 -28
  36. package/dist/agent/commands/profile.d.ts +1 -1
  37. package/dist/agent/commands/profile.js +34 -257
  38. package/dist/agent/commands/provider_choices.d.ts +12 -1
  39. package/dist/agent/commands/provider_choices.js +29 -2
  40. package/dist/agent/commands/provider_selection.d.ts +3 -0
  41. package/dist/agent/commands/provider_selection.js +63 -0
  42. package/dist/agent/commands/settings.d.ts +2 -0
  43. package/dist/agent/commands/settings.js +655 -50
  44. package/dist/agent/commands/types.d.ts +19 -2
  45. package/dist/agent/tools/ask_user.js +2 -2
  46. package/dist/app/agent_runner.d.ts +1 -1
  47. package/dist/app/agent_runner.js +2 -2
  48. package/dist/app/ask_user_response.d.ts +3 -0
  49. package/dist/app/ask_user_response.js +16 -0
  50. package/dist/app/command_context.js +29 -13
  51. package/dist/app/controller.d.ts +17 -2
  52. package/dist/app/controller.js +24 -1
  53. package/dist/app/selection_completion.d.ts +9 -3
  54. package/dist/app/selection_completion.js +54 -22
  55. package/dist/cli_args.js +10 -1
  56. package/dist/cli_main.js +85 -2
  57. package/dist/cli_ratatui.d.ts +22 -4
  58. package/dist/cli_ratatui.js +421 -146
  59. package/dist/config/defaults.d.ts +6 -0
  60. package/dist/config/defaults.js +20 -0
  61. package/dist/config/profiles.d.ts +9 -0
  62. package/dist/config/profiles.js +87 -0
  63. package/dist/config/types.d.ts +2 -0
  64. package/dist/config.d.ts +7 -0
  65. package/dist/config.js +283 -40
  66. package/dist/headless.js +14 -0
  67. package/dist/panes/model_catalog.d.ts +1 -1
  68. package/dist/panes/model_catalog.js +24 -2
  69. package/dist/panes/ztc_launch.js +6 -0
  70. package/dist/router_auth.js +3 -2
  71. package/dist/session.d.ts +2 -0
  72. package/dist/session.js +3 -0
  73. package/dist/types.d.ts +23 -2
  74. package/dist/ui/views/app.d.ts +3 -1
  75. package/dist/ui/views/app.js +3 -2
  76. package/dist/ui/views/completion_menu.js +6 -6
  77. package/dist/ui/views/input_area.d.ts +2 -1
  78. package/dist/ui/views/input_area.js +9 -8
  79. package/dist/ui/views/status_bar.d.ts +2 -0
  80. package/dist/ui/views/status_bar.js +15 -4
  81. package/dist/ui/widgets/color_panel.d.ts +9 -0
  82. package/dist/ui/widgets/color_panel.js +41 -26
  83. package/dist/ui/widgets/navigator_panel.d.ts +9 -0
  84. package/dist/ui/widgets/navigator_panel.js +17 -16
  85. package/dist/ui/widgets/panel_frame.d.ts +83 -3
  86. package/dist/ui/widgets/panel_frame.js +107 -9
  87. package/dist/ui/widgets/questionnaire.d.ts +9 -0
  88. package/dist/ui/widgets/questionnaire.js +214 -16
  89. package/dist/ui/widgets/resume_panel.d.ts +9 -0
  90. package/dist/ui/widgets/resume_panel.js +18 -17
  91. package/dist/ui/widgets/rewind_panel.d.ts +9 -0
  92. package/dist/ui/widgets/rewind_panel.js +15 -16
  93. package/dist/ui/widgets/select.d.ts +6 -0
  94. package/dist/ui/widgets/select.js +47 -12
  95. package/dist/ui/widgets/settings_panel.d.ts +75 -0
  96. package/dist/ui/widgets/settings_panel.js +197 -39
  97. package/dist/ui/widgets/status_line_panel.d.ts +9 -0
  98. package/dist/ui/widgets/status_line_panel.js +18 -19
  99. package/dist/ui/widgets/transcript_preview.d.ts +8 -0
  100. package/dist/ui/widgets/transcript_preview.js +10 -0
  101. package/dist/utils/context.js +2 -0
  102. package/dist/utils/fireworks.d.ts +20 -0
  103. package/dist/utils/fireworks.js +23 -0
  104. package/dist/utils/models.d.ts +2 -1
  105. package/dist/utils/models.js +19 -0
  106. package/dist/web/accounts_mirror.d.ts +7 -0
  107. package/dist/web/accounts_mirror.js +59 -7
  108. package/dist/web/mirror_stream.js +19 -5
  109. package/dist/zlm-embed.mjs +5734 -5362
  110. package/package.json +2 -2
  111. package/dist/agent/commands/provider.d.ts +0 -2
  112. package/dist/agent/commands/provider.js +0 -75
@@ -5,6 +5,7 @@
5
5
  * relative-time helpers live here so every picker draws the same chrome at the
6
6
  * same widths.
7
7
  */
8
+ import { type LayoutNode } from '../core/index.js';
8
9
  import type { TextStyle } from '../core/types.js';
9
10
  export interface StyledSegment {
10
11
  text: string;
@@ -77,6 +78,38 @@ export declare function framedLine(segments: StyledSegment[], width: number, bac
77
78
  export declare function bottomBorder(width: number): StyledLine;
78
79
  /** `just now`, `12m ago`, `3h ago`, `2d ago` — coarse on purpose. */
79
80
  export declare function formatRelativeTime(isoTimestamp: string, nowMs: number): string;
81
+ /**
82
+ * Build a full-page panel's styled view from its finished lines.
83
+ *
84
+ * Every full-page panel draws a framed surface and hands it to the host the
85
+ * same way, so the shape lives here rather than once per panel: a column of
86
+ * one-row boxes, offset by the surface's centring margin. Without the shared
87
+ * margin each panel would have to remember to re-emit it, and the one that
88
+ * forgot would paint at a different column from the page beside it.
89
+ *
90
+ * The margin is `marginLeft` on the frame rather than leading spaces in the
91
+ * text. The renderer then places the frame, and the panel's hit-test bands —
92
+ * which carry the same margin — describe the columns it landed on. Carrying it
93
+ * in both would double it.
94
+ */
95
+ export declare function buildWidgetFrameView(lines: StyledLine[], inset: number): LayoutNode;
96
+ /**
97
+ * The frame for a full-page panel, plus the margin it was placed at.
98
+ *
99
+ * A panel asks for the whole usable width and gets the shared cap on top, so
100
+ * every page draws at the same column on the same terminal; below the cap the
101
+ * frame keeps the full width it asked for.
102
+ *
103
+ * The frame is flush left, so the margin is zero and the spare columns sit to
104
+ * its right. It stays in the signature because the hit-test bands consume it.
105
+ *
106
+ * `termCols` is the terminal, not this surface: the host wraps every work
107
+ * surface in a `paddingX: 1` column, so `termCols - 2` is the usable width.
108
+ */
109
+ export declare function widgetFrameGeometry(termCols: number): {
110
+ width: number;
111
+ inset: number;
112
+ };
80
113
  /** Lines one wheel notch scrolls a picker pane, matching zmux's own step. */
81
114
  export declare const WHEEL_LINES = 3;
82
115
  /** Left frame cell plus its space, before the first content column. */
@@ -86,6 +119,10 @@ export declare const PANE_DIVIDER_WIDTH = 3;
86
119
  /**
87
120
  * A pane's scrollable rectangle in surface coordinates.
88
121
  *
122
+ * `inset` is the surface's centring margin: every band helper takes it so the
123
+ * hit-test columns follow the frame when a wide terminal caps it. A band left
124
+ * at the terminal origin would scroll a pane the pointer is not over.
125
+ *
89
126
  * Rows and columns count from the surface's own top-left corner, not the
90
127
  * terminal's: the host translates terminal cells into the widget's frame before
91
128
  * calling `handleMouse`, so a band recorded at paint time is directly
@@ -111,6 +148,49 @@ export interface PaneBands {
111
148
  list: PaneBand;
112
149
  preview: PaneBand;
113
150
  }
151
+ /**
152
+ * Widest frame a full-page surface draws.
153
+ *
154
+ * Past roughly this many columns a two-column table stops reading as a table:
155
+ * the label and its value drift to opposite edges and the eye has to sweep the
156
+ * whole pane to pair them. 100 columns keeps a category rail, a label column,
157
+ * and a value column inside one glance on a normal font, and holds a preview
158
+ * pane beside its list without stranding either. Every full-page panel draws
159
+ * its frame at this width on a wider terminal.
160
+ */
161
+ export declare const DEFAULT_MAX_SURFACE_WIDTH = 100;
162
+ /**
163
+ * Narrowest frame a capped surface keeps.
164
+ *
165
+ * A clamp is not a floor: a terminal narrower than this still gets every column
166
+ * it has. The value exists so the cap and the floor are both spelled out, and so
167
+ * a caller can ask what the surface would have drawn at.
168
+ */
169
+ export declare const MIN_CAPPED_SURFACE_WIDTH = 24;
170
+ /**
171
+ * The frame width a surface should draw at, and the column it starts on.
172
+ *
173
+ * A surface may declare a cap, the widest frame its content still reads well
174
+ * at. Every full-page panel shares the one cap so the pickers line up: they are
175
+ * opened and dismissed constantly and the user is looking at one at a time, so
176
+ * a page drawn at a different width from its neighbour would make the frame
177
+ * jump sideways on every switch. The cap also keeps a two-column table
178
+ * readable — past it a label and its value drift to opposite edges and the eye
179
+ * has to sweep the whole pane to pair them.
180
+ *
181
+ * The frame is left-aligned, not centred. A centred frame on a wide terminal
182
+ * leaves a black band down the left of the pane, which reads as the card having
183
+ * been pushed off-centre; the spare columns are all spent on the right instead.
184
+ * `inset` stays in the return type because the hit-test bands take it — it is
185
+ * zero while the frame is flush left, and the bands follow the frame.
186
+ *
187
+ * Below the cap nothing is reserved: a terminal that cannot afford the margin
188
+ * gets every column it asked for.
189
+ */
190
+ export declare function centeredSurface(availableWidth: number, maxWidth?: number): {
191
+ width: number;
192
+ inset: number;
193
+ };
114
194
  /**
115
195
  * One band covering a panel's whole body.
116
196
  *
@@ -121,12 +201,12 @@ export interface PaneBands {
121
201
  * panel, which is the behaviour a user expects from a surface they are
122
202
  * pointing at.
123
203
  */
124
- export declare function bodyBand(top: number, bottom: number, innerWidth: number): PaneBand;
204
+ export declare function bodyBand(top: number, bottom: number, innerWidth: number, inset?: number): PaneBand;
125
205
  /**
126
206
  * Both panes' bands for a side-by-side layout, where the panes share one
127
207
  * heading row and differ only in columns.
128
208
  */
129
- export declare function sideBySideBands(headerRow: number, lastRow: number, listWidth: number, previewWidth: number): PaneBands;
209
+ export declare function sideBySideBands(headerRow: number, lastRow: number, listWidth: number, previewWidth: number, inset?: number): PaneBands;
130
210
  /**
131
211
  * Both panes' bands for a stacked layout.
132
212
  *
@@ -139,4 +219,4 @@ export declare function stackedBands(listRows: {
139
219
  }, previewRows: {
140
220
  top: number;
141
221
  bottom: number;
142
- }, innerWidth: number): PaneBands;
222
+ }, innerWidth: number, inset?: number): PaneBands;
@@ -5,6 +5,7 @@
5
5
  * relative-time helpers live here so every picker draws the same chrome at the
6
6
  * same widths.
7
7
  */
8
+ import { box, text } from '../core/index.js';
8
9
  import { displayWidth, sanitizeTerminalText } from '../core/text_width.js';
9
10
  import { TERMINAL_THEME } from '../views/theme.js';
10
11
  const graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
@@ -184,6 +185,52 @@ export function formatRelativeTime(isoTimestamp, nowMs) {
184
185
  return `${months}mo ago`;
185
186
  return `${Math.floor(months / 12)}y ago`;
186
187
  }
188
+ /**
189
+ * Build a full-page panel's styled view from its finished lines.
190
+ *
191
+ * Every full-page panel draws a framed surface and hands it to the host the
192
+ * same way, so the shape lives here rather than once per panel: a column of
193
+ * one-row boxes, offset by the surface's centring margin. Without the shared
194
+ * margin each panel would have to remember to re-emit it, and the one that
195
+ * forgot would paint at a different column from the page beside it.
196
+ *
197
+ * The margin is `marginLeft` on the frame rather than leading spaces in the
198
+ * text. The renderer then places the frame, and the panel's hit-test bands —
199
+ * which carry the same margin — describe the columns it landed on. Carrying it
200
+ * in both would double it.
201
+ */
202
+ export function buildWidgetFrameView(lines, inset) {
203
+ const frame = box(lines.map(line => box(line.segments.map(segment => text(segment.text, segment.style)), {
204
+ flexDirection: 'row',
205
+ height: 1,
206
+ flexShrink: 0,
207
+ backgroundColor: line.backgroundColor,
208
+ })), {
209
+ flexDirection: 'column',
210
+ height: lines.length,
211
+ flexShrink: 0,
212
+ backgroundColor: TERMINAL_THEME.bg,
213
+ });
214
+ return inset > 0
215
+ ? box([frame], { flexDirection: 'column', marginLeft: inset, flexShrink: 0 })
216
+ : frame;
217
+ }
218
+ /**
219
+ * The frame for a full-page panel, plus the margin it was placed at.
220
+ *
221
+ * A panel asks for the whole usable width and gets the shared cap on top, so
222
+ * every page draws at the same column on the same terminal; below the cap the
223
+ * frame keeps the full width it asked for.
224
+ *
225
+ * The frame is flush left, so the margin is zero and the spare columns sit to
226
+ * its right. It stays in the signature because the hit-test bands consume it.
227
+ *
228
+ * `termCols` is the terminal, not this surface: the host wraps every work
229
+ * surface in a `paddingX: 1` column, so `termCols - 2` is the usable width.
230
+ */
231
+ export function widgetFrameGeometry(termCols) {
232
+ return centeredSurface(termCols - 2);
233
+ }
187
234
  // ── Mouse hit-testing ────────────────────────────────────────────────────────
188
235
  /** Lines one wheel notch scrolls a picker pane, matching zmux's own step. */
189
236
  export const WHEEL_LINES = 3;
@@ -203,6 +250,56 @@ export function isInsideBand(band, x, y) {
203
250
  return false;
204
251
  return y >= band.y0 && y <= band.y1 && x >= band.x0 && x <= band.x1;
205
252
  }
253
+ /**
254
+ * Widest frame a full-page surface draws.
255
+ *
256
+ * Past roughly this many columns a two-column table stops reading as a table:
257
+ * the label and its value drift to opposite edges and the eye has to sweep the
258
+ * whole pane to pair them. 100 columns keeps a category rail, a label column,
259
+ * and a value column inside one glance on a normal font, and holds a preview
260
+ * pane beside its list without stranding either. Every full-page panel draws
261
+ * its frame at this width on a wider terminal.
262
+ */
263
+ export const DEFAULT_MAX_SURFACE_WIDTH = 100;
264
+ /**
265
+ * Narrowest frame a capped surface keeps.
266
+ *
267
+ * A clamp is not a floor: a terminal narrower than this still gets every column
268
+ * it has. The value exists so the cap and the floor are both spelled out, and so
269
+ * a caller can ask what the surface would have drawn at.
270
+ */
271
+ export const MIN_CAPPED_SURFACE_WIDTH = 24;
272
+ /**
273
+ * The frame width a surface should draw at, and the column it starts on.
274
+ *
275
+ * A surface may declare a cap, the widest frame its content still reads well
276
+ * at. Every full-page panel shares the one cap so the pickers line up: they are
277
+ * opened and dismissed constantly and the user is looking at one at a time, so
278
+ * a page drawn at a different width from its neighbour would make the frame
279
+ * jump sideways on every switch. The cap also keeps a two-column table
280
+ * readable — past it a label and its value drift to opposite edges and the eye
281
+ * has to sweep the whole pane to pair them.
282
+ *
283
+ * The frame is left-aligned, not centred. A centred frame on a wide terminal
284
+ * leaves a black band down the left of the pane, which reads as the card having
285
+ * been pushed off-centre; the spare columns are all spent on the right instead.
286
+ * `inset` stays in the return type because the hit-test bands take it — it is
287
+ * zero while the frame is flush left, and the bands follow the frame.
288
+ *
289
+ * Below the cap nothing is reserved: a terminal that cannot afford the margin
290
+ * gets every column it asked for.
291
+ */
292
+ export function centeredSurface(availableWidth, maxWidth = DEFAULT_MAX_SURFACE_WIDTH) {
293
+ const available = Math.max(1, Math.floor(availableWidth));
294
+ // Never wider than the room, never narrower than the floor, so a terminal
295
+ // that is already tighter than the floor keeps its own columns.
296
+ const capped = Math.min(available, Math.max(MIN_CAPPED_SURFACE_WIDTH, Math.floor(maxWidth)));
297
+ // Left-aligned: the frame starts at the pane's first column and the spare
298
+ // width sits to its right. Centring it left a black band on the left of a
299
+ // wide pane (and, with the host's `paddingX: 1`, one column more of it than
300
+ // on the right), which read as the card having been pushed off-centre.
301
+ return { width: capped, inset: 0 };
302
+ }
206
303
  /**
207
304
  * One band covering a panel's whole body.
208
305
  *
@@ -213,21 +310,22 @@ export function isInsideBand(band, x, y) {
213
310
  * panel, which is the behaviour a user expects from a surface they are
214
311
  * pointing at.
215
312
  */
216
- export function bodyBand(top, bottom, innerWidth) {
313
+ export function bodyBand(top, bottom, innerWidth, inset = 0) {
314
+ const x0 = CONTENT_INSET_X + inset;
217
315
  return {
218
316
  y0: top,
219
317
  y1: bottom,
220
- x0: CONTENT_INSET_X,
221
- x1: CONTENT_INSET_X + innerWidth - 1,
318
+ x0,
319
+ x1: x0 + innerWidth - 1,
222
320
  };
223
321
  }
224
322
  /**
225
323
  * Both panes' bands for a side-by-side layout, where the panes share one
226
324
  * heading row and differ only in columns.
227
325
  */
228
- export function sideBySideBands(headerRow, lastRow, listWidth, previewWidth) {
229
- const listX0 = CONTENT_INSET_X;
230
- const previewX0 = CONTENT_INSET_X + listWidth + PANE_DIVIDER_WIDTH;
326
+ export function sideBySideBands(headerRow, lastRow, listWidth, previewWidth, inset = 0) {
327
+ const listX0 = CONTENT_INSET_X + inset;
328
+ const previewX0 = CONTENT_INSET_X + inset + listWidth + PANE_DIVIDER_WIDTH;
231
329
  return {
232
330
  list: { y0: headerRow, y1: lastRow, x0: listX0, x1: listX0 + listWidth - 1 },
233
331
  preview: { y0: headerRow, y1: lastRow, x0: previewX0, x1: previewX0 + previewWidth - 1 },
@@ -239,9 +337,9 @@ export function sideBySideBands(headerRow, lastRow, listWidth, previewWidth) {
239
337
  * Stacked, the panes share the full width and are told apart by rows, so each
240
338
  * band spans the content width and takes its own row range.
241
339
  */
242
- export function stackedBands(listRows, previewRows, innerWidth) {
243
- const x0 = CONTENT_INSET_X;
244
- const x1 = CONTENT_INSET_X + innerWidth - 1;
340
+ export function stackedBands(listRows, previewRows, innerWidth, inset = 0) {
341
+ const x0 = CONTENT_INSET_X + inset;
342
+ const x1 = x0 + innerWidth - 1;
245
343
  return {
246
344
  list: { y0: listRows.top, y1: listRows.bottom, x0, x1 },
247
345
  preview: { y0: previewRows.top, y1: previewRows.bottom, x0, x1 },
@@ -33,6 +33,8 @@ export interface QuestionnaireData {
33
33
  title?: string;
34
34
  questions: QuestionnaireQuestion[];
35
35
  allowOther?: boolean;
36
+ /** Show an optional prompt-style note beneath select options. Used by ask_user. */
37
+ allowOptionNotes?: boolean;
36
38
  callback: (answers: Record<string, string | string[]> | null) => void | Promise<void>;
37
39
  }
38
40
  export interface QuestionnaireSurface {
@@ -50,6 +52,8 @@ export declare class QuestionnaireWidget {
50
52
  private otherMode;
51
53
  private otherValue;
52
54
  private textValues;
55
+ private optionNotes;
56
+ private editingOptionNote;
53
57
  constructor(host: WidgetHost);
54
58
  /** Index of the option the agent flagged as recommended, or -1 when there is none. */
55
59
  private static recommendedOptionIndex;
@@ -57,6 +61,10 @@ export declare class QuestionnaireWidget {
57
61
  cancel(): void;
58
62
  isActive(): boolean;
59
63
  private goToQuestion;
64
+ private getOptionNote;
65
+ private setOptionNote;
66
+ private selectHighlightedOption;
67
+ private saveOptionNotes;
60
68
  private submitAnswers;
61
69
  private advanceOrSubmit;
62
70
  /**
@@ -66,6 +74,7 @@ export declare class QuestionnaireWidget {
66
74
  * key is not a delete key.
67
75
  */
68
76
  private applyDeleteKey;
77
+ private handleOptionNoteInput;
69
78
  handleInput(input: string, key: KeyEvent): void;
70
79
  /**
71
80
  * Styled rows for the form plus the plain-text view of those same rows.