codsh-cli 0.1.0 → 0.2.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.
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * The process-facing terminal, in two shapes.
3
3
  *
4
- * On a terminal this surface owns the keyboard: raw mode, its own key decoding,
5
- * and a managed region of rows at the bottom holding the input box. That is what
6
- * an inline completion menu and a multi-line prompt require `readline` reports
7
- * no lone Escape, draws no menu, and decides for itself what Enter means.
4
+ * On a terminal this surface owns the keyboard AND the screen: raw mode, its own
5
+ * key decoding, and an alternate-screen viewport ({@link Screen}) that holds the
6
+ * transcript in a scrollback buffer of its own with the input box pinned below
7
+ * it. That is what an inline completion menu, a multi-line prompt, and a session
8
+ * that reads as its own space require — `readline` reports no lone Escape, draws
9
+ * no menu, and decides for itself what Enter means.
8
10
  *
9
11
  * Off a terminal it is a line reader over `readline`, because a pipe has no
10
12
  * cursor to manage and every line in a script is a separate instruction. Both
@@ -16,6 +18,7 @@ import type { Key } from './keys.ts';
16
18
  /** The output stream this surface writes to. */
17
19
  export interface OutputStream extends NodeJS.WritableStream {
18
20
  readonly columns?: number;
21
+ readonly rows?: number;
19
22
  readonly isTTY?: boolean;
20
23
  }
21
24
  /** The input stream this surface reads from. */
@@ -41,15 +44,18 @@ export declare class TerminalConsole {
41
44
  private readonly earlyKeys;
42
45
  private escapeTimer;
43
46
  private ended;
44
- /** Rows currently drawn in the bottom region. */
45
- private regionRows;
46
- /** Where among those rows the cursor was left. */
47
- private regionCursor;
48
- /** Whether the region currently holds input focus, which shows the cursor. */
49
- private regionFocus;
47
+ /** The viewport this surface owns on a terminal; absent off one. */
48
+ private readonly screen;
50
49
  constructor(input: InputStream, output: OutputStream);
51
50
  /** Display columns available for one line, never below {@link MIN_COLUMNS}. */
52
51
  get columns(): number;
52
+ /**
53
+ * Columns content may be laid out for: one less than the width, because the
54
+ * viewport wraps at that boundary. Markdown layout MUST use this figure — a
55
+ * table laid out one column wider is refolded by the viewport, and its rows
56
+ * shear apart.
57
+ */
58
+ get contentColumns(): number;
53
59
  /** Whether the output stream is a terminal. */
54
60
  get isTty(): boolean;
55
61
  /**
@@ -69,10 +75,40 @@ export declare class TerminalConsole {
69
75
  */
70
76
  onResize(handler: () => void): () => void;
71
77
  /**
72
- * Clear the visible screen.
78
+ * Take the viewport: the transcript and the prompt live on their own screen
79
+ * from here, and the terminal keeps the buffer the person had.
80
+ */
81
+ enterScreen(): void;
82
+ /**
83
+ * Give the terminal back. Idempotent: every exit path calls it.
84
+ */
85
+ leaveScreen(): void;
86
+ /** Whether this surface currently holds its own screen. */
87
+ get owningScreen(): boolean;
88
+ /**
89
+ * Scroll the transcript inside the viewport.
90
+ * @param delta - rows to move; negative goes back into history.
91
+ */
92
+ scrollBy(delta: number): void;
93
+ /**
94
+ * Set the notice shown while the transcript is scrolled back.
95
+ * @param text - the styled line, or the empty string for none.
96
+ */
97
+ setScrollNotice(text: string): void;
98
+ /**
99
+ * Scroll the transcript by a whole viewport.
100
+ * @param direction - -1 for back into history, 1 towards the tail.
101
+ */
102
+ scrollPage(direction: -1 | 1): void;
103
+ /** Return to the tail of the transcript. */
104
+ scrollToBottom(): void;
105
+ /** Physical rows currently scrolled out of view; zero means at the tail. */
106
+ get scrolledBy(): number;
107
+ /**
108
+ * Clear the transcript this session accumulated.
73
109
  *
74
- * The scrollback survives this wipes the viewport the way a shell's clear
75
- * does. The managed region is forgotten with it, so the caller redraws.
110
+ * On its own screen there is no shell scrollback to preserve, so this empties
111
+ * the buffer the viewport shows rather than wiping a shared terminal.
76
112
  */
77
113
  clearScreen(): void;
78
114
  /**
@@ -98,39 +134,82 @@ export declare class TerminalConsole {
98
134
  /** Mark input finished and release every waiting read. */
99
135
  private end;
100
136
  /**
101
- * Write one finished line above the managed region.
137
+ * Keep one finished transcript line.
102
138
  *
103
- * The region is erased first and redrawn after, so the transcript stays
104
- * append-only while the input box keeps its place at the bottom.
139
+ * On its own screen the line goes into the viewport's scrollback, which is
140
+ * what lets the transcript scroll under a prompt that does not move. Off one
141
+ * it is written straight out, because a pipe's reader wants exactly that.
105
142
  * @param line - the line, without its terminator.
106
143
  */
107
144
  write(line: string): void;
108
145
  /**
109
- * Replace the managed region at the bottom of the screen.
146
+ * Replace the rows pinned below the transcript.
110
147
  *
111
148
  * This is the whole live area: an input box, a completion menu, a working
112
- * indicator. Everything the transcript keeps goes through {@link write}
113
- * instead, because a terminal cannot revise a row that has scrolled. Off a
114
- * terminal the call is ignored — a redirected transcript must not collect
115
- * frames of a box nobody can see.
149
+ * indicator, the status row. Off a terminal the call is ignored a
150
+ * redirected transcript must not collect frames of a box nobody can see.
116
151
  * @param rows - the rows to display, top to bottom.
117
152
  * @param cursor - where to leave the terminal cursor among them.
118
- * @param focus - whether the region holds input focus. Without it the cursor
119
- * stays hidden: a block cursor parked on a display row (the status line,
120
- * a streaming line) reads as content colliding with it.
153
+ * @param focus - whether the rows hold input focus, which is when the cursor
154
+ * shows. Parked anywhere else it reads as content colliding with it.
121
155
  */
122
156
  setRegion(rows: readonly string[], cursor: RegionCursor, focus?: boolean): void;
123
- /** Remove the region, leaving the cursor where the next write will land. */
124
- clearRegion(): void;
125
- /** Move to the region's first row and erase everything from there down. */
126
- private eraseRegion;
127
157
  /**
128
- * Draw rows from the cursor down and place the cursor among them.
129
- * @param rows - the rows to draw.
130
- * @param cursor - the target position.
131
- * @param focus - whether to show the cursor at that position afterwards.
158
+ * Keep one collapsible block: summary now, full form behind the toggle.
159
+ *
160
+ * Off a terminal only the summary is written — a pipe has no keys to toggle
161
+ * with, and scripts want the digest.
162
+ * @param summary - the collapsed lines.
163
+ * @param full - the expanded lines.
164
+ */
165
+ appendFold(summary: readonly string[], full: readonly string[]): void;
166
+ /**
167
+ * Swap every collapsible block between summary and full form.
168
+ * @returns false when there is nothing to toggle.
132
169
  */
133
- private drawRegion;
170
+ /**
171
+ * Anchor a mouse selection at a terminal position.
172
+ * @param row - terminal row, 1-based.
173
+ * @param column - terminal column, 1-based.
174
+ */
175
+ mouseDown(row: number, column: number): void;
176
+ /**
177
+ * Extend the mouse selection to a terminal position.
178
+ * @param row - terminal row, 1-based.
179
+ * @param column - terminal column, 1-based.
180
+ */
181
+ mouseDrag(row: number, column: number): void;
182
+ /**
183
+ * Finish the mouse selection.
184
+ * @returns the selected text, or undefined for a bare click.
185
+ */
186
+ mouseUp(): string | undefined;
187
+ /**
188
+ * Put text on the clipboard.
189
+ *
190
+ * Two channels, because neither is universal: OSC 52 reaches through SSH and
191
+ * works wherever the terminal permits it, and the platform helper covers the
192
+ * terminals that refuse the escape. `CODSH_CLIPBOARD` narrows it to `osc52`,
193
+ * `system`, or `off` — tests use `osc52` so a run never touches the real
194
+ * clipboard.
195
+ * @param text - the plain text to copy.
196
+ * @returns whether a copy was attempted at all.
197
+ */
198
+ copyText(text: string): boolean;
199
+ /**
200
+ * Make the last `count` written lines a collapsible block after the fact.
201
+ *
202
+ * Screen-only on purpose: a pipe already carries the full text, and a
203
+ * summary would subtract from it.
204
+ * @param count - how many trailing lines the block owns.
205
+ * @param summary - the collapsed lines, already styled.
206
+ */
207
+ foldRecent(count: number, summary: readonly string[]): void;
208
+ toggleFolds(): boolean;
209
+ /** Return every block to its summary. */
210
+ collapseFolds(): void;
211
+ /** Take the pinned rows down, leaving the transcript alone. */
212
+ clearRegion(): void;
134
213
  /** Ring the terminal bell; a pipe gets nothing to beep with. */
135
214
  bell(): void;
136
215
  /**
@@ -149,4 +228,13 @@ export declare class TerminalConsole {
149
228
  readLine(signal?: AbortSignal): Promise<string | undefined>;
150
229
  /** Restore the terminal and stop reading. */
151
230
  close(): void;
231
+ /**
232
+ * Write one line to the terminal the person keeps, not to our viewport.
233
+ *
234
+ * The exit summary is what needs this: the session's own screen disappears
235
+ * with it, so the few facts worth keeping — the session id, what it cost —
236
+ * have to land in the buffer that survives.
237
+ * @param line - the line, without its terminator.
238
+ */
239
+ writeAfterScreen(line: string): void;
152
240
  }
@@ -58,9 +58,29 @@ export type Key = {
58
58
  kind: 'clear-screen';
59
59
  } | {
60
60
  kind: 'expand-output';
61
+ } | {
62
+ kind: 'page';
63
+ direction: -1 | 1;
64
+ } | {
65
+ kind: 'scroll';
66
+ lines: number;
67
+ } | {
68
+ kind: 'scroll-end';
61
69
  } | {
62
70
  kind: 'paste';
63
71
  text: string;
72
+ } | {
73
+ kind: 'mouse-down';
74
+ row: number;
75
+ column: number;
76
+ } | {
77
+ kind: 'mouse-drag';
78
+ row: number;
79
+ column: number;
80
+ } | {
81
+ kind: 'mouse-up';
82
+ row: number;
83
+ column: number;
64
84
  };
65
85
  /** Decodes terminal bytes into keys, holding partial sequences between reads. */
66
86
  export declare class KeyDecoder {
@@ -44,6 +44,9 @@ export declare class Prompt {
44
44
  private readonly queued;
45
45
  /** The working indicator shown under the box. */
46
46
  private hint;
47
+ /** A short-lived notice that borrows the hint row, e.g. the copy toast. */
48
+ private flash;
49
+ private flashTimer;
47
50
  /** The always-current session facts shown as the region's last row. */
48
51
  private status;
49
52
  /** The assistant line still arriving, shown above the box. */
@@ -52,6 +55,9 @@ export declare class Prompt {
52
55
  private accent;
53
56
  /** Whether a read is outstanding, which decides where a submission goes. */
54
57
  private reading;
58
+ /** Wheel rows accumulated this tick, painted once — scrolling per event janks. */
59
+ private pendingScroll;
60
+ private scrollFlushQueued;
55
61
  /**
56
62
  * Whether the interactive session is running, which is when the box is worth
57
63
  * drawing. The box stays up while the agent works — typing ahead must be
@@ -86,6 +92,15 @@ export declare class Prompt {
86
92
  * @param text - the text, or undefined to drop the row.
87
93
  */
88
94
  setHint(text: string | undefined): void;
95
+ /**
96
+ * Show a notice on the hint row briefly, then give the row back.
97
+ *
98
+ * The hint row belongs to the working indicator, which repaints itself
99
+ * continuously — a notice written through setHint would last one tick. The
100
+ * flash outranks the hint until its moment passes.
101
+ * @param text - the styled notice.
102
+ */
103
+ setFlash(text: string): void;
89
104
  /**
90
105
  * Set the status row, the region's always-current last line.
91
106
  * @param text - the styled row, or undefined to drop it.
@@ -0,0 +1,204 @@
1
+ /**
2
+ * The session's own screen: an alternate-screen viewport with its own scrollback.
3
+ *
4
+ * This is what makes a session feel like a place rather than a run of output.
5
+ * The terminal's buffer is left exactly as the person had it — their shell
6
+ * history is neither scrolled away nor interleaved — and everything this
7
+ * surface shows lives in a buffer it owns: the transcript scrolls inside the
8
+ * viewport while the input box stays where it is, at the bottom.
9
+ *
10
+ * Owning the viewport means doing three jobs the terminal used to do. Lines are
11
+ * wrapped here ({@link wrapAll}), because a line that overflows would otherwise
12
+ * overwrite the row below. Scrolling is ours, because the terminal's scrollback
13
+ * does not exist on the alternate screen. And every frame is painted as a
14
+ * whole, diffed against the last one — which is what removes the class of bug
15
+ * that relative erase arithmetic keeps producing.
16
+ * @module codsh/src/screen
17
+ */
18
+ /** Where the cursor belongs within the chrome rows. */
19
+ export interface ChromeCursor {
20
+ row: number;
21
+ column: number;
22
+ }
23
+ /** What the screen writes to and measures itself against. */
24
+ export interface ScreenHost {
25
+ /** Emit raw bytes to the terminal. */
26
+ write(data: string): void;
27
+ /** Display columns currently available. */
28
+ columns(): number;
29
+ /** Screen rows currently available. */
30
+ rows(): number;
31
+ }
32
+ /** An alternate-screen viewport over a scrollback buffer this surface owns. */
33
+ export declare class Screen {
34
+ private readonly host;
35
+ /** Logical transcript lines, unwrapped, oldest first. */
36
+ private logical;
37
+ /** The same lines wrapped to the current width — what the viewport slices. */
38
+ private physical;
39
+ /** The bottom rows: input box, menu, indicator, status. */
40
+ private chrome;
41
+ private chromeCursor;
42
+ /** Whether the chrome holds input focus, which is when the cursor shows. */
43
+ private chromeFocus;
44
+ /** Physical rows hidden below the viewport; zero means following the tail. */
45
+ private offset;
46
+ /** What to show while scrolled back, drawn over the viewport's top row. */
47
+ private notice;
48
+ /** A mouse selection over the transcript, in physical-row coordinates. */
49
+ private selection;
50
+ /** Collapsed blocks in the transcript, in order, with both of their forms. */
51
+ private folds;
52
+ /** Whether the folds currently show their full form. */
53
+ private expanded;
54
+ /** The last painted frame, so a repaint only touches rows that changed. */
55
+ private painted;
56
+ /** Width the current frame was painted at, to detect a resize. */
57
+ private paintedColumns;
58
+ private active;
59
+ constructor(host: ScreenHost);
60
+ /** Whether the alternate screen is currently held. */
61
+ get entered(): boolean;
62
+ /** Physical rows scrolled up out of view; zero means the tail is showing. */
63
+ get scrolledBy(): number;
64
+ /** Take the alternate screen and start reporting the mouse. */
65
+ enter(): void;
66
+ /**
67
+ * Give the terminal back exactly as it was.
68
+ *
69
+ * Idempotent, because every exit path calls it — a normal quit, an
70
+ * interrupt, and a crash handler all have to leave the terminal usable.
71
+ */
72
+ leave(): void;
73
+ /**
74
+ * Append finished transcript lines.
75
+ *
76
+ * Following the tail is the default; a person who has scrolled up stays
77
+ * where they are, and the new rows accumulate below them.
78
+ * @param lines - the lines to keep, already styled.
79
+ */
80
+ append(lines: readonly string[]): void;
81
+ /**
82
+ * Append one collapsible block: its summary now, its full form on demand.
83
+ *
84
+ * This is what makes every long block — not merely the latest — expandable:
85
+ * the buffer keeps both forms, and toggling rebuilds the transcript in
86
+ * place, exactly like a details/summary element.
87
+ * @param summary - the collapsed lines, already styled.
88
+ * @param full - the expanded lines, already styled.
89
+ */
90
+ appendFold(summary: readonly string[], full: readonly string[]): void;
91
+ /**
92
+ * Turn the last `count` appended lines into a collapsible block after the
93
+ * fact.
94
+ *
95
+ * This is how a finished answer becomes foldable without ever having been
96
+ * withheld: it streamed in the open, and only once complete does it grow a
97
+ * summary form. The block starts expanded — the person is reading it — and
98
+ * collapses with the rest when the conversation moves on.
99
+ * @param count - how many trailing lines the block owns.
100
+ * @param summary - the collapsed lines, already styled.
101
+ */
102
+ foldBack(count: number, summary: readonly string[]): void;
103
+ /** Whether any collapsible block exists. */
104
+ get hasFolds(): boolean;
105
+ /** Whether the folds currently show their full form. */
106
+ get foldsExpanded(): boolean;
107
+ /**
108
+ * Swap every fold between its summary and its full form.
109
+ * @returns false when there is nothing to toggle.
110
+ */
111
+ toggleFolds(): boolean;
112
+ /** Return every fold to its summary, the way moving on reads as dismissal. */
113
+ collapseFolds(): void;
114
+ /** Put every fold into one form, whatever mix of states they are in now. */
115
+ private setFolds;
116
+ /**
117
+ * Replace the bottom rows.
118
+ * @param rows - the chrome, top to bottom.
119
+ * @param cursor - where the cursor belongs among them.
120
+ * @param focus - whether to show the cursor there.
121
+ */
122
+ setChrome(rows: readonly string[], cursor: ChromeCursor, focus: boolean): void;
123
+ /**
124
+ * Set the line shown while the reader is away from the tail.
125
+ *
126
+ * Drawn OVER the viewport's top row rather than added to the chrome: a notice
127
+ * that changed the chrome's height would move the input box as a side effect
128
+ * of scrolling, and would make a page up and a page down different sizes.
129
+ * @param text - the styled notice, already fitted.
130
+ */
131
+ setScrollNotice(text: string): void;
132
+ /**
133
+ * Scroll the transcript.
134
+ * @param delta - rows to move; negative scrolls back into history.
135
+ */
136
+ scrollBy(delta: number): void;
137
+ /**
138
+ * Scroll by a whole viewport, which is what the page keys mean.
139
+ * @param direction - -1 for back into history, 1 towards the tail.
140
+ */
141
+ scrollPage(direction: -1 | 1): void;
142
+ /** Jump back to the tail, which is also what a new submission does. */
143
+ scrollToBottom(): void;
144
+ /**
145
+ * Drop the transcript, keeping the chrome.
146
+ *
147
+ * Ctrl-L on a shared terminal clears a viewport the person may want back; on
148
+ * our own screen the buffer IS the session's history, so this empties it.
149
+ */
150
+ clearTranscript(): void;
151
+ /** Re-wrap and repaint after the terminal changed size. */
152
+ resize(): void;
153
+ /**
154
+ * Anchor a selection where the left button went down.
155
+ *
156
+ * The terminal cannot select for us while mouse reporting is on, so the
157
+ * viewport does it: press anchors, motion extends, release copies — the
158
+ * shape opencode and Claude give the same gesture.
159
+ * @param row - terminal row, 1-based.
160
+ * @param column - terminal column, 1-based.
161
+ */
162
+ mouseDown(row: number, column: number): void;
163
+ /**
164
+ * Extend the selection to where the pointer moved.
165
+ * @param row - terminal row, 1-based.
166
+ * @param column - terminal column, 1-based.
167
+ */
168
+ mouseDrag(row: number, column: number): void;
169
+ /**
170
+ * Finish the gesture.
171
+ *
172
+ * The highlight stays up — the copy already happened, and the marks show
173
+ * what it took — until the next click or reflow dismisses it.
174
+ * @returns the selected text, or undefined for a bare click.
175
+ */
176
+ mouseUp(): string | undefined;
177
+ /** The selection's bounds in order, top-left first. */
178
+ private orderedSelection;
179
+ /** The plain text under the selection, visual rows joined by newlines. */
180
+ private selectedText;
181
+ /**
182
+ * Map a terminal position to a physical buffer position.
183
+ * @param row - terminal row, 1-based.
184
+ * @param column - terminal column, 1-based.
185
+ * @param clamp - pull an outside position to the nearest content row, the
186
+ * way dragging past an edge keeps selecting, instead of refusing it.
187
+ * @returns the position, or undefined when it misses the content.
188
+ */
189
+ private locate;
190
+ /** Rows the transcript viewport occupies. */
191
+ private viewportHeight;
192
+ /** Columns content is laid out for, one short of the width so no row wraps. */
193
+ private contentColumns;
194
+ /** Re-wrap every kept line at the current width. */
195
+ private rewrap;
196
+ /**
197
+ * Compose and paint the frame.
198
+ *
199
+ * The viewport is padded at the top when the transcript is shorter than the
200
+ * screen, which is what puts the chrome at the bottom from the first frame
201
+ * rather than wherever output happened to reach.
202
+ */
203
+ private render;
204
+ }
@@ -44,7 +44,6 @@ export type SelectorStep = {
44
44
  kind: 'done';
45
45
  outcome: SelectOutcome;
46
46
  };
47
- /** An in-progress selection. */
48
47
  export declare class Selector {
49
48
  private readonly spec;
50
49
  private selected;
@@ -48,9 +48,15 @@ export declare class Spinner {
48
48
  /**
49
49
  * Start the indicator, or do nothing when it is already running or the
50
50
  * surface has no cursor to rewrite.
51
+ *
52
+ * The clock keeps running across a pause: the elapsed figure is the whole
53
+ * turn's, and an indicator that restarted from zero at every tool call would
54
+ * report each step instead.
51
55
  */
52
56
  start(): void;
53
- /** Stop the indicator and clear its line. */
57
+ /** Hide the indicator without forgetting when the turn began. */
58
+ pause(): void;
59
+ /** Stop the indicator: the turn is over, and the next one starts at zero. */
54
60
  stop(): void;
55
61
  /** Paint the current frame. */
56
62
  private draw;
@@ -50,11 +50,12 @@ export declare function createTheme(isTty: boolean, env: Record<string, string |
50
50
  /**
51
51
  * Display columns a string occupies once printed, ignoring styling sequences.
52
52
  *
53
- * Combining marks are counted as zero and East Asian Wide/Fullwidth code
54
- * points as two, which is what a terminal's own cursor arithmetic does. This
55
- * covers the alignment and wrapping this surface needs; it is not a complete
56
- * grapheme segmenter, so a ZWJ emoji sequence still counts each joined code
57
- * point ({@link ../README.md | Known Limitations}).
53
+ * Measured by `string-width` the width authority cli-table3, ink, and every
54
+ * maintained terminal renderer sit on so East Asian Wide, emoji presentation
55
+ * (`⚡` included), combining marks, and ZWJ sequences all match what a
56
+ * terminal's cursor actually does. A hand-kept range table here mis-sized `⚡`
57
+ * and sheared a real table's columns; widths are exactly the kind of data
58
+ * nobody should maintain by hand.
58
59
  * @param text - the string to measure, possibly carrying SGR sequences.
59
60
  * @returns the number of display columns.
60
61
  */
@@ -42,8 +42,8 @@ export declare class Transcript {
42
42
  private readonly options;
43
43
  private readonly presenters;
44
44
  private readonly calls;
45
- /** The most recent result whose body the cap clipped, kept in full. */
46
- private clipped;
45
+ /** The full form of the event just rendered, when its body was collapsed. */
46
+ private fold;
47
47
  constructor(options: TranscriptOptions, presenters: ToolPresenters);
48
48
  /**
49
49
  * Shorten an absolute path inside the workspace to a workspace-relative one.
@@ -88,14 +88,14 @@ export declare class Transcript {
88
88
  */
89
89
  private renderResult;
90
90
  /**
91
- * The last clipped result, rendered without its cap.
92
- *
93
- * Ctrl-O's answer. The full body is kept from the render itself because a
94
- * tool's own output limits are upstream of the log this is everything the
95
- * model saw, which is everything recoverable.
96
- * @returns the header and full body, or undefined when nothing was clipped.
91
+ * The expanded form of the lines {@link render} just returned, when that
92
+ * event's body was collapsed — the whole event re-rendered without its cap,
93
+ * because a fold swaps entire blocks, not just the clipped tail. The full
94
+ * body is kept from the render itself: what a tool truncated before
95
+ * returning is upstream of the log and unrecoverable everywhere.
96
+ * @returns the full lines, or undefined when nothing was collapsed.
97
97
  */
98
- expandLast(): string[] | undefined;
98
+ takeFold(): string[] | undefined;
99
99
  /**
100
100
  * Render one completed call's status suffix and body from its declared view.
101
101
  * @param view - the result view, absent when no presenter answered.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Display-width wrapping for styled text.
3
+ *
4
+ * Inside the alternate screen the terminal no longer wraps for us: a transcript
5
+ * line longer than the viewport must be broken into rows before it is painted,
6
+ * or it would overwrite the row below. The lines being broken are already
7
+ * styled, so a naive split would cut an escape sequence in half and leak
8
+ * gibberish — and a continuation row would lose the colour its first half set.
9
+ * @module codsh/src/wrap
10
+ */
11
+ /**
12
+ * Break one styled line into rows no wider than `columns` display columns.
13
+ *
14
+ * Styles carry across the break: each continuation row re-opens whatever was
15
+ * active where the cut fell, and every row that opened a style closes it.
16
+ * @param text - the styled line, without a terminator.
17
+ * @param columns - display columns available per row.
18
+ * @returns the rows, at least one (an empty line yields one empty row).
19
+ */
20
+ export declare function wrapStyled(text: string, columns: number): string[];
21
+ /**
22
+ * Wrap many lines, keeping their order.
23
+ * @param lines - styled lines.
24
+ * @param columns - display columns per row.
25
+ * @returns the physical rows they occupy.
26
+ */
27
+ export declare function wrapAll(lines: readonly string[], columns: number): string[];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codsh-cli",
3
3
  "description": "A Claude Code-style coding agent for the terminal, composed on the DeepSeek Harness (dsh): interactive TTY surface, plan mode, approvals, custom commands, and session management over the dsh plugin runtime",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -75,7 +75,8 @@
75
75
  "@deepseek-ai/dsh-workflow-worker-thread": "^0.1.0-rc.7",
76
76
  "@deepseek-ai/schemastery": "^3.18.1",
77
77
  "commander": "^15.0.0",
78
- "diff": "^9.0.0"
78
+ "diff": "^9.0.0",
79
+ "string-width": "^8.2.2"
79
80
  },
80
81
  "peerDependencies": {
81
82
  "@deepseek-ai/cordis": "^4.0.1",
@@ -144,6 +145,7 @@
144
145
  "test": "vitest run",
145
146
  "test:e2e": "pnpm run build && vitest run --config vitest.e2e.config.ts",
146
147
  "dev": "node scripts/dev.mjs",
148
+ "sync:dsh": "node scripts/sync-dsh.mjs",
147
149
  "release": "pnpm run build && changeset publish"
148
150
  }
149
151
  }