@mudah-cli/terminal 0.1.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/dist/ansi.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { OscWriter } from './osc.js';
2
+ /** ANSI cursor/screen helpers shared by the animation and UI layers. */
3
+ export declare const ansi: {
4
+ /** Erase the current line. */
5
+ clearLine(stream: OscWriter): void;
6
+ /** Erase from the cursor to the end of the line. */
7
+ eraseEol(stream: OscWriter): void;
8
+ /** Move the cursor up `n` lines (clamped at the top). */
9
+ moveUp(stream: OscWriter, n: number): void;
10
+ /** Move to the start of the line. */
11
+ toLineStart(stream: OscWriter): void;
12
+ /** Clear the whole screen and home the cursor. */
13
+ clearScreen(stream: OscWriter): void;
14
+ /** Erase from the cursor to the end of the screen. */
15
+ eraseToEnd(stream: OscWriter): void;
16
+ hideCursor(stream: OscWriter): void;
17
+ showCursor(stream: OscWriter): void;
18
+ /** Wrap `text` in dim styling. */
19
+ dim(text: string): string;
20
+ bold(text: string): string;
21
+ underline(text: string): string;
22
+ };
package/dist/ansi.js ADDED
@@ -0,0 +1,45 @@
1
+ /** ANSI cursor/screen helpers shared by the animation and UI layers. */
2
+ export const ansi = {
3
+ /** Erase the current line. */
4
+ clearLine(stream) {
5
+ stream.write('\x1b[2K');
6
+ },
7
+ /** Erase from the cursor to the end of the line. */
8
+ eraseEol(stream) {
9
+ stream.write('\x1b[K');
10
+ },
11
+ /** Move the cursor up `n` lines (clamped at the top). */
12
+ moveUp(stream, n) {
13
+ if (n > 0)
14
+ stream.write(`\x1b[${n}A`);
15
+ },
16
+ /** Move to the start of the line. */
17
+ toLineStart(stream) {
18
+ stream.write('\x1b[0G');
19
+ },
20
+ /** Clear the whole screen and home the cursor. */
21
+ clearScreen(stream) {
22
+ stream.write('\x1b[2J\x1b[H');
23
+ },
24
+ /** Erase from the cursor to the end of the screen. */
25
+ eraseToEnd(stream) {
26
+ stream.write('\x1b[J');
27
+ },
28
+ hideCursor(stream) {
29
+ stream.write('\x1b[?25l');
30
+ },
31
+ showCursor(stream) {
32
+ stream.write('\x1b[?25h');
33
+ },
34
+ /** Wrap `text` in dim styling. */
35
+ dim(text) {
36
+ return `\x1b[2m${text}\x1b[22m`;
37
+ },
38
+ bold(text) {
39
+ return `\x1b[1m${text}\x1b[22m`;
40
+ },
41
+ underline(text) {
42
+ return `\x1b[4m${text}\x1b[24m`;
43
+ },
44
+ };
45
+ //# sourceMappingURL=ansi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ansi.js","sourceRoot":"","sources":["../src/ansi.ts"],"names":[],"mappings":"AAEA,wEAAwE;AACxE,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,8BAA8B;IAC9B,SAAS,CAAC,MAAiB;QACzB,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1B,CAAC;IACD,oDAAoD;IACpD,QAAQ,CAAC,MAAiB;QACxB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC;IACD,yDAAyD;IACzD,MAAM,CAAC,MAAiB,EAAE,CAAS;QACjC,IAAI,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACxC,CAAC;IACD,qCAAqC;IACrC,WAAW,CAAC,MAAiB;QAC3B,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1B,CAAC;IACD,kDAAkD;IAClD,WAAW,CAAC,MAAiB;QAC3B,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAChC,CAAC;IACD,sDAAsD;IACtD,UAAU,CAAC,MAAiB;QAC1B,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC;IACD,UAAU,CAAC,MAAiB;QAC1B,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5B,CAAC;IACD,UAAU,CAAC,MAAiB;QAC1B,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5B,CAAC;IACD,kCAAkC;IAClC,GAAG,CAAC,IAAY;QACd,OAAO,UAAU,IAAI,UAAU,CAAC;IAClC,CAAC;IACD,IAAI,CAAC,IAAY;QACf,OAAO,UAAU,IAAI,UAAU,CAAC;IAClC,CAAC;IACD,SAAS,CAAC,IAAY;QACpB,OAAO,UAAU,IAAI,UAAU,CAAC;IAClC,CAAC;CACF,CAAC"}
@@ -0,0 +1,49 @@
1
+ export type ColorLevel = 0 | 1 | 8 | 24;
2
+ export interface TerminalCapabilities {
3
+ /** Output is attached to a terminal. */
4
+ readonly isTty: boolean;
5
+ /** Color output is enabled. */
6
+ readonly color: boolean;
7
+ /** Effective color level. */
8
+ readonly colorLevel: ColorLevel;
9
+ /** 24-bit truecolor support. */
10
+ readonly trueColor: boolean;
11
+ /** Use unicode box-drawing characters (false falls back to ASCII). */
12
+ readonly unicode: boolean;
13
+ /** Animations/motion are allowed. */
14
+ readonly animations: boolean;
15
+ /** The user (or terminal) requested reduced motion. */
16
+ readonly reducedMotion: boolean;
17
+ /** OSC 9 desktop notifications (Ghostty, WezTerm). */
18
+ readonly osc9: boolean;
19
+ /** OSC 133 semantic prompt markers (Kitty, WezTerm, iTerm2, VS Code, foot). */
20
+ readonly osc133: boolean;
21
+ /** Cursor show/hide is safe. */
22
+ readonly cursorControl: boolean;
23
+ /** Best-guess terminal theme. */
24
+ readonly theme: 'dark' | 'light' | 'unknown';
25
+ readonly width: number;
26
+ readonly height: number;
27
+ /** Detected terminal brand. */
28
+ readonly brand: TerminalBrand;
29
+ }
30
+ export type TerminalBrand = 'ghostty' | 'kitty' | 'wezterm' | 'alacritty' | 'iterm' | 'apple-terminal' | 'vscode' | 'foot' | 'xterm' | 'unknown';
31
+ export interface CapabilityOverrides {
32
+ color?: boolean;
33
+ unicode?: boolean;
34
+ reducedMotion?: boolean;
35
+ theme?: 'dark' | 'light';
36
+ }
37
+ export interface DetectCapabilitiesOptions {
38
+ isTty?: boolean;
39
+ width?: number;
40
+ height?: number;
41
+ /** Environment map (defaults to `process.env`). */
42
+ env?: NodeJS.ProcessEnv;
43
+ overrides?: CapabilityOverrides;
44
+ }
45
+ /**
46
+ * Detect terminal capabilities from the environment. Pure function of its
47
+ * options — pass `env` in tests. Explicit overrides win over detection.
48
+ */
49
+ export declare function detectCapabilities(options?: DetectCapabilitiesOptions): TerminalCapabilities;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Detect terminal capabilities from the environment. Pure function of its
3
+ * options — pass `env` in tests. Explicit overrides win over detection.
4
+ */
5
+ export function detectCapabilities(options = {}) {
6
+ const env = options.env ?? process.env;
7
+ const isTty = options.isTty ?? process.stdout.isTTY === true;
8
+ const overrides = options.overrides ?? {};
9
+ const brand = detectBrand(env);
10
+ const colorLevel = detectColorLevel(isTty, env);
11
+ const reducedMotion = overrides.reducedMotion ?? (env['MUDAH_REDUCED_MOTION'] === '1' || env['NO_ANIMATION'] === '1');
12
+ return {
13
+ isTty,
14
+ color: overrides.color ?? colorLevel > 0,
15
+ colorLevel,
16
+ trueColor: colorLevel === 24,
17
+ unicode: overrides.unicode ?? env['TERM'] !== 'dumb',
18
+ animations: isTty && !reducedMotion && env['CI'] !== 'true' && env['CI'] !== '1',
19
+ reducedMotion,
20
+ osc9: brand === 'ghostty' || brand === 'wezterm',
21
+ osc133: brand === 'kitty' || brand === 'wezterm' || brand === 'iterm' || brand === 'vscode' || brand === 'foot',
22
+ cursorControl: isTty,
23
+ theme: overrides.theme ?? 'unknown',
24
+ width: options.width ?? process.stdout.columns ?? 80,
25
+ height: options.height ?? process.stdout.rows ?? 24,
26
+ brand,
27
+ };
28
+ }
29
+ function detectBrand(env) {
30
+ const program = env['TERM_PROGRAM'];
31
+ const term = env['TERM'];
32
+ if (program === 'ghostty' || term === 'xterm-ghostty')
33
+ return 'ghostty';
34
+ if (program === 'WezTerm' || term === 'xterm-wezterm')
35
+ return 'wezterm';
36
+ if (term === 'xterm-kitty')
37
+ return 'kitty';
38
+ if (program === 'iTerm.app')
39
+ return 'iterm';
40
+ if (program === 'Apple_Terminal')
41
+ return 'apple-terminal';
42
+ if (program === 'vscode')
43
+ return 'vscode';
44
+ if (program === 'foot' || term === 'xterm-foot')
45
+ return 'foot';
46
+ if (term === 'alacritty' || env['ALACRITTY_INSTANCE_ID'] !== undefined)
47
+ return 'alacritty';
48
+ if (term?.startsWith('xterm'))
49
+ return 'xterm';
50
+ return 'unknown';
51
+ }
52
+ function detectColorLevel(isTty, env) {
53
+ if (env['NO_COLOR'] !== undefined)
54
+ return 0;
55
+ const forced = env['FORCE_COLOR'];
56
+ if (forced !== undefined) {
57
+ if (forced === '' || forced === 'true' || forced === '1')
58
+ return 8;
59
+ if (forced === '2')
60
+ return 8;
61
+ if (forced === '3')
62
+ return 24;
63
+ if (forced === '0' || forced === 'false')
64
+ return 0;
65
+ }
66
+ if (env['CLICOLOR_FORCE'] === '1')
67
+ return 8;
68
+ if (!isTty) {
69
+ // CI systems are TTY-less but render color; trust COLORTERM when present.
70
+ if (env['CI'] === 'true' || env['CI'] === '1') {
71
+ return env['COLORTERM'] === 'truecolor' || env['COLORTERM'] === 'yes' ? 24 : 8;
72
+ }
73
+ return 0;
74
+ }
75
+ if (env['COLORTERM'] === 'truecolor' || env['COLORTERM'] === 'yes')
76
+ return 24;
77
+ if (env['TERM']?.includes('256color'))
78
+ return 8;
79
+ if (env['TERM']?.includes('color'))
80
+ return 8;
81
+ return 1;
82
+ }
83
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { ansi } from './ansi.js';
2
+ export { detectCapabilities, type CapabilityOverrides, type ColorLevel, type DetectCapabilitiesOptions, type TerminalBrand, type TerminalCapabilities, } from './capabilities.js';
3
+ export { guardedOsc, osc, type OscWriter } from './osc.js';
4
+ export { enterRawMode, KeyParser, parseKeys, type KeyEvent, type KeyName } from './keys.js';
5
+ export { Ticker, type TickerOptions } from './ticker.js';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { ansi } from './ansi.js';
2
+ export { detectCapabilities, } from './capabilities.js';
3
+ export { guardedOsc, osc } from './osc.js';
4
+ export { enterRawMode, KeyParser, parseKeys } from './keys.js';
5
+ export { Ticker } from './ticker.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,kBAAkB,GAMnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,GAAG,EAAkB,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAA+B,MAAM,WAAW,CAAC;AAC5F,OAAO,EAAE,MAAM,EAAsB,MAAM,aAAa,CAAC"}
package/dist/keys.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { ReadStream } from 'node:tty';
2
+ export type KeyName = 'up' | 'down' | 'left' | 'right' | 'home' | 'end' | 'page-up' | 'page-down' | 'enter' | 'escape' | 'backspace' | 'delete' | 'tab' | 'shift-tab' | 'space' | `ctrl+${string}` | `alt+${string}` | string;
3
+ export interface KeyEvent {
4
+ name: KeyName;
5
+ /** Raw character for printable keys. */
6
+ ch?: string;
7
+ }
8
+ /**
9
+ * Parse a raw input buffer into key events. Pure and synchronous.
10
+ * Handles CSI sequences (arrows, home/end, paging, delete), bare escape,
11
+ * alt+char, control characters, and printable text.
12
+ */
13
+ export declare function parseKeys(buffer: string): KeyEvent[];
14
+ /**
15
+ * Incremental key parser: feed raw chunks as they arrive, receive complete
16
+ * key events. Partial escape sequences are held until the next chunk.
17
+ */
18
+ export declare class KeyParser {
19
+ private pending;
20
+ feed(chunk: string): KeyEvent[];
21
+ /** Index up to which the buffer can be split without cutting a sequence. */
22
+ private safeSplitIndex;
23
+ }
24
+ /** Enter raw mode on a TTY and return a cleanup function. */
25
+ export declare function enterRawMode(input: ReadStream): () => void;
package/dist/keys.js ADDED
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Parse a raw input buffer into key events. Pure and synchronous.
3
+ * Handles CSI sequences (arrows, home/end, paging, delete), bare escape,
4
+ * alt+char, control characters, and printable text.
5
+ */
6
+ export function parseKeys(buffer) {
7
+ const events = [];
8
+ let i = 0;
9
+ while (i < buffer.length) {
10
+ const char = buffer[i];
11
+ if (char === '\x1b') {
12
+ const rest = buffer.slice(i + 1);
13
+ if (rest.startsWith('[') || rest.startsWith('O')) {
14
+ const matcher = /^\[([0-9;?]*)([\x40-\x7e])/.exec(rest) ?? /^O([\x40-\x7e])/.exec(rest);
15
+ if (matcher) {
16
+ const event = escapeSequence(matcher[1] ?? '', (matcher[2] ?? matcher[1]));
17
+ if (event)
18
+ events.push(event);
19
+ i += 1 + matcher[0].length;
20
+ continue;
21
+ }
22
+ // Incomplete sequence (should not happen for complete buffers).
23
+ break;
24
+ }
25
+ if (rest.startsWith(' ')) {
26
+ const ch = rest.slice(1, 2);
27
+ events.push({ name: `alt+${ch}`, ch });
28
+ i += 2;
29
+ continue;
30
+ }
31
+ events.push({ name: 'escape' });
32
+ i += 1;
33
+ continue;
34
+ }
35
+ if (char === '\r' || char === '\n') {
36
+ events.push({ name: 'enter', ch: '\r' });
37
+ i += 1;
38
+ continue;
39
+ }
40
+ if (char === '\x7f' || char === '\x08') {
41
+ events.push({ name: 'backspace' });
42
+ i += 1;
43
+ continue;
44
+ }
45
+ if (char === '\t') {
46
+ events.push({ name: 'tab' });
47
+ i += 1;
48
+ continue;
49
+ }
50
+ if (char >= '\x01' && char <= '\x1a') {
51
+ // ctrl+a .. ctrl+z
52
+ const letter = String.fromCharCode(char.charCodeAt(0) + 96);
53
+ events.push({ name: `ctrl+${letter}` });
54
+ i += 1;
55
+ continue;
56
+ }
57
+ if (char < '\x20') {
58
+ i += 1;
59
+ continue;
60
+ }
61
+ if (char === ' ') {
62
+ events.push({ name: 'space', ch: ' ' });
63
+ i += 1;
64
+ continue;
65
+ }
66
+ events.push({ name: char, ch: char });
67
+ i += 1;
68
+ }
69
+ return events;
70
+ }
71
+ function escapeSequence(code, letter) {
72
+ const param = code.split(';')[0] ?? '';
73
+ switch (letter) {
74
+ case 'A':
75
+ return { name: 'up' };
76
+ case 'B':
77
+ return { name: 'down' };
78
+ case 'C':
79
+ return { name: 'right' };
80
+ case 'D':
81
+ return { name: 'left' };
82
+ case 'H':
83
+ return { name: 'home' };
84
+ case 'F':
85
+ return { name: 'end' };
86
+ case 'Z':
87
+ return { name: 'shift-tab' };
88
+ case '~':
89
+ switch (param) {
90
+ case '1':
91
+ return { name: 'home' };
92
+ case '2':
93
+ case '3':
94
+ return { name: 'delete' };
95
+ case '4':
96
+ return { name: 'end' };
97
+ case '5':
98
+ return { name: 'page-up' };
99
+ case '6':
100
+ return { name: 'page-down' };
101
+ default:
102
+ return null;
103
+ }
104
+ default:
105
+ return null;
106
+ }
107
+ }
108
+ /**
109
+ * Incremental key parser: feed raw chunks as they arrive, receive complete
110
+ * key events. Partial escape sequences are held until the next chunk.
111
+ */
112
+ export class KeyParser {
113
+ pending = '';
114
+ feed(chunk) {
115
+ this.pending += chunk;
116
+ const safe = this.safeSplitIndex(this.pending);
117
+ if (safe < 0)
118
+ return [];
119
+ const ready = this.pending.slice(0, safe);
120
+ this.pending = this.pending.slice(safe);
121
+ return parseKeys(ready);
122
+ }
123
+ /** Index up to which the buffer can be split without cutting a sequence. */
124
+ safeSplitIndex(text) {
125
+ const esc = text.lastIndexOf('\x1b');
126
+ if (esc < 0)
127
+ return text.length;
128
+ const rest = text.slice(esc + 1);
129
+ if (rest.length === 0)
130
+ return -1; // bare escape: wait to see if a sequence follows
131
+ if (rest[0] === '[') {
132
+ // Complete only when a final byte (0x40–0x7e) terminates the sequence.
133
+ return /^\[[0-9;?]*[\x40-\x7e]$/.test(rest) ? text.length : -1;
134
+ }
135
+ if (rest[0] === 'O') {
136
+ return /^O[\x40-\x7e]$/.test(rest) ? text.length : -1;
137
+ }
138
+ if (rest[0] === ' ') {
139
+ return rest.length >= 2 ? text.length : -1;
140
+ }
141
+ // Bare escape followed by other data: the escape itself is complete.
142
+ return esc + 1;
143
+ }
144
+ }
145
+ /** Enter raw mode on a TTY and return a cleanup function. */
146
+ export function enterRawMode(input) {
147
+ if (!input.isTTY)
148
+ return () => { };
149
+ input.setRawMode(true);
150
+ input.resume();
151
+ return () => {
152
+ if (input.isTTY)
153
+ input.setRawMode(false);
154
+ input.pause();
155
+ };
156
+ }
157
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
package/dist/osc.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ export interface OscWriter {
2
+ write(data: string): unknown;
3
+ }
4
+ /**
5
+ * OSC (operating system command) emitters for modern terminal features:
6
+ * titles, desktop notifications, hyperlinks, and semantic prompt markers.
7
+ * All writers accept any stream-like object so they can be pointed at
8
+ * buffers in tests.
9
+ */
10
+ export declare const osc: {
11
+ /** Set the terminal window/tab title. */
12
+ title(stream: OscWriter, title: string): void;
13
+ /**
14
+ * Desktop notification. Uses OSC 9 with the unit separator (Ghostty) and
15
+ * the legacy xterm OSC 777 form so at least one lands.
16
+ */
17
+ notify(stream: OscWriter, title: string, message: string): void;
18
+ /** OSC 8 hyperlink: `text` renders as a clickable link to `uri`. */
19
+ hyperlink(stream: OscWriter, uri: string, text: string): void;
20
+ /** Mark the start of a prompt line (OSC 133 A). */
21
+ promptStart(stream: OscWriter): void;
22
+ /** Mark the end of a prompt / start of the command (OSC 133 B). */
23
+ promptEnd(stream: OscWriter): void;
24
+ /** Mark command output end, with exit status (OSC 133 D;status). */
25
+ commandEnd(stream: OscWriter, status?: number): void;
26
+ };
27
+ /**
28
+ * Wrap a stream with OSC capability guards: emitters become no-ops when the
29
+ * capability is unavailable.
30
+ */
31
+ export declare function guardedOsc(stream: OscWriter, caps: {
32
+ osc9: boolean;
33
+ osc133: boolean;
34
+ }): {
35
+ title: (title: string) => void;
36
+ notify: (title: string, message: string) => void;
37
+ hyperlink: (uri: string, text: string) => void;
38
+ promptStart: () => void;
39
+ promptEnd: () => void;
40
+ commandEnd: (status?: number) => void;
41
+ };
package/dist/osc.js ADDED
@@ -0,0 +1,69 @@
1
+ const BEL = '\x07';
2
+ const SEP = '\x1f'; // OSC 9 unit separator
3
+ function oscSequence(code, payload) {
4
+ return `\x1b]${code};${payload}${BEL}`;
5
+ }
6
+ /**
7
+ * OSC (operating system command) emitters for modern terminal features:
8
+ * titles, desktop notifications, hyperlinks, and semantic prompt markers.
9
+ * All writers accept any stream-like object so they can be pointed at
10
+ * buffers in tests.
11
+ */
12
+ export const osc = {
13
+ /** Set the terminal window/tab title. */
14
+ title(stream, title) {
15
+ stream.write(oscSequence(0, title));
16
+ },
17
+ /**
18
+ * Desktop notification. Uses OSC 9 with the unit separator (Ghostty) and
19
+ * the legacy xterm OSC 777 form so at least one lands.
20
+ */
21
+ notify(stream, title, message) {
22
+ stream.write(oscSequence(9, `${title}${SEP}${message}`));
23
+ stream.write(oscSequence(777, `notify;${title};${message}`));
24
+ },
25
+ /** OSC 8 hyperlink: `text` renders as a clickable link to `uri`. */
26
+ hyperlink(stream, uri, text) {
27
+ const params = uri === '' ? '' : `8;;${uri}`;
28
+ stream.write(`\x1b]${params}\x1b\\${text}\x1b]8;;\x1b\\`);
29
+ },
30
+ /** Mark the start of a prompt line (OSC 133 A). */
31
+ promptStart(stream) {
32
+ stream.write(oscSequence(133, 'A'));
33
+ },
34
+ /** Mark the end of a prompt / start of the command (OSC 133 B). */
35
+ promptEnd(stream) {
36
+ stream.write(oscSequence(133, 'B'));
37
+ },
38
+ /** Mark command output end, with exit status (OSC 133 D;status). */
39
+ commandEnd(stream, status = 0) {
40
+ stream.write(oscSequence(133, `D;${status}`));
41
+ },
42
+ };
43
+ /**
44
+ * Wrap a stream with OSC capability guards: emitters become no-ops when the
45
+ * capability is unavailable.
46
+ */
47
+ export function guardedOsc(stream, caps) {
48
+ return {
49
+ title: (title) => osc.title(stream, title),
50
+ notify: (title, message) => {
51
+ if (caps.osc9)
52
+ osc.notify(stream, title, message);
53
+ },
54
+ hyperlink: (uri, text) => osc.hyperlink(stream, uri, text),
55
+ promptStart: () => {
56
+ if (caps.osc133)
57
+ osc.promptStart(stream);
58
+ },
59
+ promptEnd: () => {
60
+ if (caps.osc133)
61
+ osc.promptEnd(stream);
62
+ },
63
+ commandEnd: (status = 0) => {
64
+ if (caps.osc133)
65
+ osc.commandEnd(stream, status);
66
+ },
67
+ };
68
+ }
69
+ //# sourceMappingURL=osc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"osc.js","sourceRoot":"","sources":["../src/osc.ts"],"names":[],"mappings":"AAAA,MAAM,GAAG,GAAG,MAAM,CAAC;AACnB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,uBAAuB;AAE3C,SAAS,WAAW,CAAC,IAAY,EAAE,OAAe;IAChD,OAAO,QAAQ,IAAI,IAAI,OAAO,GAAG,GAAG,EAAE,CAAC;AACzC,CAAC;AAMD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,yCAAyC;IACzC,KAAK,CAAC,MAAiB,EAAE,KAAa;QACpC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IACtC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,MAAiB,EAAE,KAAa,EAAE,OAAe;QACtD,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,GAAG,KAAK,GAAG,GAAG,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;QACzD,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,oEAAoE;IACpE,SAAS,CAAC,MAAiB,EAAE,GAAW,EAAE,IAAY;QACpD,MAAM,MAAM,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,QAAQ,MAAM,SAAS,IAAI,gBAAgB,CAAC,CAAC;IAC5D,CAAC;IAED,mDAAmD;IACnD,WAAW,CAAC,MAAiB;QAC3B,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,mEAAmE;IACnE,SAAS,CAAC,MAAiB;QACzB,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,oEAAoE;IACpE,UAAU,CAAC,MAAiB,EAAE,MAAM,GAAW,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC;IAChD,CAAC;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAiB,EAAE,IAAwC;IACpF,OAAO;QACL,KAAK,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC;QAClD,MAAM,EAAE,CAAC,KAAa,EAAE,OAAe,EAAE,EAAE;YACzC,IAAI,IAAI,CAAC,IAAI;gBAAE,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QACpD,CAAC;QACD,SAAS,EAAE,CAAC,GAAW,EAAE,IAAY,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;QAC1E,WAAW,EAAE,GAAG,EAAE;YAChB,IAAI,IAAI,CAAC,MAAM;gBAAE,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAC3C,CAAC;QACD,SAAS,EAAE,GAAG,EAAE;YACd,IAAI,IAAI,CAAC,MAAM;gBAAE,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACzC,CAAC;QACD,UAAU,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE;YACzB,IAAI,IAAI,CAAC,MAAM;gBAAE,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAClD,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Frame ticker. Uses a 30fps interval (or `fps`) — deterministic enough for
3
+ * spinners and progress bars, and trivially testable with fake timers.
4
+ */
5
+ export interface TickerOptions {
6
+ fps?: number;
7
+ }
8
+ export declare class Ticker {
9
+ private readonly fps;
10
+ private readonly listeners;
11
+ private timer;
12
+ private lastTick;
13
+ private frame;
14
+ constructor(options?: TickerOptions);
15
+ onFrame(fn: (dtMs: number) => void): () => void;
16
+ get running(): boolean;
17
+ get frameIndex(): number;
18
+ start(): void;
19
+ stop(): void;
20
+ private tick;
21
+ }
package/dist/ticker.js ADDED
@@ -0,0 +1,42 @@
1
+ export class Ticker {
2
+ fps;
3
+ listeners = new Set();
4
+ timer = null;
5
+ lastTick = 0;
6
+ frame = 0;
7
+ constructor(options = {}) {
8
+ this.fps = options.fps ?? 30;
9
+ }
10
+ onFrame(fn) {
11
+ this.listeners.add(fn);
12
+ return () => this.listeners.delete(fn);
13
+ }
14
+ get running() {
15
+ return this.timer !== null;
16
+ }
17
+ get frameIndex() {
18
+ return this.frame;
19
+ }
20
+ start() {
21
+ if (this.timer)
22
+ return;
23
+ this.lastTick = performance.now();
24
+ this.timer = setInterval(() => this.tick(), Math.round(1000 / this.fps));
25
+ }
26
+ stop() {
27
+ if (!this.timer)
28
+ return;
29
+ clearInterval(this.timer);
30
+ this.timer = null;
31
+ }
32
+ tick() {
33
+ const now = performance.now();
34
+ const dt = now - this.lastTick;
35
+ this.lastTick = now;
36
+ this.frame += 1;
37
+ for (const listener of this.listeners) {
38
+ listener(dt);
39
+ }
40
+ }
41
+ }
42
+ //# sourceMappingURL=ticker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticker.js","sourceRoot":"","sources":["../src/ticker.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,MAAM;IACA,GAAG,CAAS;IACZ,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;IACvD,KAAK,GAA0C,IAAI,CAAC;IACpD,QAAQ,GAAG,CAAC,CAAC;IACb,KAAK,GAAG,CAAC,CAAC;IAElB,YAAY,OAAO,GAAkB,EAAE;QACrC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC;IAC/B,CAAC;IAED,OAAO,CAAC,EAA0B;QAChC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC;IAC7B,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAClC,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO;QACxB,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAEO,IAAI;QACV,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC9B,MAAM,EAAE,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QAChB,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@mudah-cli/terminal",
3
+ "version": "0.1.0",
4
+ "description": "Terminal capability detection, OSC feature emitters, and a modern input layer (kitty keyboard, bracketed paste).",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=26"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "publishConfig": {
21
+ "access": "public"
22
+ }
23
+ }