codsh-bundle 0.3.0 → 0.5.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 +880 -118
- package/lib/types/console.d.ts +32 -3
- package/lib/types/keys.d.ts +21 -0
- package/lib/types/prompt.d.ts +26 -1
- package/lib/types/screen.d.ts +111 -5
- package/lib/types/ship.d.ts +8 -0
- package/lib/types/theme.d.ts +17 -0
- package/lib/types/todos.d.ts +49 -0
- package/lib/types/transcript.d.ts +84 -0
- package/package.json +46 -46
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 {
|
|
@@ -42,6 +43,11 @@ export declare class TerminalConsole {
|
|
|
42
43
|
private keyHandler;
|
|
43
44
|
/** Keys decoded before any handler registered — type-ahead is never dropped. */
|
|
44
45
|
private readonly earlyKeys;
|
|
46
|
+
/** Whether the terminal window has focus; undefined until it reports. */
|
|
47
|
+
private focused;
|
|
48
|
+
/** The terminal's OSC 11 background answer, kept for late listeners. */
|
|
49
|
+
private background;
|
|
50
|
+
private backgroundHandler;
|
|
45
51
|
private escapeTimer;
|
|
46
52
|
private ended;
|
|
47
53
|
/** The viewport this surface owns on a terminal; absent off one. */
|
|
@@ -140,8 +146,10 @@ export declare class TerminalConsole {
|
|
|
140
146
|
* what lets the transcript scroll under a prompt that does not move. Off one
|
|
141
147
|
* it is written straight out, because a pipe's reader wants exactly that.
|
|
142
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.
|
|
143
151
|
*/
|
|
144
|
-
write(line: string): void;
|
|
152
|
+
write(line: string, rule?: string): void;
|
|
145
153
|
/**
|
|
146
154
|
* Replace the rows pinned below the transcript.
|
|
147
155
|
*
|
|
@@ -161,8 +169,11 @@ export declare class TerminalConsole {
|
|
|
161
169
|
* with, and scripts want the digest.
|
|
162
170
|
* @param summary - the collapsed lines.
|
|
163
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.
|
|
164
175
|
*/
|
|
165
|
-
appendFold(summary: readonly string[], full: readonly string[]): void;
|
|
176
|
+
appendFold(summary: readonly string[], full: readonly string[], rule?: string, label?: string): void;
|
|
166
177
|
/**
|
|
167
178
|
* Swap every collapsible block between summary and full form.
|
|
168
179
|
* @returns false when there is nothing to toggle.
|
|
@@ -179,6 +190,14 @@ export declare class TerminalConsole {
|
|
|
179
190
|
* @param column - terminal column, 1-based.
|
|
180
191
|
*/
|
|
181
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;
|
|
182
201
|
/**
|
|
183
202
|
* Finish the mouse selection.
|
|
184
203
|
* @returns the selected text, or undefined for a bare click.
|
|
@@ -203,8 +222,10 @@ export declare class TerminalConsole {
|
|
|
203
222
|
* summary would subtract from it.
|
|
204
223
|
* @param count - how many trailing lines the block owns.
|
|
205
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.
|
|
206
227
|
*/
|
|
207
|
-
foldRecent(count: number, summary: readonly string[]): void;
|
|
228
|
+
foldRecent(count: number, summary: readonly string[], label?: string): void;
|
|
208
229
|
toggleFolds(): boolean;
|
|
209
230
|
/** Return every block to its summary. */
|
|
210
231
|
collapseFolds(): void;
|
|
@@ -212,6 +233,14 @@ export declare class TerminalConsole {
|
|
|
212
233
|
clearRegion(): void;
|
|
213
234
|
/** Ring the terminal bell; a pipe gets nothing to beep with. */
|
|
214
235
|
bell(): void;
|
|
236
|
+
/**
|
|
237
|
+
* Register for the terminal's background-color answer (OSC 11).
|
|
238
|
+
*
|
|
239
|
+
* The answer often lands before anyone is ready to hear it — the query goes
|
|
240
|
+
* out with the first frame — so a buffered reply is delivered immediately.
|
|
241
|
+
* @param handler - receives the raw payload, e.g. `rgb:1e1e/1e1e/2e2e`.
|
|
242
|
+
*/
|
|
243
|
+
onBackground(handler: (payload: string) => void): void;
|
|
215
244
|
/**
|
|
216
245
|
* Set the terminal window title.
|
|
217
246
|
* @param title - the title text; control bytes are the terminal's to reject.
|
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;
|
|
@@ -77,10 +79,21 @@ export type Key = {
|
|
|
77
79
|
kind: 'mouse-drag';
|
|
78
80
|
row: number;
|
|
79
81
|
column: number;
|
|
82
|
+
} | {
|
|
83
|
+
kind: 'mouse-move';
|
|
84
|
+
row: number;
|
|
85
|
+
column: number;
|
|
80
86
|
} | {
|
|
81
87
|
kind: 'mouse-up';
|
|
82
88
|
row: number;
|
|
83
89
|
column: number;
|
|
90
|
+
} | {
|
|
91
|
+
kind: 'focus';
|
|
92
|
+
focused: boolean;
|
|
93
|
+
} | {
|
|
94
|
+
kind: 'osc-reply';
|
|
95
|
+
code: number;
|
|
96
|
+
payload: string;
|
|
84
97
|
};
|
|
85
98
|
/** Decodes terminal bytes into keys, holding partial sequences between reads. */
|
|
86
99
|
export declare class KeyDecoder {
|
|
@@ -111,6 +124,14 @@ export declare class KeyDecoder {
|
|
|
111
124
|
* @returns the keys produced, or undefined when more bytes are needed.
|
|
112
125
|
*/
|
|
113
126
|
private take;
|
|
127
|
+
/**
|
|
128
|
+
* Map one kitty-protocol report onto the same keys the legacy bytes make.
|
|
129
|
+
* @param code - the key's Unicode code point.
|
|
130
|
+
* @param mods - the encoded modifiers, offset by one.
|
|
131
|
+
* @param event - press (1), repeat (2), or release (3).
|
|
132
|
+
* @returns the keys produced; unknown chords are swallowed, never typed.
|
|
133
|
+
*/
|
|
134
|
+
private kittyKey;
|
|
114
135
|
/**
|
|
115
136
|
* Collect bracketed-paste content up to its end marker.
|
|
116
137
|
* @returns the paste key once complete, otherwise undefined.
|
package/lib/types/prompt.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { TerminalConsole } from './console.ts';
|
|
|
12
12
|
import type { EditorSources } from './editor.ts';
|
|
13
13
|
import type { SelectOutcome, SelectSpec } from './selector.ts';
|
|
14
14
|
import type { Theme } from './theme.ts';
|
|
15
|
+
import type { TodoList } from './todos.ts';
|
|
15
16
|
/** What the prompt reports to its owner. */
|
|
16
17
|
export interface PromptHandlers {
|
|
17
18
|
/** Ctrl-C: stop the work, or leave. */
|
|
@@ -46,9 +47,18 @@ export declare class Prompt {
|
|
|
46
47
|
private hint;
|
|
47
48
|
/** A short-lived notice that borrows the hint row, e.g. the copy toast. */
|
|
48
49
|
private flash;
|
|
50
|
+
/** What the pointer is resting on, borrowing the hint row while it rests. */
|
|
51
|
+
private hover;
|
|
49
52
|
private flashTimer;
|
|
50
53
|
/** The always-current session facts shown as the region's last row. */
|
|
51
54
|
private status;
|
|
55
|
+
/**
|
|
56
|
+
* The agent's current todo list, kept in the chrome rather than only in the
|
|
57
|
+
* transcript: the card that announced it scrolls away, this does not.
|
|
58
|
+
*/
|
|
59
|
+
private todos;
|
|
60
|
+
/** Whether the todo readout shows every item or only the one in flight. */
|
|
61
|
+
private todosExpanded;
|
|
52
62
|
/** The assistant line still arriving, shown above the box. */
|
|
53
63
|
private streaming;
|
|
54
64
|
/** Frame styling for the current mode, e.g. plan mode's accent. */
|
|
@@ -106,6 +116,14 @@ export declare class Prompt {
|
|
|
106
116
|
* @param text - the styled row, or undefined to drop it.
|
|
107
117
|
*/
|
|
108
118
|
setStatus(text: string | undefined): void;
|
|
119
|
+
/**
|
|
120
|
+
* Set the todo readout to the list the session now holds.
|
|
121
|
+
*
|
|
122
|
+
* Compared by content, not identity: this is pushed on every session event,
|
|
123
|
+
* and a repaint per event would flicker the chrome for nothing.
|
|
124
|
+
* @param todos - the current list, empty to drop the readout.
|
|
125
|
+
*/
|
|
126
|
+
setTodos(todos: TodoList): void;
|
|
109
127
|
/**
|
|
110
128
|
* Set the frame accent, which is how a mode shows on the box itself.
|
|
111
129
|
* @param accent - the styling, or undefined for the default frame.
|
|
@@ -119,8 +137,9 @@ export declare class Prompt {
|
|
|
119
137
|
/**
|
|
120
138
|
* Write one finished transcript line above the region.
|
|
121
139
|
* @param line - the line to keep.
|
|
140
|
+
* @param rule - a styled left rule marking which block the line belongs to.
|
|
122
141
|
*/
|
|
123
|
-
write(line: string): void;
|
|
142
|
+
write(line: string, rule?: string): void;
|
|
124
143
|
/**
|
|
125
144
|
* Wait for the next submitted text.
|
|
126
145
|
* @param signal - abandons the read, which an aborted tool call does.
|
|
@@ -145,6 +164,12 @@ export declare class Prompt {
|
|
|
145
164
|
* @param key - the decoded keystroke.
|
|
146
165
|
*/
|
|
147
166
|
private onKey;
|
|
167
|
+
/**
|
|
168
|
+
* The todo readout's rows: the one in flight, or the whole list once opened.
|
|
169
|
+
* @param columns - display columns available.
|
|
170
|
+
* @returns the rows, empty when no list is live.
|
|
171
|
+
*/
|
|
172
|
+
private todoRows;
|
|
148
173
|
/** Recompose and redraw the bottom region. */
|
|
149
174
|
private render;
|
|
150
175
|
}
|
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,8 @@
|
|
|
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 `/ship` prompt body; `$ARGUMENTS` is the typed one-sentence requirement. */
|
|
8
|
+
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: before anything else, ask for the one-sentence requirement with ask_user_question, and use the answer as the idea for the rest of this workflow.\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 is objectively checkable \u2014 named tests, commands with expected output, observable behavior. 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. 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. 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, and fix until green 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 durable source of truth, instructs each round to read the spec and plan from disk, pick up the next unfinished milestone, implement and test it, and record progress in the workspace, and defines completion as every acceptance criterion in the spec passing.\n\nPhase 5 \u2014 done means verified. The workflow ends only when every acceptance criterion passes with you actually running the named tests and commands and reading their real output. 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. Close with a short honest report: what shipped, what was verified and how, and 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.";
|
package/lib/types/theme.d.ts
CHANGED
|
@@ -21,6 +21,14 @@ export interface Theme {
|
|
|
21
21
|
path(text: string): string;
|
|
22
22
|
/** The user's own echoed input. */
|
|
23
23
|
user(text: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Adopt the light- or dark-background palette.
|
|
26
|
+
*
|
|
27
|
+
* Base ANSI colors are the terminal theme's to map, but the secondary-text
|
|
28
|
+
* gray is an absolute palette entry, and the shade that recedes on a dark
|
|
29
|
+
* background washes out on a light one.
|
|
30
|
+
*/
|
|
31
|
+
setLight(light: boolean): void;
|
|
24
32
|
/** Roles used inside a fenced code block. */
|
|
25
33
|
readonly syntax: SyntaxTheme;
|
|
26
34
|
}
|
|
@@ -47,6 +55,15 @@ export interface SyntaxTheme {
|
|
|
47
55
|
* @returns the styling functions for this surface.
|
|
48
56
|
*/
|
|
49
57
|
export declare function createTheme(isTty: boolean, env: Record<string, string | undefined>): Theme;
|
|
58
|
+
/**
|
|
59
|
+
* Whether an OSC 10/11 color answer names a light color.
|
|
60
|
+
*
|
|
61
|
+
* Channels arrive as `rgb:RR/GG/BB` with one to four hex digits each; each is
|
|
62
|
+
* normalized by its own width before the relative-luminance weighting.
|
|
63
|
+
* @param payload - the reply payload, e.g. `rgb:ffff/ffff/ffff`.
|
|
64
|
+
* @returns true for light, false for dark, undefined when unparseable.
|
|
65
|
+
*/
|
|
66
|
+
export declare function backgroundIsLight(payload: string): boolean | undefined;
|
|
50
67
|
/**
|
|
51
68
|
* Display columns a string occupies once printed, ignoring styling sequences.
|
|
52
69
|
*
|
|
@@ -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.
|