praxis-agent 0.43.0 → 0.44.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 (59) hide show
  1. package/README.md +48 -47
  2. package/dist/application/native-session-projection.d.ts +4 -0
  3. package/dist/application/native-session-projection.js +135 -0
  4. package/dist/application/session-service.d.ts +0 -1
  5. package/dist/application/session-service.js +9 -170
  6. package/dist/application/top-level-agent-manager.d.ts +7 -0
  7. package/dist/application/top-level-agent-manager.js +21 -3
  8. package/dist/cli/controls.js +4 -0
  9. package/dist/cli/interactive.js +112 -156
  10. package/dist/cli/protocol.d.ts +5 -0
  11. package/dist/cli/protocol.js +45 -0
  12. package/dist/cli/provider-auth-command.d.ts +32 -0
  13. package/dist/cli/provider-auth-command.js +249 -0
  14. package/dist/cli/tui/ansi-frame-renderer.d.ts +2 -0
  15. package/dist/cli/tui/ansi-frame-renderer.js +109 -21
  16. package/dist/cli/tui/ansi-surface.d.ts +6 -22
  17. package/dist/cli/tui/ansi-surface.js +6 -126
  18. package/dist/cli/tui/ansi-theme.d.ts +3 -2
  19. package/dist/cli/tui/ansi-theme.js +22 -1
  20. package/dist/cli/tui/quiet-choice-rows.d.ts +26 -0
  21. package/dist/cli/tui/quiet-choice-rows.js +302 -0
  22. package/dist/cli/tui/quiet-frame-adapter.d.ts +18 -0
  23. package/dist/cli/tui/quiet-frame-adapter.js +90 -0
  24. package/dist/cli/tui/quiet-frame.d.ts +43 -0
  25. package/dist/cli/tui/quiet-frame.js +346 -0
  26. package/dist/cli/tui/quiet-operational-rows.d.ts +17 -0
  27. package/dist/cli/tui/quiet-operational-rows.js +413 -0
  28. package/dist/cli/tui/quiet-screen-projector.d.ts +25 -0
  29. package/dist/cli/tui/quiet-screen-projector.js +110 -0
  30. package/dist/cli/tui/quiet-settings-rows.d.ts +17 -0
  31. package/dist/cli/tui/quiet-settings-rows.js +490 -0
  32. package/dist/cli/tui/status-line.d.ts +15 -9
  33. package/dist/cli/tui/status-line.js +15 -6
  34. package/dist/cli/tui/theme.js +20 -20
  35. package/dist/cli/tui/transcript-viewport.d.ts +5 -0
  36. package/dist/cli/tui/transcript-viewport.js +213 -78
  37. package/dist/cli/tui/tui-row-ir.js +4 -4
  38. package/dist/cli/workspace-trust-prompt.js +2 -2
  39. package/dist/cli-runtime.d.ts +34 -1
  40. package/dist/cli-runtime.js +587 -184
  41. package/dist/maintenance/doctor.js +100 -22
  42. package/dist/persistence/in-memory-transcript-store.js +1 -7
  43. package/dist/persistence/native-transcript-store.d.ts +0 -9
  44. package/dist/persistence/native-transcript-store.js +4 -38
  45. package/dist/persistence/provider-credential-vault.d.ts +72 -0
  46. package/dist/persistence/provider-credential-vault.js +498 -0
  47. package/dist/providers/codex-oauth.d.ts +104 -0
  48. package/dist/providers/codex-oauth.js +770 -0
  49. package/dist/providers/codex-subscription.d.ts +33 -0
  50. package/dist/providers/codex-subscription.js +849 -0
  51. package/dist/providers/provider-auth.d.ts +71 -0
  52. package/dist/providers/provider-auth.js +149 -0
  53. package/dist/providers/provider-registry.d.ts +52 -0
  54. package/dist/providers/provider-registry.js +187 -0
  55. package/dist/providers/provider-settings.d.ts +38 -0
  56. package/dist/providers/provider-settings.js +334 -0
  57. package/dist/security/workspace-trust.d.ts +4 -3
  58. package/dist/security/workspace-trust.js +35 -1
  59. package/package.json +8 -4
@@ -0,0 +1,26 @@
1
+ import { type QuietFrameDensity, type QuietFrameRow } from './quiet-frame.js';
2
+ import type { TuiCommandPaletteModel } from './command-palette-model.js';
3
+ import type { TuiDecisionSurfaceModel } from './decision-surface-model.js';
4
+ import type { TuiElicitationSurfaceModel } from './mcp-elicitation-surface-model.js';
5
+ import type { TuiMentionPickerModel } from './mention-picker-model.js';
6
+ import type { TuiPermissionSurfaceModel } from './permission-surface-model.js';
7
+ import type { TuiSessionPickerModel } from './session-picker-model.js';
8
+ export type QuietPrioritySurface = {
9
+ readonly kind: 'editor-wait';
10
+ } | {
11
+ readonly kind: 'permission';
12
+ readonly surface: TuiPermissionSurfaceModel;
13
+ } | TuiDecisionSurfaceModel | {
14
+ readonly kind: 'elicitation';
15
+ readonly surface: TuiElicitationSurfaceModel;
16
+ };
17
+ export type QuietOverlaySurface = TuiCommandPaletteModel | TuiMentionPickerModel | {
18
+ readonly kind: 'exit-confirmation';
19
+ };
20
+ export type QuietChoiceSurface = QuietPrioritySurface | QuietOverlaySurface | TuiSessionPickerModel | TuiPermissionSurfaceModel;
21
+ export interface QuietChoiceProjectionOptions {
22
+ readonly density: QuietFrameDensity;
23
+ readonly screenReader?: boolean;
24
+ }
25
+ export declare function projectQuietChoiceRows(surface: QuietChoiceSurface, options: QuietChoiceProjectionOptions): readonly QuietFrameRow[];
26
+ //# sourceMappingURL=quiet-choice-rows.d.ts.map
@@ -0,0 +1,302 @@
1
+ import { createQuietFrameRow, } from './quiet-frame.js';
2
+ const excerpt = (value, max = 160) => typeof value === 'string'
3
+ ? value.replace(/\s+/gu, ' ').trim().slice(0, max)
4
+ : '';
5
+ const detailAllowed = (density) => density === 'full' || density === 'standard';
6
+ const row = (key, text, role = 'body', sr) => createQuietFrameRow(key, text, 'focus', role, sr);
7
+ const footer = (screenReader, text = '↑/↓ select Enter confirm Esc cancel') => row('quiet:choice:footer', text, 'muted', screenReader
8
+ ? 'Use up and down arrows to select. Press Enter to confirm. Press Escape to cancel.'
9
+ : undefined);
10
+ const explicitFooter = (key, visual, accessible) => row(key, visual, 'muted', accessible);
11
+ const marker = (selected, label, screenReader) => screenReader
12
+ ? selected
13
+ ? `Selected: ${label}`
14
+ : `Option: ${label}`
15
+ : `${selected ? '❯ ' : ' '}${label}`;
16
+ const excerptOption = (label, description, density) => detailAllowed(density) && description
17
+ ? `${label} — ${excerpt(description)}`
18
+ : label;
19
+ const unsupported = (value) => {
20
+ throw new Error(`Unsupported quiet choice surface: ${String(value)}`);
21
+ };
22
+ function optionsRows(options, prefix, density, sr) {
23
+ return options.map((item, index) => row(`${prefix}:${item.id ?? item.index ?? index}`, marker(item.selected, excerptOption(item.label, item.description, density), sr), item.selected ? 'selection' : 'body', sr
24
+ ? item.selected
25
+ ? `Selected: ${item.label}`
26
+ : `Option: ${item.label}`
27
+ : undefined));
28
+ }
29
+ function permission(surface, density, sr) {
30
+ const out = [];
31
+ switch (surface.kind) {
32
+ case 'tool-request': {
33
+ out.push(row('quiet:permission:heading', excerpt(surface.tool.question || surface.heading), 'heading'));
34
+ if (detailAllowed(density)) {
35
+ for (const [key, value] of [
36
+ ['subtitle', surface.tool.subtitle],
37
+ ['explanation', surface.tool.explanation],
38
+ ])
39
+ if (value)
40
+ out.push(row(`quiet:permission:${key}`, excerpt(value), 'muted'));
41
+ surface.tool.detail
42
+ .slice(0, 20)
43
+ .forEach((item, i) => out.push(row(`quiet:permission:detail:${i}`, `${item.prefix ?? ' '} ${excerpt(item.text)}`, item.prefix === '-'
44
+ ? 'diffRemoved'
45
+ : item.prefix === '+'
46
+ ? 'diffAdded'
47
+ : 'body')));
48
+ }
49
+ out.push(...optionsRows(surface.options.map((item, index) => ({
50
+ ...item,
51
+ label: permissionLabel({
52
+ ...item,
53
+ index: index + 1,
54
+ selected: index === surface.selectedIndex,
55
+ }),
56
+ selected: index === surface.selectedIndex,
57
+ })), 'quiet:permission:option', density, sr));
58
+ if (surface.feedbackMode)
59
+ out.push(row('quiet:permission:feedback', `> ${excerpt(surface.feedback) || 'Enter feedback…'}`, 'input'));
60
+ return [...out, footer(sr)];
61
+ }
62
+ case 'recovery-request':
63
+ out.push(row('quiet:recovery:heading', surface.heading, 'heading'), row('quiet:recovery:question', surface.question), row('quiet:recovery:display', excerpt(surface.display), 'muted'));
64
+ if (surface.feedbackMode)
65
+ out.push(row('quiet:recovery:feedback', `> ${excerpt(surface.feedback) || surface.feedbackPlaceholder}`, 'input'));
66
+ return [
67
+ ...out,
68
+ ...optionsRows(surface.options, 'quiet:recovery:option', density, sr),
69
+ footer(sr),
70
+ ];
71
+ case 'permission-dashboard':
72
+ out.push(row('quiet:dashboard:heading', surface.heading, 'heading'), row('quiet:dashboard:tab', `Tab: ${surface.tabs.find((x) => x.current)?.label ?? 'Permissions'}`));
73
+ if (detailAllowed(density))
74
+ out.push(row('quiet:dashboard:description', excerpt(surface.description)));
75
+ if (surface.query)
76
+ out.push(row('quiet:dashboard:search', `Search: ${excerpt(surface.query)}`, 'input'));
77
+ if (surface.originalWorkspace)
78
+ out.push(row('quiet:dashboard:workspace:original', `Original workspace: ${surface.originalWorkspace.label}`, 'muted'));
79
+ out.push(...surface.rows
80
+ .slice(0, 20)
81
+ .map((item, i) => row(`quiet:dashboard:${item.id ?? item.index ?? i}`, marker(item.selected, `${item.label}${item.status === 'retrying' ? ' (retrying)' : item.status === 'denied' ? ' (denied)' : ''}`, sr), item.selected
82
+ ? 'selection'
83
+ : item.status === 'denied'
84
+ ? 'error'
85
+ : item.status === 'retrying'
86
+ ? 'warning'
87
+ : 'body')));
88
+ if (surface.rows.length === 0 && surface.emptyState)
89
+ out.push(row('quiet:dashboard:empty', surface.emptyState, 'muted'));
90
+ return [
91
+ ...out,
92
+ explicitFooter('quiet:dashboard:footer', surface.rows.length > 0
93
+ ? '↑/↓ select Enter open Esc close'
94
+ : 'Esc close', surface.rows.length > 0
95
+ ? 'Use left and right arrows to switch tabs. Use up and down arrows to select. Press Enter to open. Press Escape to close.'
96
+ : 'Use left and right arrows to switch tabs. Press Escape to close.'),
97
+ ];
98
+ case 'permission-rule-input':
99
+ case 'workspace-directory-input':
100
+ out.push(row(`quiet:${surface.kind}:heading`, surface.heading, 'heading'), row(`quiet:${surface.kind}:description`, excerpt(surface.description)), row(`quiet:${surface.kind}:value`, `> ${excerpt(surface.value) || surface.placeholder}`, 'input'));
101
+ return [
102
+ ...out,
103
+ explicitFooter(`quiet:${surface.kind}:footer`, 'Enter confirm Esc cancel', 'Press Enter to confirm. Press Escape to cancel.'),
104
+ ];
105
+ case 'permission-scope':
106
+ out.push(row('quiet:scope:heading', surface.heading, 'heading'), row('quiet:scope:description', excerpt(surface.description)));
107
+ return [
108
+ ...out,
109
+ ...optionsRows(surface.options, 'quiet:scope:option', density, sr),
110
+ footer(sr),
111
+ ];
112
+ case 'permission-delete':
113
+ out.push(row('quiet:delete:heading', surface.heading, 'heading'), row('quiet:delete:rule', `${surface.rule} · ${surface.scope}`), row('quiet:delete:question', surface.question, 'warning'));
114
+ if (surface.description)
115
+ out.push(row('quiet:delete:description', excerpt(surface.description), 'warning'));
116
+ return [
117
+ ...out,
118
+ ...optionsRows(surface.options, 'quiet:delete:option', density, sr).map((x, i) => i === 0
119
+ ? {
120
+ ...x,
121
+ segments: x.segments.map((s) => ({
122
+ ...s,
123
+ role: 'error',
124
+ })),
125
+ }
126
+ : x),
127
+ footer(sr),
128
+ ];
129
+ case 'workspace-directory-delete':
130
+ out.push(row('quiet:workspace-delete:heading', surface.heading, 'heading'), row('quiet:workspace-delete:path', surface.path), row('quiet:workspace-delete:description', excerpt(surface.description), 'warning'));
131
+ return [
132
+ ...out,
133
+ ...optionsRows(surface.options, 'quiet:workspace-delete:option', density, sr),
134
+ footer(sr),
135
+ ];
136
+ default:
137
+ return unsupported(surface);
138
+ }
139
+ }
140
+ function permissionLabel(item) {
141
+ if (item.action === 'allow-once')
142
+ return 'Allow once';
143
+ if (item.action === 'deny')
144
+ return 'Deny';
145
+ const label = item.label.trim();
146
+ if (/^yes,\s*/iu.test(label)) {
147
+ const consequence = label.replace(/^yes,\s*/iu, '');
148
+ return /^allow\b/iu.test(consequence)
149
+ ? `Allow${consequence.slice('allow'.length)}`
150
+ : `Allow ${consequence}`;
151
+ }
152
+ if (/^no,\s*/iu.test(label))
153
+ return `Deny ${label.replace(/^no,\s*/iu, '')}`;
154
+ if (/^yes$/iu.test(label))
155
+ return 'Allow for this session';
156
+ if (/^no$/iu.test(label))
157
+ return 'Deny';
158
+ return label;
159
+ }
160
+ function decision(surface, density, sr) {
161
+ const out = [
162
+ row(`quiet:decision:${surface.kind}:heading`, surface.heading, 'heading'),
163
+ ];
164
+ if (surface.kind === 'plan-approval') {
165
+ if (detailAllowed(density)) {
166
+ out.push(row('quiet:plan:explanation', surface.explanation));
167
+ if (surface.plan)
168
+ out.push(row('quiet:plan:content', excerpt(surface.plan, 160), 'muted'));
169
+ out.push(row('quiet:plan:path', `Plan: ${excerpt(surface.planPath)}`, 'muted'));
170
+ }
171
+ out.push(...optionsRows(surface.options, 'quiet:plan:option', density, sr));
172
+ if (surface.feedbackMode)
173
+ out.push(row('quiet:plan:feedback', `> ${excerpt(surface.feedback) || surface.feedbackPlaceholder}`, 'input'));
174
+ return [
175
+ ...out,
176
+ explicitFooter('quiet:plan:footer', '↑/↓ select Enter confirm Esc cancel', 'Use up and down arrows to select. Press Enter to confirm. Press Escape to cancel.'),
177
+ ];
178
+ }
179
+ if (surface.progress)
180
+ out.push(row('quiet:question:progress', surface.progress, 'muted'));
181
+ if (surface.question)
182
+ out.push(row('quiet:question:text', surface.question));
183
+ out.push(...optionsRows(surface.options, 'quiet:question:option', density, sr));
184
+ out.push(row('quiet:question:answer', `Current answer: ${excerpt(surface.answer) || '(empty)'}`, 'input'));
185
+ if (surface.guidance)
186
+ out.push(row('quiet:question:guidance', surface.guidance, 'muted'));
187
+ if (surface.emptyState)
188
+ out.push(row('quiet:question:empty', surface.emptyState, 'muted'));
189
+ out.push(explicitFooter('quiet:question:footer', 'Enter answer Esc cancel', 'Press Enter to submit the answer. Press Escape to cancel.'));
190
+ return out;
191
+ }
192
+ function picker(surface, density, sr) {
193
+ const name = surface.kind === 'session-picker'
194
+ ? 'Sessions'
195
+ : surface.kind === 'command-palette'
196
+ ? 'Commands'
197
+ : 'Mentions';
198
+ const out = [
199
+ row(`quiet:${surface.kind}:heading`, `${name}${surface.query ? ` · ${excerpt(surface.query)}` : ''}`, 'heading'),
200
+ ];
201
+ const { start, end } = surface.visibleRange;
202
+ surface.rows.slice(start, end).forEach((item) => {
203
+ const label = 'path' in item
204
+ ? item.path
205
+ : 'invocation' in item
206
+ ? detailAllowed(density)
207
+ ? `${item.invocation} — ${item.description}`
208
+ : item.invocation
209
+ : 'name' in item
210
+ ? detailAllowed(density)
211
+ ? `${item.name} — ${item.description}`
212
+ : item.name
213
+ : detailAllowed(density) && item.detail
214
+ ? `${item.label} — ${item.detail}`
215
+ : item.label;
216
+ out.push(row(`quiet:${surface.kind}:${item.id}`, marker(item.selected, excerptOption(label, undefined, density), sr), item.selected ? 'selection' : 'body'));
217
+ });
218
+ if (surface.rows.length === 0)
219
+ out.push(row(`quiet:${surface.kind}:empty`, 'No matching choices.', 'muted'));
220
+ return [
221
+ ...out,
222
+ explicitFooter(`quiet:${surface.kind}:footer`, '↑/↓ select Enter confirm Esc cancel', 'Use up and down arrows to select. Press Enter to confirm. Press Escape to cancel.'),
223
+ ];
224
+ }
225
+ function elicitation(surface, density, sr) {
226
+ const out = [
227
+ row(`quiet:elicitation:${surface.kind}:server`, `Server: ${excerpt(surface.serverName)}`, 'heading'),
228
+ row(`quiet:elicitation:${surface.kind}:message`, excerpt(surface.message)),
229
+ ];
230
+ if (surface.kind === 'elicitation-url') {
231
+ out.push(row('quiet:elicitation:url', `URL: ${excerpt(surface.url)}`), row('quiet:elicitation:waiting', surface.waiting
232
+ ? 'Waiting for confirmation…'
233
+ : marker(surface.selection === 0, surface.actionLabel, sr), surface.waiting
234
+ ? 'warning'
235
+ : surface.selection === 0
236
+ ? 'selection'
237
+ : 'body'), row('quiet:elicitation:cancel', marker(surface.selection === 1, 'Cancel', sr), surface.selection === 1 ? 'selection' : 'body'));
238
+ return [...out, footer(sr)];
239
+ }
240
+ const start = Math.max(0, Math.min(surface.state.focusIndex - 2, surface.state.fields.length - Math.max(1, surface.maxVisibleFields)));
241
+ surface.state.fields
242
+ .slice(start, start + Math.max(1, surface.maxVisibleFields))
243
+ .forEach((field) => {
244
+ const stored = surface.state.values[field.name];
245
+ const value = surface.state.fields.indexOf(field) === surface.state.focusIndex &&
246
+ ['text', 'number', 'integer'].includes(field.kind) &&
247
+ surface.state.expandedField !== field.name
248
+ ? surface.input
249
+ : stored;
250
+ const text = `${field.title}${field.required ? ' (required)' : ''}: ${value === undefined || value === '' ? '(empty)' : String(value)}${detailAllowed(density) && field.description ? ` — ${excerpt(field.description)}` : ''}${surface.state.errors[field.name] ? ` — ${excerpt(surface.state.errors[field.name])}` : ''}`;
251
+ out.push(row(`quiet:elicitation:field:${field.name}`, marker(surface.state.fields.indexOf(field) === surface.state.focusIndex, text, sr), surface.state.fields.indexOf(field) === surface.state.focusIndex
252
+ ? 'selection'
253
+ : surface.state.errors[field.name]
254
+ ? 'error'
255
+ : 'body'));
256
+ if (surface.state.expandedField === field.name) {
257
+ const optionStart = Math.max(0, Math.min(surface.state.optionIndex - 10, field.options.length - 20));
258
+ field.options
259
+ .slice(optionStart, optionStart + 20)
260
+ .forEach((option, offset) => {
261
+ const optionIndex = optionStart + offset;
262
+ const focused = surface.state.optionIndex === optionIndex;
263
+ const checked = field.kind === 'multi-enum' &&
264
+ Array.isArray(stored) &&
265
+ stored.includes(option.value);
266
+ const label = field.kind === 'multi-enum'
267
+ ? `${checked ? '[x]' : '[ ]'} ${option.label}`
268
+ : option.label;
269
+ out.push(row(`quiet:elicitation:field:${field.name}:option:${option.value}`, marker(focused, label, sr), focused ? 'selection' : checked ? 'success' : 'body'));
270
+ });
271
+ }
272
+ });
273
+ return [
274
+ ...out,
275
+ explicitFooter('quiet:elicitation:footer', '↑/↓ field Enter edit/select Esc cancel', 'Use up and down arrows to change fields. Press Enter to edit or select. Press Escape to cancel.'),
276
+ ];
277
+ }
278
+ export function projectQuietChoiceRows(surface, options) {
279
+ const sr = options.screenReader === true;
280
+ if (surface.kind === 'permission')
281
+ return permission(surface.surface, options.density, sr);
282
+ if (surface.kind === 'elicitation')
283
+ return elicitation(surface.surface, options.density, sr);
284
+ if (surface.kind === 'editor-wait')
285
+ return [
286
+ row('quiet:editor-wait', 'Editor is open. Waiting for changes…', 'warning'),
287
+ explicitFooter('quiet:editor-wait:footer', 'Esc cancel', 'Press Escape to cancel.'),
288
+ ];
289
+ if (surface.kind === 'exit-confirmation')
290
+ return [
291
+ row('quiet:exit:heading', 'Exit Praxis?', 'heading'),
292
+ explicitFooter('quiet:exit:footer', 'Enter confirm Esc cancel', 'Press Enter to confirm. Press Escape to cancel.'),
293
+ ];
294
+ if (surface.kind === 'plan-approval' || surface.kind === 'question')
295
+ return decision(surface, options.density, sr);
296
+ if (surface.kind === 'session-picker' ||
297
+ surface.kind === 'command-palette' ||
298
+ surface.kind === 'mention-picker')
299
+ return picker(surface, options.density, sr);
300
+ return permission(surface, options.density, sr);
301
+ }
302
+ //# sourceMappingURL=quiet-choice-rows.js.map
@@ -0,0 +1,18 @@
1
+ import type { QuietFrame } from './quiet-frame.js';
2
+ import { type TuiTextRole } from './theme.js';
3
+ export interface QuietInkLineSegment {
4
+ readonly text: string;
5
+ readonly role: TuiTextRole;
6
+ readonly cursor?: boolean;
7
+ }
8
+ export interface QuietInkFrameLine {
9
+ readonly key: string;
10
+ readonly segments: readonly QuietInkLineSegment[];
11
+ }
12
+ export declare function projectQuietInkFrameLines(frame: QuietFrame, screenReader?: boolean): readonly QuietInkFrameLine[];
13
+ export interface QuietInkFrameProps {
14
+ readonly frame: QuietFrame;
15
+ readonly screenReader?: boolean;
16
+ }
17
+ export declare function QuietInkFrame({ frame, screenReader, }: QuietInkFrameProps): import("react").JSX.Element;
18
+ //# sourceMappingURL=quiet-frame-adapter.d.ts.map
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ import { useTuiTheme } from './theme.js';
4
+ import { terminalGraphemeWidth, terminalGraphemes, terminalTextWidth, } from './transcript-viewport.js';
5
+ const ROLE_MAP = {
6
+ body: 'body',
7
+ heading: 'heading',
8
+ muted: 'muted',
9
+ accent: 'focusMarker',
10
+ success: 'success',
11
+ warning: 'warning',
12
+ error: 'error',
13
+ tool: 'info',
14
+ selection: 'focusMarker',
15
+ input: 'inputMarker',
16
+ diffAdded: 'diffAdded',
17
+ diffRemoved: 'diffRemoved',
18
+ };
19
+ function plainText(row) {
20
+ return row.segments.map((segment) => segment.text).join('') || ' ';
21
+ }
22
+ export function projectQuietInkFrameLines(frame, screenReader = false) {
23
+ return frame.lines.map((row) => {
24
+ if (screenReader)
25
+ return {
26
+ key: row.key,
27
+ segments: [
28
+ {
29
+ text: row.accessibleText ?? plainText(row),
30
+ role: 'body',
31
+ },
32
+ ],
33
+ };
34
+ const cursor = frame.cursor?.rowKey === row.key ? frame.cursor.column : undefined;
35
+ if (cursor === undefined)
36
+ return {
37
+ key: row.key,
38
+ segments: row.segments.map((segment) => ({
39
+ ...segment,
40
+ role: ROLE_MAP[segment.role],
41
+ })),
42
+ };
43
+ const output = [];
44
+ let position = 0;
45
+ let inserted = false;
46
+ for (const segment of row.segments) {
47
+ const chars = terminalGraphemes(segment.text);
48
+ const mappedRole = ROLE_MAP[segment.role];
49
+ const segmentWidth = terminalTextWidth(segment.text);
50
+ if (!inserted && cursor >= position && cursor < position + segmentWidth) {
51
+ let offset = 0;
52
+ let cell = position;
53
+ for (const [index, grapheme] of chars.entries()) {
54
+ const width = terminalGraphemeWidth(grapheme);
55
+ if (cursor < cell + width) {
56
+ offset = index;
57
+ break;
58
+ }
59
+ cell += width;
60
+ }
61
+ const before = chars.slice(0, offset).join('');
62
+ const at = chars[offset] ?? ' ';
63
+ const after = chars
64
+ .slice(offset + (offset < chars.length ? 1 : 0))
65
+ .join('');
66
+ if (before)
67
+ output.push({ text: before, role: mappedRole });
68
+ output.push({ text: at, role: mappedRole, cursor: true });
69
+ if (after)
70
+ output.push({ text: after, role: mappedRole });
71
+ inserted = true;
72
+ }
73
+ else
74
+ output.push({ text: segment.text, role: mappedRole });
75
+ position += segmentWidth;
76
+ }
77
+ if (!inserted)
78
+ output.push({ text: ' ', role: 'body', cursor: true });
79
+ return { key: row.key, segments: output };
80
+ });
81
+ }
82
+ export function QuietInkFrame({ frame, screenReader = false, }) {
83
+ const theme = useTuiTheme();
84
+ const lines = projectQuietInkFrameLines(frame, screenReader);
85
+ return (_jsx(_Fragment, { children: lines.map((line) => (_jsx(Text, { children: line.segments.map((segment, index) => {
86
+ const style = theme.text[segment.role];
87
+ return (_jsx(Text, { ...style, ...(segment.cursor || style.inverse ? { inverse: true } : {}), children: segment.text }, `${line.key}:${index}`));
88
+ }) }, line.key))) }));
89
+ }
90
+ //# sourceMappingURL=quiet-frame-adapter.js.map
@@ -0,0 +1,43 @@
1
+ import type { TuiScreenModel } from './tui-screen-model.js';
2
+ import type { TuiRow, TuiTextRole } from './tui-row-ir.js';
3
+ export type QuietFrameDensity = 'full' | 'standard' | 'compact' | 'narrow' | 'minimal';
4
+ export type QuietFrameRegion = 'identity' | 'transcript' | 'active' | 'focus' | 'composer' | 'status';
5
+ export interface QuietFrameRow extends TuiRow {
6
+ readonly region: QuietFrameRegion;
7
+ readonly accessibleText?: string;
8
+ }
9
+ export interface QuietFrameCursor {
10
+ readonly rowKey: string;
11
+ readonly column: number;
12
+ }
13
+ export interface QuietFrame {
14
+ readonly columns: number;
15
+ readonly rows: number;
16
+ readonly density: QuietFrameDensity;
17
+ readonly lines: readonly QuietFrameRow[];
18
+ readonly cursor?: QuietFrameCursor;
19
+ }
20
+ export interface QuietFrameInput {
21
+ readonly screen: TuiScreenModel;
22
+ readonly width: number;
23
+ readonly rows: number | undefined;
24
+ readonly composerText: string;
25
+ readonly composerCursor: number;
26
+ readonly shellMode: boolean;
27
+ readonly busy: boolean;
28
+ readonly status: string;
29
+ readonly display?: {
30
+ readonly cwd?: string;
31
+ readonly model?: string;
32
+ readonly effort?: string;
33
+ readonly permissionMode?: string;
34
+ readonly version?: string;
35
+ };
36
+ readonly focusRows: readonly (TuiRow & {
37
+ readonly accessibleText?: string;
38
+ })[];
39
+ }
40
+ export declare function resolveQuietFrameDensity(width: number): QuietFrameDensity;
41
+ export declare function createQuietFrameRow(key: string, text: string, region: QuietFrameRegion, role?: TuiTextRole, accessibleText?: string): QuietFrameRow;
42
+ export declare function projectQuietFrame(input: QuietFrameInput): QuietFrame;
43
+ //# sourceMappingURL=quiet-frame.d.ts.map