codsh-bundle 0.4.0 → 0.6.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.
- package/lib/index.js +1272 -146
- package/lib/types/clipboard-image.d.ts +44 -0
- package/lib/types/console.d.ts +19 -3
- package/lib/types/keys.d.ts +8 -0
- package/lib/types/prompt.d.ts +86 -1
- package/lib/types/screen.d.ts +111 -5
- package/lib/types/ship.d.ts +15 -0
- package/lib/types/todos.d.ts +49 -0
- package/lib/types/transcript.d.ts +84 -0
- package/lib/types/vision.d.ts +83 -0
- package/package.json +48 -46
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading an image off the system clipboard.
|
|
3
|
+
*
|
|
4
|
+
* An image never arrives through the terminal: bracketed paste is text by
|
|
5
|
+
* construction, and a screenshot sitting on the clipboard has no byte channel
|
|
6
|
+
* into stdin at all. So — exactly as Claude Code does — Ctrl+V asks the
|
|
7
|
+
* platform for the clipboard's image directly: `osascript` on macOS,
|
|
8
|
+
* `wl-paste`/`xclip` on Linux, PowerShell on Windows. A machine without the
|
|
9
|
+
* helper, or a clipboard holding text, reads as "no image" rather than an
|
|
10
|
+
* error, the same silent tolerance the clipboard WRITE path has.
|
|
11
|
+
* @module codsh-bundle/src/clipboard-image
|
|
12
|
+
*/
|
|
13
|
+
import type { ImageMediaType } from '@deepseek-ai/dsh-attachment/types';
|
|
14
|
+
/** An image read off the clipboard, plus what the flash line wants to say. */
|
|
15
|
+
export interface ClipboardImage {
|
|
16
|
+
/** The raw bytes. */
|
|
17
|
+
data: Buffer;
|
|
18
|
+
/** The type the bytes actually are, sniffed rather than trusted. */
|
|
19
|
+
mediaType: ImageMediaType;
|
|
20
|
+
/** Pixel width, when the header could be read. */
|
|
21
|
+
width?: number;
|
|
22
|
+
/** Pixel height, when the header could be read. */
|
|
23
|
+
height?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* What image type these bytes are, from their magic numbers.
|
|
27
|
+
*
|
|
28
|
+
* Sniffed rather than taken from the reader's word: the store verifies the
|
|
29
|
+
* declared type against the decoded bytes and refuses a mismatch, so lying
|
|
30
|
+
* here would only defer the failure to a worse moment.
|
|
31
|
+
* @param data - the bytes.
|
|
32
|
+
* @returns the media type, or undefined for anything that is not an image.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sniffImageType(data: Buffer): ImageMediaType | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* The image on the system clipboard, or undefined when it holds none.
|
|
37
|
+
*
|
|
38
|
+
* `CODSH_CLIPBOARD_IMAGE_CMD` overrides the platform reader with a shell
|
|
39
|
+
* command whose stdout is the image bytes — the seam the tests use, exactly
|
|
40
|
+
* as `CODSH_CLIPBOARD=osc52` keeps the write path off the real clipboard.
|
|
41
|
+
* @param env - the environment, for the override and the display probes.
|
|
42
|
+
* @returns the image with its sniffed type and dimensions, or undefined.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readClipboardImage(env: Record<string, string | undefined>): Promise<ClipboardImage | undefined>;
|
package/lib/types/console.d.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* same code path as a person typing.
|
|
15
15
|
* @module codsh-bundle/src/console
|
|
16
16
|
*/
|
|
17
|
+
import type { HoverBlock } from './screen.ts';
|
|
17
18
|
import type { Key } from './keys.ts';
|
|
18
19
|
/** The output stream this surface writes to. */
|
|
19
20
|
export interface OutputStream extends NodeJS.WritableStream {
|
|
@@ -145,8 +146,10 @@ export declare class TerminalConsole {
|
|
|
145
146
|
* what lets the transcript scroll under a prompt that does not move. Off one
|
|
146
147
|
* it is written straight out, because a pipe's reader wants exactly that.
|
|
147
148
|
* @param line - the line, without its terminator.
|
|
149
|
+
* @param rule - a styled left rule to draw down the line, `''` for none. Off
|
|
150
|
+
* a terminal it is dropped: a pipe's reader wants the text, not the frame.
|
|
148
151
|
*/
|
|
149
|
-
write(line: string): void;
|
|
152
|
+
write(line: string, rule?: string): void;
|
|
150
153
|
/**
|
|
151
154
|
* Replace the rows pinned below the transcript.
|
|
152
155
|
*
|
|
@@ -166,8 +169,11 @@ export declare class TerminalConsole {
|
|
|
166
169
|
* with, and scripts want the digest.
|
|
167
170
|
* @param summary - the collapsed lines.
|
|
168
171
|
* @param full - the expanded lines.
|
|
172
|
+
* @param rule - a styled left rule for the whole block, `''` for none.
|
|
173
|
+
* @param label - what the block is, for the readout naming what the pointer
|
|
174
|
+
* is over.
|
|
169
175
|
*/
|
|
170
|
-
appendFold(summary: readonly string[], full: readonly string[]): void;
|
|
176
|
+
appendFold(summary: readonly string[], full: readonly string[], rule?: string, label?: string): void;
|
|
171
177
|
/**
|
|
172
178
|
* Swap every collapsible block between summary and full form.
|
|
173
179
|
* @returns false when there is nothing to toggle.
|
|
@@ -184,6 +190,14 @@ export declare class TerminalConsole {
|
|
|
184
190
|
* @param column - terminal column, 1-based.
|
|
185
191
|
*/
|
|
186
192
|
mouseDrag(row: number, column: number): void;
|
|
193
|
+
/**
|
|
194
|
+
* Note where the pointer is resting, nothing held down.
|
|
195
|
+
* @param row - terminal row, 1-based.
|
|
196
|
+
* @param column - terminal column, 1-based.
|
|
197
|
+
* @returns the block now under the pointer when it changed, undefined
|
|
198
|
+
* otherwise.
|
|
199
|
+
*/
|
|
200
|
+
mouseMove(row: number, column: number): HoverBlock | undefined;
|
|
187
201
|
/**
|
|
188
202
|
* Finish the mouse selection.
|
|
189
203
|
* @returns the selected text, or undefined for a bare click.
|
|
@@ -208,8 +222,10 @@ export declare class TerminalConsole {
|
|
|
208
222
|
* summary would subtract from it.
|
|
209
223
|
* @param count - how many trailing lines the block owns.
|
|
210
224
|
* @param summary - the collapsed lines, already styled.
|
|
225
|
+
* @param label - what the block is, for the readout naming what the pointer
|
|
226
|
+
* is over.
|
|
211
227
|
*/
|
|
212
|
-
foldRecent(count: number, summary: readonly string[]): void;
|
|
228
|
+
foldRecent(count: number, summary: readonly string[], label?: string): void;
|
|
213
229
|
toggleFolds(): boolean;
|
|
214
230
|
/** Return every block to its summary. */
|
|
215
231
|
collapseFolds(): void;
|
package/lib/types/keys.d.ts
CHANGED
|
@@ -58,6 +58,8 @@ export type Key = {
|
|
|
58
58
|
kind: 'clear-screen';
|
|
59
59
|
} | {
|
|
60
60
|
kind: 'expand-output';
|
|
61
|
+
} | {
|
|
62
|
+
kind: 'toggle-todos';
|
|
61
63
|
} | {
|
|
62
64
|
kind: 'page';
|
|
63
65
|
direction: -1 | 1;
|
|
@@ -69,6 +71,8 @@ export type Key = {
|
|
|
69
71
|
} | {
|
|
70
72
|
kind: 'paste';
|
|
71
73
|
text: string;
|
|
74
|
+
} | {
|
|
75
|
+
kind: 'paste-image';
|
|
72
76
|
} | {
|
|
73
77
|
kind: 'mouse-down';
|
|
74
78
|
row: number;
|
|
@@ -77,6 +81,10 @@ export type Key = {
|
|
|
77
81
|
kind: 'mouse-drag';
|
|
78
82
|
row: number;
|
|
79
83
|
column: number;
|
|
84
|
+
} | {
|
|
85
|
+
kind: 'mouse-move';
|
|
86
|
+
row: number;
|
|
87
|
+
column: number;
|
|
80
88
|
} | {
|
|
81
89
|
kind: 'mouse-up';
|
|
82
90
|
row: number;
|
package/lib/types/prompt.d.ts
CHANGED
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
* pipe and draws nothing. Callers ask for the next submission either way.
|
|
9
9
|
* @module codsh-bundle/src/prompt
|
|
10
10
|
*/
|
|
11
|
+
import type { EncodedImageAttachment } from '@deepseek-ai/dsh-attachment/types';
|
|
12
|
+
import type { ClipboardImage } from './clipboard-image.ts';
|
|
11
13
|
import type { TerminalConsole } from './console.ts';
|
|
12
14
|
import type { EditorSources } from './editor.ts';
|
|
13
15
|
import type { SelectOutcome, SelectSpec } from './selector.ts';
|
|
14
16
|
import type { Theme } from './theme.ts';
|
|
17
|
+
import type { TodoList } from './todos.ts';
|
|
15
18
|
/** What the prompt reports to its owner. */
|
|
16
19
|
export interface PromptHandlers {
|
|
17
20
|
/** Ctrl-C: stop the work, or leave. */
|
|
@@ -24,6 +27,27 @@ export interface PromptHandlers {
|
|
|
24
27
|
shiftTab?(): void;
|
|
25
28
|
/** Ctrl-O: show the last clipped tool output in full. */
|
|
26
29
|
expandOutput?(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Ctrl-V: the system clipboard's image, or undefined for none.
|
|
32
|
+
*
|
|
33
|
+
* Injected rather than imported so the pure-module tests can hand the
|
|
34
|
+
* prompt a fixture instead of a machine's clipboard.
|
|
35
|
+
*/
|
|
36
|
+
readClipboardImage?(): Promise<ClipboardImage | undefined>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One pasted image awaiting its submission.
|
|
40
|
+
*
|
|
41
|
+
* The wire form the runtime admits, plus the dimensions the paste already
|
|
42
|
+
* probed — the submission pipeline says them back without re-decoding.
|
|
43
|
+
*/
|
|
44
|
+
export interface PendingImage {
|
|
45
|
+
/** The number the `[Image #N]` token wears, for context that names it back. */
|
|
46
|
+
id: number;
|
|
47
|
+
/** Base64 bytes and their sniffed media type. */
|
|
48
|
+
image: EncodedImageAttachment;
|
|
49
|
+
width?: number;
|
|
50
|
+
height?: number;
|
|
27
51
|
}
|
|
28
52
|
/** Drives the input box and answers reads and selections. */
|
|
29
53
|
export declare class Prompt {
|
|
@@ -42,13 +66,31 @@ export declare class Prompt {
|
|
|
42
66
|
* not be lost; the queue is what a line reader provides for free.
|
|
43
67
|
*/
|
|
44
68
|
private readonly queued;
|
|
69
|
+
/** Images pasted into the box, by the number their `[Image #N]` token wears. */
|
|
70
|
+
private readonly pendingImages;
|
|
71
|
+
/** Numbers are never reused within a session: a recalled token must not
|
|
72
|
+
* silently pick up a different image. */
|
|
73
|
+
private imageCounter;
|
|
74
|
+
/** The images belonging to the line the last read handed out. */
|
|
75
|
+
private submittedImages;
|
|
76
|
+
/** Whether a clipboard read is already in flight; a second Ctrl+V waits. */
|
|
77
|
+
private pastingImage;
|
|
45
78
|
/** The working indicator shown under the box. */
|
|
46
79
|
private hint;
|
|
47
80
|
/** A short-lived notice that borrows the hint row, e.g. the copy toast. */
|
|
48
81
|
private flash;
|
|
82
|
+
/** What the pointer is resting on, borrowing the hint row while it rests. */
|
|
83
|
+
private hover;
|
|
49
84
|
private flashTimer;
|
|
50
85
|
/** The always-current session facts shown as the region's last row. */
|
|
51
86
|
private status;
|
|
87
|
+
/**
|
|
88
|
+
* The agent's current todo list, kept in the chrome rather than only in the
|
|
89
|
+
* transcript: the card that announced it scrolls away, this does not.
|
|
90
|
+
*/
|
|
91
|
+
private todos;
|
|
92
|
+
/** Whether the todo readout shows every item or only the one in flight. */
|
|
93
|
+
private todosExpanded;
|
|
52
94
|
/** The assistant line still arriving, shown above the box. */
|
|
53
95
|
private streaming;
|
|
54
96
|
/** Frame styling for the current mode, e.g. plan mode's accent. */
|
|
@@ -106,6 +148,14 @@ export declare class Prompt {
|
|
|
106
148
|
* @param text - the styled row, or undefined to drop it.
|
|
107
149
|
*/
|
|
108
150
|
setStatus(text: string | undefined): void;
|
|
151
|
+
/**
|
|
152
|
+
* Set the todo readout to the list the session now holds.
|
|
153
|
+
*
|
|
154
|
+
* Compared by content, not identity: this is pushed on every session event,
|
|
155
|
+
* and a repaint per event would flicker the chrome for nothing.
|
|
156
|
+
* @param todos - the current list, empty to drop the readout.
|
|
157
|
+
*/
|
|
158
|
+
setTodos(todos: TodoList): void;
|
|
109
159
|
/**
|
|
110
160
|
* Set the frame accent, which is how a mode shows on the box itself.
|
|
111
161
|
* @param accent - the styling, or undefined for the default frame.
|
|
@@ -119,8 +169,9 @@ export declare class Prompt {
|
|
|
119
169
|
/**
|
|
120
170
|
* Write one finished transcript line above the region.
|
|
121
171
|
* @param line - the line to keep.
|
|
172
|
+
* @param rule - a styled left rule marking which block the line belongs to.
|
|
122
173
|
*/
|
|
123
|
-
write(line: string): void;
|
|
174
|
+
write(line: string, rule?: string): void;
|
|
124
175
|
/**
|
|
125
176
|
* Wait for the next submitted text.
|
|
126
177
|
* @param signal - abandons the read, which an aborted tool call does.
|
|
@@ -145,6 +196,40 @@ export declare class Prompt {
|
|
|
145
196
|
* @param key - the decoded keystroke.
|
|
146
197
|
*/
|
|
147
198
|
private onKey;
|
|
199
|
+
/**
|
|
200
|
+
* Read the clipboard and attach its image behind an `[Image #N]` token.
|
|
201
|
+
*
|
|
202
|
+
* The read shells out and takes real time, so it runs off the key handler;
|
|
203
|
+
* a second Ctrl+V during it is dropped rather than raced. Numbers count up
|
|
204
|
+
* for the whole session — a token in a recalled line must never quietly
|
|
205
|
+
* name a different image than the one it was minted for.
|
|
206
|
+
*/
|
|
207
|
+
private pasteImage;
|
|
208
|
+
/**
|
|
209
|
+
* The images a submitted line actually references, in token order.
|
|
210
|
+
*
|
|
211
|
+
* The tokens are the source of truth: a token the person deleted drops its
|
|
212
|
+
* image, a token duplicated by editing still names one attachment once.
|
|
213
|
+
* Claimed images leave the pending pool, so a token recalled from history
|
|
214
|
+
* later submits as plain text rather than resurrecting consumed bytes.
|
|
215
|
+
* @param text - the submitted line.
|
|
216
|
+
* @returns the referenced images, ready to ride the submission.
|
|
217
|
+
*/
|
|
218
|
+
private claimImages;
|
|
219
|
+
/**
|
|
220
|
+
* The images belonging to the line the last read returned.
|
|
221
|
+
*
|
|
222
|
+
* A drain, in the transcript's `take*` idiom: the caller reads the line,
|
|
223
|
+
* then takes its images exactly once.
|
|
224
|
+
* @returns the images in token order, empty for a plain line.
|
|
225
|
+
*/
|
|
226
|
+
takeAttachments(): PendingImage[];
|
|
227
|
+
/**
|
|
228
|
+
* The todo readout's rows: the one in flight, or the whole list once opened.
|
|
229
|
+
* @param columns - display columns available.
|
|
230
|
+
* @returns the rows, empty when no list is live.
|
|
231
|
+
*/
|
|
232
|
+
private todoRows;
|
|
148
233
|
/** Recompose and redraw the bottom region. */
|
|
149
234
|
private render;
|
|
150
235
|
}
|
package/lib/types/screen.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* viewport while the input box stays where it is, at the bottom.
|
|
9
9
|
*
|
|
10
10
|
* Owning the viewport means doing three jobs the terminal used to do. Lines are
|
|
11
|
-
* wrapped here ({@link
|
|
11
|
+
* wrapped here ({@link wrapStyled}), because a line that overflows would otherwise
|
|
12
12
|
* overwrite the row below. Scrolling is ours, because the terminal's scrollback
|
|
13
13
|
* does not exist on the alternate screen. And every frame is painted as a
|
|
14
14
|
* whole, diffed against the last one — which is what removes the class of bug
|
|
@@ -20,6 +20,15 @@ export interface ChromeCursor {
|
|
|
20
20
|
row: number;
|
|
21
21
|
column: number;
|
|
22
22
|
}
|
|
23
|
+
/** What the pointer is resting on, for a surface that names it. */
|
|
24
|
+
export interface HoverBlock {
|
|
25
|
+
/** What the block is, e.g. `thinking`. */
|
|
26
|
+
label: string;
|
|
27
|
+
/** Lines its full form holds. */
|
|
28
|
+
lines: number;
|
|
29
|
+
/** Whether it is showing that full form now. */
|
|
30
|
+
expanded: boolean;
|
|
31
|
+
}
|
|
23
32
|
/** What the screen writes to and measures itself against. */
|
|
24
33
|
export interface ScreenHost {
|
|
25
34
|
/** Emit raw bytes to the terminal. */
|
|
@@ -34,8 +43,18 @@ export declare class Screen {
|
|
|
34
43
|
private readonly host;
|
|
35
44
|
/** Logical transcript lines, unwrapped, oldest first. */
|
|
36
45
|
private logical;
|
|
46
|
+
/**
|
|
47
|
+
* The left rule each logical line carries, `''` for none.
|
|
48
|
+
*
|
|
49
|
+
* Kept beside the text rather than inside it: a rule has to repeat on every
|
|
50
|
+
* row a line wraps to, and it must never reach the clipboard — it is a mark
|
|
51
|
+
* the surface draws, not something the person wrote.
|
|
52
|
+
*/
|
|
53
|
+
private rules;
|
|
37
54
|
/** The same lines wrapped to the current width — what the viewport slices. */
|
|
38
55
|
private physical;
|
|
56
|
+
/** Display columns the rule occupies on each physical row, for copy and hits. */
|
|
57
|
+
private ruleWidths;
|
|
39
58
|
/** The bottom rows: input box, menu, indicator, status. */
|
|
40
59
|
private chrome;
|
|
41
60
|
private chromeCursor;
|
|
@@ -49,6 +68,18 @@ export declare class Screen {
|
|
|
49
68
|
private selection;
|
|
50
69
|
/** Collapsed blocks in the transcript, in order, with both of their forms. */
|
|
51
70
|
private folds;
|
|
71
|
+
/** The block the pointer rests on, or undefined when it rests on none. */
|
|
72
|
+
private hovered;
|
|
73
|
+
/**
|
|
74
|
+
* Physical row ranges the blocks occupy, or undefined when they need
|
|
75
|
+
* measuring again.
|
|
76
|
+
*
|
|
77
|
+
* Motion arrives a report per cell crossed, and measuring a block's row from
|
|
78
|
+
* the wrapped height of everything above it is a walk over the buffer — far
|
|
79
|
+
* too much to redo per report. The walk happens once after the buffer
|
|
80
|
+
* changes instead, and every report in between is a lookup.
|
|
81
|
+
*/
|
|
82
|
+
private ranges;
|
|
52
83
|
/** Whether the folds currently show their full form. */
|
|
53
84
|
private expanded;
|
|
54
85
|
/** The last painted frame, so a repaint only touches rows that changed. */
|
|
@@ -77,7 +108,7 @@ export declare class Screen {
|
|
|
77
108
|
* where they are, and the new rows accumulate below them.
|
|
78
109
|
* @param lines - the lines to keep, already styled.
|
|
79
110
|
*/
|
|
80
|
-
append(lines: readonly string[]): void;
|
|
111
|
+
append(lines: readonly string[], rule?: string): void;
|
|
81
112
|
/**
|
|
82
113
|
* Append one collapsible block: its summary now, its full form on demand.
|
|
83
114
|
*
|
|
@@ -86,8 +117,10 @@ export declare class Screen {
|
|
|
86
117
|
* place, exactly like a details/summary element.
|
|
87
118
|
* @param summary - the collapsed lines, already styled.
|
|
88
119
|
* @param full - the expanded lines, already styled.
|
|
120
|
+
* @param rule - a styled left rule for the whole block, `''` for none.
|
|
121
|
+
* @param label - what the block is, for the hover readout that names it.
|
|
89
122
|
*/
|
|
90
|
-
appendFold(summary: readonly string[], full: readonly string[]): void;
|
|
123
|
+
appendFold(summary: readonly string[], full: readonly string[], rule?: string, label?: string): void;
|
|
91
124
|
/**
|
|
92
125
|
* Turn the last `count` appended lines into a collapsible block after the
|
|
93
126
|
* fact.
|
|
@@ -98,19 +131,63 @@ export declare class Screen {
|
|
|
98
131
|
* collapses with the rest when the conversation moves on.
|
|
99
132
|
* @param count - how many trailing lines the block owns.
|
|
100
133
|
* @param summary - the collapsed lines, already styled.
|
|
134
|
+
* @param label - what the block is, for the hover readout that names it.
|
|
101
135
|
*/
|
|
102
|
-
foldBack(count: number, summary: readonly string[]): void;
|
|
136
|
+
foldBack(count: number, summary: readonly string[], label?: string): void;
|
|
103
137
|
/** Whether any collapsible block exists. */
|
|
104
138
|
get hasFolds(): boolean;
|
|
105
139
|
/** Whether the folds currently show their full form. */
|
|
106
140
|
get foldsExpanded(): boolean;
|
|
107
141
|
/**
|
|
108
142
|
* Swap every fold between its summary and its full form.
|
|
143
|
+
*
|
|
144
|
+
* What the blocks show decides the direction, not what the last Ctrl+O did:
|
|
145
|
+
* clicking blocks open one at a time would otherwise leave the key pointing
|
|
146
|
+
* the wrong way, and a press that visibly does nothing reads as broken.
|
|
109
147
|
* @returns false when there is nothing to toggle.
|
|
110
148
|
*/
|
|
111
149
|
toggleFolds(): boolean;
|
|
112
150
|
/** Return every fold to its summary, the way moving on reads as dismissal. */
|
|
113
151
|
collapseFolds(): void;
|
|
152
|
+
/**
|
|
153
|
+
* Work the block a bare click landed on, the way a details element opens.
|
|
154
|
+
*
|
|
155
|
+
* The whole block is the target, in both forms: collapsed, the `+N lines`
|
|
156
|
+
* line is what a person aims at, and open, anywhere inside the text folds it
|
|
157
|
+
* back — hunting for a head row that has scrolled off the top is not an
|
|
158
|
+
* affordance. Selecting text inside a block is a drag, which never reaches
|
|
159
|
+
* here, so reading is unaffected.
|
|
160
|
+
* @param row - physical buffer row the press anchored on.
|
|
161
|
+
*/
|
|
162
|
+
private clickFold;
|
|
163
|
+
/**
|
|
164
|
+
* Where each block sits in physical rows.
|
|
165
|
+
*
|
|
166
|
+
* Blocks are recorded in logical lines while the mouse reports physical
|
|
167
|
+
* rows, so the wrapped height of everything above a block is what bridges
|
|
168
|
+
* the two — measured under each line's own rule, which costs columns and so
|
|
169
|
+
* changes the height.
|
|
170
|
+
* @returns one range per block, in buffer order.
|
|
171
|
+
*/
|
|
172
|
+
private foldRanges;
|
|
173
|
+
/**
|
|
174
|
+
* The block covering a physical row.
|
|
175
|
+
* @param row - physical buffer row, 0-based.
|
|
176
|
+
* @returns the block, or undefined when the row is not in one.
|
|
177
|
+
*/
|
|
178
|
+
private foldAt;
|
|
179
|
+
/**
|
|
180
|
+
* Swap one block, leaving the reader where they were.
|
|
181
|
+
*
|
|
182
|
+
* Someone who opened a block halfway up their history did not ask to be
|
|
183
|
+
* moved to the tail: the rows above the block keep their screen positions,
|
|
184
|
+
* and the transcript grows or shrinks below them. Following the tail there
|
|
185
|
+
* is nothing to hold on to, so the frame keeps following it — which is what
|
|
186
|
+
* Ctrl+O does for every block at once.
|
|
187
|
+
* @param fold - the block to swap.
|
|
188
|
+
* @param expanded - the form to put on screen.
|
|
189
|
+
*/
|
|
190
|
+
private setFold;
|
|
114
191
|
/** Put every fold into one form, whatever mix of states they are in now. */
|
|
115
192
|
private setFolds;
|
|
116
193
|
/**
|
|
@@ -150,6 +227,19 @@ export declare class Screen {
|
|
|
150
227
|
clearTranscript(): void;
|
|
151
228
|
/** Re-wrap and repaint after the terminal changed size. */
|
|
152
229
|
resize(): void;
|
|
230
|
+
/**
|
|
231
|
+
* Note where the pointer is resting, with nothing held down.
|
|
232
|
+
*
|
|
233
|
+
* A block is clickable, so it says so while the pointer is on it rather
|
|
234
|
+
* than only once it is hit. Reports arrive a cell at a time, so the frame is
|
|
235
|
+
* only touched when the block under the pointer actually changes — moving
|
|
236
|
+
* along one block, or across the chrome, costs a lookup and nothing else.
|
|
237
|
+
* @param row - terminal row, 1-based.
|
|
238
|
+
* @param column - terminal column, 1-based.
|
|
239
|
+
* @returns the block under the pointer, or undefined for none — reported
|
|
240
|
+
* every time, so a caller need not track the changes itself.
|
|
241
|
+
*/
|
|
242
|
+
mouseMove(row: number, column: number): HoverBlock | undefined;
|
|
153
243
|
/**
|
|
154
244
|
* Anchor a selection where the left button went down.
|
|
155
245
|
*
|
|
@@ -170,7 +260,9 @@ export declare class Screen {
|
|
|
170
260
|
* Finish the gesture.
|
|
171
261
|
*
|
|
172
262
|
* The highlight stays up — the copy already happened, and the marks show
|
|
173
|
-
* what it took — until the next click or reflow dismisses it.
|
|
263
|
+
* what it took — until the next click or reflow dismisses it. A press that
|
|
264
|
+
* never moved is not a selection but a click, and a click on a collapsible
|
|
265
|
+
* block works that one block: open it, or fold it back.
|
|
174
266
|
* @returns the selected text, or undefined for a bare click.
|
|
175
267
|
*/
|
|
176
268
|
mouseUp(): string | undefined;
|
|
@@ -191,6 +283,20 @@ export declare class Screen {
|
|
|
191
283
|
private viewportHeight;
|
|
192
284
|
/** Columns content is laid out for, one short of the width so no row wraps. */
|
|
193
285
|
private contentColumns;
|
|
286
|
+
/**
|
|
287
|
+
* Wrap one logical line, repeating its rule on every row.
|
|
288
|
+
*
|
|
289
|
+
* The rule costs columns, so the text wraps inside what is left of the width;
|
|
290
|
+
* a continuation row without the rule would break the block's left edge
|
|
291
|
+
* exactly where a long line made it matter most.
|
|
292
|
+
* @param line - the styled logical line.
|
|
293
|
+
* @param rule - the styled left rule, `''` for none.
|
|
294
|
+
* @param columns - display columns available for rule and text together.
|
|
295
|
+
* @returns the physical rows, rule included.
|
|
296
|
+
*/
|
|
297
|
+
private wrapLine;
|
|
298
|
+
/** Re-wrap every kept line at the current width, rules and all. */
|
|
299
|
+
private wrapBuffer;
|
|
194
300
|
/** Re-wrap every kept line at the current width. */
|
|
195
301
|
private rewrap;
|
|
196
302
|
/**
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/ship` prompt: a canned workflow that takes a one-sentence requirement
|
|
3
|
+
* from idea to shipped, verified code — a research-grounded interview, a
|
|
4
|
+
* confirmed spec (gate 1), an approved plan (gate 2), then autonomous landing
|
|
5
|
+
* until the spec's acceptance criteria pass.
|
|
6
|
+
*
|
|
7
|
+
* The spec FILE is the workflow's memory, not the conversation: the approved
|
|
8
|
+
* plan is written into it, its Status line names the phase, its checkboxes
|
|
9
|
+
* are the progress, and a bare /ship offers to resume whatever it finds
|
|
10
|
+
* unfinished. Conversations get interrupted, compacted, and cleared; the file
|
|
11
|
+
* survives all three, which is what makes the landing reliable rather than
|
|
12
|
+
* merely well-intentioned.
|
|
13
|
+
*/
|
|
14
|
+
/** The `/ship` prompt body; `$ARGUMENTS` is the typed one-sentence requirement. */
|
|
15
|
+
export declare const SHIP_PROMPT = "Run the /ship workflow: take the one-sentence requirement below from idea to shipped, verified code in this repository. The requirement, exactly as typed:\n\n<idea>\n$ARGUMENTS\n</idea>\n\nIf the idea between the <idea> tags is empty, that is not an error. First look for unfinished work: scan the repository's spec directory (docs/specs/, or the repo's own design-document convention) for a spec whose Status line is not `shipped` \u2014 a bare /ship most likely means \"carry on\", so offer through ask_user_question to resume that spec from the phase its Status names, with everything below applying from that phase onward. Only when there is nothing to resume, ask for the one-sentence requirement with ask_user_question and use the answer as the idea. Images accompanying the command \u2014 [Image #N] tokens, <pasted-image> context, attached image blocks \u2014 are part of the requirement: a mockup or a screenshot is requirements material, so read it and cite what it shows in the interview.\n\nPhase 1 \u2014 grounded interview. Research before you ask: read the repository layout, the docs, and the code paths the idea touches, so every question is informed by what actually exists. Then interrogate the idea with ask_user_question, one focused question per call, never a batch. Cover, as far as they are genuinely open: who this is for and what success looks like, scope and explicit non-goals, constraints (compatibility, performance, security, dependencies), edge cases and failure behavior, and how the result should be verified. Prefer concrete options grounded in what you found over open-ended prompts. Do not ask what inspection can answer \u2014 where code lives or how current behavior works is yours to find out. Stop when answers stop changing the design; do not pad the interview to look thorough.\n\nPhase 2 \u2014 the spec (gate 1). Write the agreed design to a spec file inside the repository. Follow the repo's existing convention for design documents if one exists (a specs, rfcs, or ADR directory); otherwise create docs/specs/<kebab-case-slug>.md. The spec must stand alone for a reader without this conversation: the one-sentence requirement, background, each interview decision with its reason, scope and non-goals, constraints, edge cases, and a numbered list of acceptance criteria where every criterion names the exact command that proves it and the output that counts as passing \u2014 the final phase runs those commands verbatim, so a criterion without a command is not finished. Give the file a `Status:` line (interviewing, confirmed, planned, landing, shipped) and keep it current at every phase change: it is what lets an interrupted /ship resume instead of starting over. Present the spec file path and a compact summary through ask_user_question and get an explicit yes. If the answer amends or rejects it, update the file and ask again. Do not proceed on silence or a vague reply.\n\nPhase 3 \u2014 the plan (gate 2). Only after the spec is confirmed, produce an implementation plan: ordered milestones with the files each touches, the tests each milestone adds or changes, which acceptance criterion each milestone satisfies, and the commands that prove the whole thing (build, typecheck, test). Present the plan through ask_user_question and get an explicit yes; fold rejections back in and present again. Once approved, write the plan into the spec file as a `## Plan` section with one checkbox per milestone \u2014 an approved plan lives on disk, not in a conversation that can be compacted or lost. Then, still before any implementation code, establish the ground: check the working tree is clean (uncommitted unrelated changes are the user's to decide about \u2014 ask), and run the plan's proof commands once, recording the baseline in the spec. A baseline that is already red changes what \"green\" will mean, so surface it here rather than discovering it under your own diff. Write no implementation code before this gate passes, and do not use todo_write before it either \u2014 it tracks landing, not the interview.\n\nPhase 4 \u2014 landing. After gate 2, work autonomously; return to the user only for a genuine blocker that contradicts the spec, never for routine decisions. Either way the spec file \u2014 not this conversation \u2014 is the working memory: re-read it before starting each milestone, tick the milestone's checkbox and update Status as you go, and commit after each milestone turns green \u2014 small commits are the progress that survives a crash and the history a reviewer can walk. Choose the mechanism by the approved plan's size. If it has at most three milestones and you expect the whole change to fit comfortably in this session's context, implement in-session: track the milestones with todo_write, and for each one implement, run the tests, fix until green, then commit before moving on. If it is larger \u2014 four or more substantially independent milestones, or work you expect to exceed what one session can hold \u2014 the user running /ship is their explicit request for a fresh-agent Ralph loop: call the ralph tool once, with an objective that names the spec file path as the single source of truth, instructs each round to read the spec from disk (plan, checkboxes, baseline), pick the first unchecked milestone, implement and test it, then commit and tick its checkbox, and defines completion as every acceptance criterion in the spec passing. Bound the loop: budget about three rounds per milestone, and instruct it to stop and report rather than continue past two consecutive rounds that tick nothing.\n\nPhase 5 \u2014 done means verified. The workflow ends only when every acceptance criterion passes with you actually running its named command and reading the real output. After a Ralph loop returns, run every proof command again yourself \u2014 the loop's word is a report, not a verification. Never report a result you did not run, and never weaken a criterion to make it pass; if one cannot be met, say so plainly and why. When a decision changes mid-flight, update the spec file first so the file on disk stays the truth. Set Status to shipped only after that final run, and close with an honest report listing each criterion, the command that proved it, and what it printed \u2014 plus anything left open.\n\nIf the session is in plan mode, the plan-mode rules win: nothing here authorizes writes while it is active. Tell the user this workflow needs to write the spec file and ask them to leave plan mode before continuing past the interview.";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The todo readout: the pinned row that keeps the agent's list in view after
|
|
3
|
+
* the write that produced it has scrolled away, and the full list that row
|
|
4
|
+
* expands into.
|
|
5
|
+
*
|
|
6
|
+
* One renderer serves all three places the list shows — the transcript card,
|
|
7
|
+
* the pinned row, and `/todos` — so the glyphs and counts cannot drift apart.
|
|
8
|
+
* Nothing is tracked here: every figure comes from the `todos` projection, so a
|
|
9
|
+
* resumed session reports the list it left off with.
|
|
10
|
+
* @module codsh-bundle/src/todos
|
|
11
|
+
*/
|
|
12
|
+
import type { TodoItem } from '@deepseek-ai/dsh-session';
|
|
13
|
+
import type { Theme } from './theme.ts';
|
|
14
|
+
/** The list as the projection holds it: whole-value, latest write wins. */
|
|
15
|
+
export type TodoList = readonly TodoItem[];
|
|
16
|
+
/** How the full list may be shortened for a surface that cannot scroll. */
|
|
17
|
+
export interface TodoReportOptions {
|
|
18
|
+
/** A trailing note for the header, e.g. the key that collapses the list. */
|
|
19
|
+
hint?: string | undefined;
|
|
20
|
+
/** Most items to print; the rest are counted on one tail line. */
|
|
21
|
+
limit?: number | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Render the pinned row: one line naming the work in flight and the progress
|
|
25
|
+
* around it.
|
|
26
|
+
*
|
|
27
|
+
* This row is the whole point of reading from a projection rather than from the
|
|
28
|
+
* write event: the card that announced the list scrolls away, the row does not,
|
|
29
|
+
* so the list stays answerable at a glance for the rest of the session.
|
|
30
|
+
* @param todos - the current list.
|
|
31
|
+
* @param theme - styling for the segments.
|
|
32
|
+
* @param columns - display columns available; a longer row is cut, never wrapped.
|
|
33
|
+
* @param hint - a trailing note, e.g. the key that expands the list.
|
|
34
|
+
* @returns the row, or undefined when there is no list to report.
|
|
35
|
+
*/
|
|
36
|
+
export declare function todoRow(todos: TodoList, theme: Theme, columns: number, hint?: string): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Render the whole list: the header, then every item under it.
|
|
39
|
+
*
|
|
40
|
+
* A surface that cannot scroll passes `limit`, and the items past it are
|
|
41
|
+
* counted rather than dropped in silence — a list that looks complete but is
|
|
42
|
+
* not is worse than an honest tail.
|
|
43
|
+
* @param todos - the current list.
|
|
44
|
+
* @param theme - styling for the marks and text.
|
|
45
|
+
* @param columns - display columns available; longer lines are cut.
|
|
46
|
+
* @param options - header note and item cap.
|
|
47
|
+
* @returns the lines, empty when there is no list.
|
|
48
|
+
*/
|
|
49
|
+
export declare function todoReport(todos: TodoList, theme: Theme, columns: number, options?: TodoReportOptions): string[];
|
|
@@ -37,6 +37,69 @@ export interface TranscriptOptions {
|
|
|
37
37
|
/** Session workspace, stripped from absolute paths so cards stay short. */
|
|
38
38
|
cwd: string;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* The left rules the transcript draws down a block's edge, one per kind.
|
|
42
|
+
*
|
|
43
|
+
* A rule is how a segment shows where it starts and ends without a frame or a
|
|
44
|
+
* background fill: the references converge on a left border (Claude Code's
|
|
45
|
+
* `borderLeft`, opencode's `border: ["left"]`), and a border costs one column
|
|
46
|
+
* where a fill costs the terminal's own background — which is the theme's to
|
|
47
|
+
* decide, not this surface's (ADR-0001).
|
|
48
|
+
*
|
|
49
|
+
* The person's own words get the heavy mark; a tool block gets the light one,
|
|
50
|
+
* in the error colour when the call failed. What a person actually reads — an
|
|
51
|
+
* answer, a thinking summary — stays flush, so the rules mark the machinery
|
|
52
|
+
* around it rather than everything equally.
|
|
53
|
+
* @param theme - styling for the marks.
|
|
54
|
+
* @returns the rule per block kind.
|
|
55
|
+
*/
|
|
56
|
+
export declare function blockRules(theme: Theme): {
|
|
57
|
+
user: string;
|
|
58
|
+
tool: string;
|
|
59
|
+
error: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* What the two nameless block kinds are called when a readout names them.
|
|
63
|
+
*
|
|
64
|
+
* A tool block answers with its card's own title, which is already on screen;
|
|
65
|
+
* thinking and an answer have no title of their own, so these are theirs.
|
|
66
|
+
*/
|
|
67
|
+
export declare const FOLD_LABELS: {
|
|
68
|
+
readonly thinking: "thinking";
|
|
69
|
+
readonly answer: "answer";
|
|
70
|
+
};
|
|
71
|
+
/** A finished answer longer than this many rendered lines becomes a fold. */
|
|
72
|
+
export declare const ANSWER_FOLD_LINES = 24;
|
|
73
|
+
/** How many of its head lines a collapsed answer keeps visible. */
|
|
74
|
+
export declare const ANSWER_HEAD_LINES = 8;
|
|
75
|
+
/**
|
|
76
|
+
* The collapsed form of a finished answer, when it is long enough to fold.
|
|
77
|
+
*
|
|
78
|
+
* Shared by the live turn and by replay: a resumed session must offer the same
|
|
79
|
+
* summary the turn itself left behind, or history would read as a different
|
|
80
|
+
* conversation from the one that happened.
|
|
81
|
+
* @param lines - the answer's rendered lines, without its trailing blank.
|
|
82
|
+
* @param theme - styling for the count line.
|
|
83
|
+
* @returns the collapsed lines, or undefined when the answer is short enough
|
|
84
|
+
* to stand as it is.
|
|
85
|
+
*/
|
|
86
|
+
export declare function answerSummary(lines: readonly string[], theme: Theme): string[] | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* The two forms of a thinking block: one dim line, and the deliberation behind
|
|
89
|
+
* it.
|
|
90
|
+
*
|
|
91
|
+
* Pages of reasoning would bury the conversation, so the transcript keeps the
|
|
92
|
+
* summary and hands the rest to Ctrl+O — live and on replay alike.
|
|
93
|
+
* @param lines - the rendered thinking lines, already styled.
|
|
94
|
+
* @param theme - styling for the header.
|
|
95
|
+
* @param seconds - how long the thinking took, when the surface timed it; a
|
|
96
|
+
* replayed log carries no clock, so the header simply says it thought.
|
|
97
|
+
* @returns the collapsed and expanded forms.
|
|
98
|
+
*/
|
|
99
|
+
export declare function thinkingFold(lines: readonly string[], theme: Theme, seconds?: number): {
|
|
100
|
+
summary: string[];
|
|
101
|
+
full: string[];
|
|
102
|
+
};
|
|
40
103
|
/** Renders one session's appended events as terminal lines. */
|
|
41
104
|
export declare class Transcript {
|
|
42
105
|
private readonly options;
|
|
@@ -44,6 +107,10 @@ export declare class Transcript {
|
|
|
44
107
|
private readonly calls;
|
|
45
108
|
/** The full form of the event just rendered, when its body was collapsed. */
|
|
46
109
|
private fold;
|
|
110
|
+
/** What the block {@link render} just returned is, for a hover readout. */
|
|
111
|
+
private label;
|
|
112
|
+
/** The left rule the block {@link render} just returned belongs to. */
|
|
113
|
+
private rule;
|
|
47
114
|
constructor(options: TranscriptOptions, presenters: ToolPresenters);
|
|
48
115
|
/**
|
|
49
116
|
* Shorten an absolute path inside the workspace to a workspace-relative one.
|
|
@@ -96,6 +163,23 @@ export declare class Transcript {
|
|
|
96
163
|
* @returns the full lines, or undefined when nothing was collapsed.
|
|
97
164
|
*/
|
|
98
165
|
takeFold(): string[] | undefined;
|
|
166
|
+
/**
|
|
167
|
+
* What the block {@link takeFold} just described is called.
|
|
168
|
+
*
|
|
169
|
+
* The card's title, so the readout that names what the pointer rests on says
|
|
170
|
+
* the same thing the block's own head line says.
|
|
171
|
+
* @returns the label, or `''` when the block has no name of its own.
|
|
172
|
+
*/
|
|
173
|
+
takeLabel(): string;
|
|
174
|
+
/**
|
|
175
|
+
* The left rule for the block {@link render} just returned, `''` when the
|
|
176
|
+
* block stands flush.
|
|
177
|
+
*
|
|
178
|
+
* Paired with the lines rather than baked into them: the rule repeats on
|
|
179
|
+
* every row the block wraps to, which only the buffer that wraps them knows.
|
|
180
|
+
* @returns the styled rule, or `''`.
|
|
181
|
+
*/
|
|
182
|
+
takeRule(): string;
|
|
99
183
|
/**
|
|
100
184
|
* Render one completed call's status suffix and body from its declared view.
|
|
101
185
|
* @param view - the result view, absent when no presenter answered.
|