codsh-bundle 0.3.0 → 0.4.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 +160 -4
- package/lib/types/console.d.ts +13 -0
- package/lib/types/keys.d.ts +15 -0
- package/lib/types/theme.d.ts +17 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -88,6 +88,7 @@ const SGR$1 = {
|
|
|
88
88
|
/** A theme that emits no sequences, used off a TTY and under `NO_COLOR`. */
|
|
89
89
|
const PLAIN = {
|
|
90
90
|
colored: false,
|
|
91
|
+
setLight: () => {},
|
|
91
92
|
dim: (text) => text,
|
|
92
93
|
bold: (text) => text,
|
|
93
94
|
error: (text) => text,
|
|
@@ -122,9 +123,13 @@ function createTheme(isTty, env) {
|
|
|
122
123
|
if (!isTty || env.NO_COLOR !== void 0) return PLAIN;
|
|
123
124
|
const palette = env.TERM?.includes("256color") === true || env.COLORTERM !== void 0;
|
|
124
125
|
const wrap = (code) => (text) => `${code}${text}${SGR$1.reset}`;
|
|
126
|
+
let gray = "\x1B[38;5;245m";
|
|
125
127
|
return {
|
|
126
128
|
colored: true,
|
|
127
|
-
|
|
129
|
+
setLight(light) {
|
|
130
|
+
gray = light ? "\x1B[38;5;242m" : "\x1B[38;5;245m";
|
|
131
|
+
},
|
|
132
|
+
dim: (text) => `${palette ? gray : SGR$1.dim}${text}${SGR$1.reset}`,
|
|
128
133
|
bold: wrap(SGR$1.bold),
|
|
129
134
|
error: wrap(SGR$1.red),
|
|
130
135
|
success: wrap(SGR$1.green),
|
|
@@ -141,6 +146,25 @@ function createTheme(isTty, env) {
|
|
|
141
146
|
};
|
|
142
147
|
}
|
|
143
148
|
/**
|
|
149
|
+
* Whether an OSC 10/11 color answer names a light color.
|
|
150
|
+
*
|
|
151
|
+
* Channels arrive as `rgb:RR/GG/BB` with one to four hex digits each; each is
|
|
152
|
+
* normalized by its own width before the relative-luminance weighting.
|
|
153
|
+
* @param payload - the reply payload, e.g. `rgb:ffff/ffff/ffff`.
|
|
154
|
+
* @returns true for light, false for dark, undefined when unparseable.
|
|
155
|
+
*/
|
|
156
|
+
function backgroundIsLight(payload) {
|
|
157
|
+
const match = /^rgba?:([0-9a-f]{1,4})\/([0-9a-f]{1,4})\/([0-9a-f]{1,4})/i.exec(payload.trim());
|
|
158
|
+
if (match === null) return void 0;
|
|
159
|
+
const channel = (hex) => Number.parseInt(hex, 16) / (16 ** hex.length - 1);
|
|
160
|
+
const [red, green, blue] = [
|
|
161
|
+
channel(match[1] ?? "0"),
|
|
162
|
+
channel(match[2] ?? "0"),
|
|
163
|
+
channel(match[3] ?? "0")
|
|
164
|
+
];
|
|
165
|
+
return .2126 * red + .7152 * green + .0722 * blue > .5;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
144
168
|
* Display columns a string occupies once printed, ignoring styling sequences.
|
|
145
169
|
*
|
|
146
170
|
* Measured by `string-width` — the width authority cli-table3, ink, and every
|
|
@@ -653,14 +677,48 @@ const PASTE_END = "\x1B[201~";
|
|
|
653
677
|
const WHEEL_LINES = 1;
|
|
654
678
|
/** An SGR mouse report: `ESC [ < button ; column ; row (M|m)`. */
|
|
655
679
|
const MOUSE = /^\u001B\[<(\d+);(\d+);(\d+)([Mm])/;
|
|
680
|
+
/**
|
|
681
|
+
* A kitty-keyboard-protocol report: `ESC [ code (:alternates) ; mods (:event) u`.
|
|
682
|
+
*
|
|
683
|
+
* The viewport pushes the protocol's disambiguate flag on entry (the same
|
|
684
|
+
* flag Claude Code pushes), so terminals that speak it report Esc and every
|
|
685
|
+
* modified key unambiguously — which is what makes Shift+Enter a key at all.
|
|
686
|
+
* Terminals that don't speak it ignore the push and keep sending the legacy
|
|
687
|
+
* sequences below.
|
|
688
|
+
*/
|
|
689
|
+
const KITTY = /^\u001B\[(\d+)(?::\d+)*(?:;(\d+)(?::(\d+))?)?u/;
|
|
690
|
+
/** Kitty modifier bits, after the encoded +1 offset is removed. */
|
|
691
|
+
const KITTY_SHIFT = 1;
|
|
692
|
+
/** The Alt bit. */
|
|
693
|
+
const KITTY_ALT = 2;
|
|
694
|
+
/** The Control bit. */
|
|
695
|
+
const KITTY_CTRL = 4;
|
|
656
696
|
/** Wheel-up button code in the SGR encoding; wheel-down is one higher. */
|
|
657
697
|
const WHEEL_UP = 64;
|
|
658
698
|
/** Modifier bits in an SGR button code: Shift, Meta, and Control. */
|
|
659
699
|
const MOUSE_MODIFIERS = 28;
|
|
660
700
|
/** The motion bit, set on reports sent while a button is held. */
|
|
661
701
|
const MOUSE_MOTION = 32;
|
|
702
|
+
/**
|
|
703
|
+
* An OSC reply from the terminal: `ESC ] code ; payload (BEL | ESC \)`.
|
|
704
|
+
*
|
|
705
|
+
* The viewport asks for the background color (OSC 11) on entry; the reply
|
|
706
|
+
* arrives on stdin and must be consumed here — leaked past the decoder it
|
|
707
|
+
* would be typed into the input box as text.
|
|
708
|
+
*/
|
|
709
|
+
const OSC_REPLY = /^\u001B\](\d+);([^\u0007\u001B]*)(?:\u0007|\u001B\\)/;
|
|
710
|
+
/** An OSC reply still arriving, its possible ST half included. */
|
|
711
|
+
const OSC_PARTIAL = /^\u001B\](?:\d*(?:;[^\u0007\u001B]*)?)?\u001B?$/;
|
|
662
712
|
/** Sequences that resolve to one key, longest first so a prefix never wins. */
|
|
663
713
|
const SEQUENCES = [
|
|
714
|
+
["\x1B[I", {
|
|
715
|
+
kind: "focus",
|
|
716
|
+
focused: true
|
|
717
|
+
}],
|
|
718
|
+
["\x1B[O", {
|
|
719
|
+
kind: "focus",
|
|
720
|
+
focused: false
|
|
721
|
+
}],
|
|
664
722
|
["\x1B[5~", {
|
|
665
723
|
kind: "page",
|
|
666
724
|
direction: -1
|
|
@@ -816,7 +874,23 @@ var KeyDecoder = class {
|
|
|
816
874
|
}
|
|
817
875
|
return [];
|
|
818
876
|
}
|
|
819
|
-
|
|
877
|
+
const kitty = KITTY.exec(this.held);
|
|
878
|
+
if (kitty !== null) {
|
|
879
|
+
this.held = this.held.slice(kitty[0].length);
|
|
880
|
+
return this.kittyKey(Number(kitty[1]), Number(kitty[2] ?? "1"), Number(kitty[3] ?? "1"));
|
|
881
|
+
}
|
|
882
|
+
const osc = OSC_REPLY.exec(this.held);
|
|
883
|
+
if (osc !== null) {
|
|
884
|
+
this.held = this.held.slice(osc[0].length);
|
|
885
|
+
const code = Number(osc[1]);
|
|
886
|
+
if (code === 10 || code === 11) return [{
|
|
887
|
+
kind: "osc-reply",
|
|
888
|
+
code,
|
|
889
|
+
payload: osc[2] ?? ""
|
|
890
|
+
}];
|
|
891
|
+
return [];
|
|
892
|
+
}
|
|
893
|
+
if (/^\u001B(\[[\d;:<]*)?$/.test(this.held) || OSC_PARTIAL.test(this.held)) return void 0;
|
|
820
894
|
for (const [sequence, key] of SEQUENCES) {
|
|
821
895
|
if (this.held.startsWith(sequence)) {
|
|
822
896
|
this.held = this.held.slice(sequence.length);
|
|
@@ -846,6 +920,38 @@ var KeyDecoder = class {
|
|
|
846
920
|
}];
|
|
847
921
|
}
|
|
848
922
|
/**
|
|
923
|
+
* Map one kitty-protocol report onto the same keys the legacy bytes make.
|
|
924
|
+
* @param code - the key's Unicode code point.
|
|
925
|
+
* @param mods - the encoded modifiers, offset by one.
|
|
926
|
+
* @param event - press (1), repeat (2), or release (3).
|
|
927
|
+
* @returns the keys produced; unknown chords are swallowed, never typed.
|
|
928
|
+
*/
|
|
929
|
+
kittyKey(code, mods, event) {
|
|
930
|
+
if (event === 3) return [];
|
|
931
|
+
const bits = Math.max(0, mods - 1);
|
|
932
|
+
const shift = (bits & KITTY_SHIFT) !== 0;
|
|
933
|
+
const alt = (bits & KITTY_ALT) !== 0;
|
|
934
|
+
const ctrl = (bits & KITTY_CTRL) !== 0;
|
|
935
|
+
if (code === 27) return [{ kind: "escape" }];
|
|
936
|
+
if (code === 13) return [shift || alt ? { kind: "newline" } : { kind: "enter" }];
|
|
937
|
+
if (code === 9) return [shift ? { kind: "shift-tab" } : { kind: "tab" }];
|
|
938
|
+
if (code === 127 || code === 8) return [alt || ctrl ? { kind: "kill-word" } : { kind: "backspace" }];
|
|
939
|
+
if (ctrl && code >= 97 && code <= 122) {
|
|
940
|
+
const control = CONTROLS[String.fromCharCode(code - 96)];
|
|
941
|
+
return control === void 0 ? [] : [control];
|
|
942
|
+
}
|
|
943
|
+
if (alt) {
|
|
944
|
+
if (code === 98) return [{ kind: "word-left" }];
|
|
945
|
+
if (code === 102) return [{ kind: "word-right" }];
|
|
946
|
+
return [];
|
|
947
|
+
}
|
|
948
|
+
if (!ctrl && code >= 32) return [{
|
|
949
|
+
kind: "text",
|
|
950
|
+
text: String.fromCodePoint(code)
|
|
951
|
+
}];
|
|
952
|
+
return [];
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
849
955
|
* Collect bracketed-paste content up to its end marker.
|
|
850
956
|
* @returns the paste key once complete, otherwise undefined.
|
|
851
957
|
*/
|
|
@@ -958,6 +1064,24 @@ const LEAVE_ALT = "\x1B[?1049l";
|
|
|
958
1064
|
const ENABLE_MOUSE = "\x1B[?1002h\x1B[?1006h";
|
|
959
1065
|
/** Stop reporting them. */
|
|
960
1066
|
const DISABLE_MOUSE = "\x1B[?1006l\x1B[?1002l";
|
|
1067
|
+
/**
|
|
1068
|
+
* Push the kitty keyboard protocol's disambiguate flag — what Claude Code
|
|
1069
|
+
* pushes — so Shift+Enter, Esc, and control chords report unambiguously on
|
|
1070
|
+
* terminals that speak it. Others ignore the push and keep the legacy bytes.
|
|
1071
|
+
*/
|
|
1072
|
+
const ENABLE_KITTY_KEYS = "\x1B[>1u";
|
|
1073
|
+
/** Pop it, restoring whatever the shell had. */
|
|
1074
|
+
const DISABLE_KITTY_KEYS = "\x1B[<u";
|
|
1075
|
+
/** Report focus in/out (mode 1004), which the bell policy reads. */
|
|
1076
|
+
const ENABLE_FOCUS = "\x1B[?1004h";
|
|
1077
|
+
/** Stop reporting focus. */
|
|
1078
|
+
const DISABLE_FOCUS = "\x1B[?1004l";
|
|
1079
|
+
/**
|
|
1080
|
+
* Ask the terminal for its background color (OSC 11), the way opencode and
|
|
1081
|
+
* Codex do. The reply decides the light-background palette; a terminal that
|
|
1082
|
+
* never answers leaves the dark default standing.
|
|
1083
|
+
*/
|
|
1084
|
+
const QUERY_BACKGROUND = "\x1B]11;?\x07";
|
|
961
1085
|
/** Ask the terminal to paint a frame atomically, so no half-frame is shown. */
|
|
962
1086
|
const SYNC_BEGIN = "\x1B[?2026h";
|
|
963
1087
|
/** End the atomic frame. */
|
|
@@ -1037,7 +1161,7 @@ var Screen = class {
|
|
|
1037
1161
|
enter() {
|
|
1038
1162
|
if (this.active) return;
|
|
1039
1163
|
this.active = true;
|
|
1040
|
-
this.host.write(`${ENTER_ALT}${ENABLE_MOUSE}${HIDE_CURSOR}`);
|
|
1164
|
+
this.host.write(`${ENTER_ALT}${ENABLE_MOUSE}${ENABLE_KITTY_KEYS}${ENABLE_FOCUS}${QUERY_BACKGROUND}${HIDE_CURSOR}`);
|
|
1041
1165
|
this.painted = [];
|
|
1042
1166
|
this.render();
|
|
1043
1167
|
}
|
|
@@ -1050,7 +1174,7 @@ var Screen = class {
|
|
|
1050
1174
|
leave() {
|
|
1051
1175
|
if (!this.active) return;
|
|
1052
1176
|
this.active = false;
|
|
1053
|
-
this.host.write(`${DISABLE_MOUSE}${SHOW_CURSOR}${LEAVE_ALT}`);
|
|
1177
|
+
this.host.write(`${DISABLE_FOCUS}${DISABLE_KITTY_KEYS}${DISABLE_MOUSE}${SHOW_CURSOR}${LEAVE_ALT}`);
|
|
1054
1178
|
this.painted = [];
|
|
1055
1179
|
}
|
|
1056
1180
|
/**
|
|
@@ -1442,6 +1566,11 @@ var TerminalConsole = class {
|
|
|
1442
1566
|
keyHandler;
|
|
1443
1567
|
/** Keys decoded before any handler registered — type-ahead is never dropped. */
|
|
1444
1568
|
earlyKeys = [];
|
|
1569
|
+
/** Whether the terminal window has focus; undefined until it reports. */
|
|
1570
|
+
focused;
|
|
1571
|
+
/** The terminal's OSC 11 background answer, kept for late listeners. */
|
|
1572
|
+
background;
|
|
1573
|
+
backgroundHandler;
|
|
1445
1574
|
escapeTimer;
|
|
1446
1575
|
ended = false;
|
|
1447
1576
|
/** The viewport this surface owns on a terminal; absent off one. */
|
|
@@ -1600,6 +1729,17 @@ var TerminalConsole = class {
|
|
|
1600
1729
|
}
|
|
1601
1730
|
/** Hand one key to the handler, or hold it until one registers. */
|
|
1602
1731
|
deliver(key) {
|
|
1732
|
+
if (key.kind === "focus") {
|
|
1733
|
+
this.focused = key.focused;
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
if (key.kind === "osc-reply") {
|
|
1737
|
+
if (key.code === 11) {
|
|
1738
|
+
this.background = key.payload;
|
|
1739
|
+
this.backgroundHandler?.(key.payload);
|
|
1740
|
+
}
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1603
1743
|
if (this.keyHandler === void 0) {
|
|
1604
1744
|
this.earlyKeys.push(key);
|
|
1605
1745
|
return;
|
|
@@ -1771,9 +1911,21 @@ var TerminalConsole = class {
|
|
|
1771
1911
|
/** Ring the terminal bell; a pipe gets nothing to beep with. */
|
|
1772
1912
|
bell() {
|
|
1773
1913
|
if (!this.isTty) return;
|
|
1914
|
+
if (this.focused === true) return;
|
|
1774
1915
|
this.output.write("\x07");
|
|
1775
1916
|
}
|
|
1776
1917
|
/**
|
|
1918
|
+
* Register for the terminal's background-color answer (OSC 11).
|
|
1919
|
+
*
|
|
1920
|
+
* The answer often lands before anyone is ready to hear it — the query goes
|
|
1921
|
+
* out with the first frame — so a buffered reply is delivered immediately.
|
|
1922
|
+
* @param handler - receives the raw payload, e.g. `rgb:1e1e/1e1e/2e2e`.
|
|
1923
|
+
*/
|
|
1924
|
+
onBackground(handler) {
|
|
1925
|
+
this.backgroundHandler = handler;
|
|
1926
|
+
if (this.background !== void 0) handler(this.background);
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1777
1929
|
* Set the terminal window title.
|
|
1778
1930
|
* @param title - the title text; control bytes are the terminal's to reject.
|
|
1779
1931
|
*/
|
|
@@ -4131,6 +4283,10 @@ async function run(ctx, config, io) {
|
|
|
4131
4283
|
if (sessions === void 0) return;
|
|
4132
4284
|
const cwd = process.cwd();
|
|
4133
4285
|
const theme = createTheme(io.console.isTty, process.env);
|
|
4286
|
+
io.console.onBackground((payload) => {
|
|
4287
|
+
const light = backgroundIsLight(payload);
|
|
4288
|
+
if (light !== void 0) theme.setLight(light);
|
|
4289
|
+
});
|
|
4134
4290
|
const preset = await installPackagedPreset();
|
|
4135
4291
|
if (preset.installed) io.console.write(theme.dim(`installed preset into ${preset.path}`));
|
|
4136
4292
|
const composed = await compose(ctx, config, cwd);
|
package/lib/types/console.d.ts
CHANGED
|
@@ -42,6 +42,11 @@ export declare class TerminalConsole {
|
|
|
42
42
|
private keyHandler;
|
|
43
43
|
/** Keys decoded before any handler registered — type-ahead is never dropped. */
|
|
44
44
|
private readonly earlyKeys;
|
|
45
|
+
/** Whether the terminal window has focus; undefined until it reports. */
|
|
46
|
+
private focused;
|
|
47
|
+
/** The terminal's OSC 11 background answer, kept for late listeners. */
|
|
48
|
+
private background;
|
|
49
|
+
private backgroundHandler;
|
|
45
50
|
private escapeTimer;
|
|
46
51
|
private ended;
|
|
47
52
|
/** The viewport this surface owns on a terminal; absent off one. */
|
|
@@ -212,6 +217,14 @@ export declare class TerminalConsole {
|
|
|
212
217
|
clearRegion(): void;
|
|
213
218
|
/** Ring the terminal bell; a pipe gets nothing to beep with. */
|
|
214
219
|
bell(): void;
|
|
220
|
+
/**
|
|
221
|
+
* Register for the terminal's background-color answer (OSC 11).
|
|
222
|
+
*
|
|
223
|
+
* The answer often lands before anyone is ready to hear it — the query goes
|
|
224
|
+
* out with the first frame — so a buffered reply is delivered immediately.
|
|
225
|
+
* @param handler - receives the raw payload, e.g. `rgb:1e1e/1e1e/2e2e`.
|
|
226
|
+
*/
|
|
227
|
+
onBackground(handler: (payload: string) => void): void;
|
|
215
228
|
/**
|
|
216
229
|
* Set the terminal window title.
|
|
217
230
|
* @param title - the title text; control bytes are the terminal's to reject.
|
package/lib/types/keys.d.ts
CHANGED
|
@@ -81,6 +81,13 @@ export type Key = {
|
|
|
81
81
|
kind: 'mouse-up';
|
|
82
82
|
row: number;
|
|
83
83
|
column: number;
|
|
84
|
+
} | {
|
|
85
|
+
kind: 'focus';
|
|
86
|
+
focused: boolean;
|
|
87
|
+
} | {
|
|
88
|
+
kind: 'osc-reply';
|
|
89
|
+
code: number;
|
|
90
|
+
payload: string;
|
|
84
91
|
};
|
|
85
92
|
/** Decodes terminal bytes into keys, holding partial sequences between reads. */
|
|
86
93
|
export declare class KeyDecoder {
|
|
@@ -111,6 +118,14 @@ export declare class KeyDecoder {
|
|
|
111
118
|
* @returns the keys produced, or undefined when more bytes are needed.
|
|
112
119
|
*/
|
|
113
120
|
private take;
|
|
121
|
+
/**
|
|
122
|
+
* Map one kitty-protocol report onto the same keys the legacy bytes make.
|
|
123
|
+
* @param code - the key's Unicode code point.
|
|
124
|
+
* @param mods - the encoded modifiers, offset by one.
|
|
125
|
+
* @param event - press (1), repeat (2), or release (3).
|
|
126
|
+
* @returns the keys produced; unknown chords are swallowed, never typed.
|
|
127
|
+
*/
|
|
128
|
+
private kittyKey;
|
|
114
129
|
/**
|
|
115
130
|
* Collect bracketed-paste content up to its end marker.
|
|
116
131
|
* @returns the paste key once complete, otherwise undefined.
|
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
|
*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codsh-bundle",
|
|
3
3
|
"description": "The codsh runtime: the interactive TTY surface and code-cli agent preset, installed into a dsh profile. Users install codsh-cli (the launcher) — this package is what it registers.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "lib/index.js",
|