terminal-pilot 0.0.50 → 0.0.52
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/assets/jetbrains-mono-400-italic.ttf +0 -0
- package/assets/jetbrains-mono-400-normal.ttf +0 -0
- package/assets/jetbrains-mono-700-italic.ttf +0 -0
- package/assets/jetbrains-mono-700-normal.ttf +0 -0
- package/dist/cli.js +64 -32
- package/dist/cli.js.map +4 -4
- package/dist/commands/close-session.js +26 -0
- package/dist/commands/close-session.js.map +2 -2
- package/dist/commands/create-session.js +26 -0
- package/dist/commands/create-session.js.map +2 -2
- package/dist/commands/daemon-runtime.js.map +1 -1
- package/dist/commands/fill.js +26 -0
- package/dist/commands/fill.js.map +2 -2
- package/dist/commands/get-session.js +26 -0
- package/dist/commands/get-session.js.map +2 -2
- package/dist/commands/index.js +39 -6
- package/dist/commands/index.js.map +4 -4
- package/dist/commands/install.js +42 -3
- package/dist/commands/install.js.map +4 -4
- package/dist/commands/installer.js +16 -3
- package/dist/commands/installer.js.map +4 -4
- package/dist/commands/list-sessions.js +26 -0
- package/dist/commands/list-sessions.js.map +2 -2
- package/dist/commands/press-key.js +26 -0
- package/dist/commands/press-key.js.map +2 -2
- package/dist/commands/read-history.js +26 -0
- package/dist/commands/read-history.js.map +2 -2
- package/dist/commands/read-screen.js +26 -0
- package/dist/commands/read-screen.js.map +2 -2
- package/dist/commands/resize.js +26 -0
- package/dist/commands/resize.js.map +2 -2
- package/dist/commands/runtime.js.map +1 -1
- package/dist/commands/screenshot.js +26 -0
- package/dist/commands/screenshot.js.map +2 -2
- package/dist/commands/send-signal.js +26 -0
- package/dist/commands/send-signal.js.map +2 -2
- package/dist/commands/type.js +26 -0
- package/dist/commands/type.js.map +2 -2
- package/dist/commands/uninstall.js +42 -3
- package/dist/commands/uninstall.js.map +4 -4
- package/dist/commands/wait-for-exit.js +26 -0
- package/dist/commands/wait-for-exit.js.map +2 -2
- package/dist/commands/wait-for.js +26 -0
- package/dist/commands/wait-for.js.map +2 -2
- package/dist/composition.json +1 -1
- package/dist/testing/cli-repl.js +64 -32
- package/dist/testing/cli-repl.js.map +4 -4
- package/dist/testing/qa-cli.js +64 -32
- package/dist/testing/qa-cli.js.map +4 -4
- package/node_modules/toolcraft-design/README.md +30 -0
- package/node_modules/toolcraft-design/dist/dashboard/terminal-width.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/dashboard/terminal-width.js +18 -1
- package/node_modules/toolcraft-design/dist/explorer/actions.d.ts +3 -2
- package/node_modules/toolcraft-design/dist/explorer/actions.js +50 -4
- package/node_modules/toolcraft-design/dist/explorer/events.d.ts +7 -2
- package/node_modules/toolcraft-design/dist/explorer/filter.js +1 -1
- package/node_modules/toolcraft-design/dist/explorer/index.d.ts +2 -4
- package/node_modules/toolcraft-design/dist/explorer/index.js +1 -2
- package/node_modules/toolcraft-design/dist/explorer/jobs.d.ts +2 -2
- package/node_modules/toolcraft-design/dist/explorer/jobs.js +3 -5
- package/node_modules/toolcraft-design/dist/explorer/keymap.d.ts +13 -3
- package/node_modules/toolcraft-design/dist/explorer/keymap.js +89 -348
- package/node_modules/toolcraft-design/dist/explorer/layout.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/explorer/layout.js +14 -4
- package/node_modules/toolcraft-design/dist/explorer/reducer.js +116 -16
- package/node_modules/toolcraft-design/dist/explorer/render/detail.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/explorer/render/detail.js +45 -7
- package/node_modules/toolcraft-design/dist/explorer/render/footer.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/explorer/render/footer.js +18 -3
- package/node_modules/toolcraft-design/dist/explorer/render/header.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/explorer/render/header.js +6 -2
- package/node_modules/toolcraft-design/dist/explorer/render/index.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/explorer/render/index.js +2 -1
- package/node_modules/toolcraft-design/dist/explorer/render/list.d.ts +16 -2
- package/node_modules/toolcraft-design/dist/explorer/render/list.js +13 -34
- package/node_modules/toolcraft-design/dist/explorer/render/modal.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/explorer/render/modal.js +11 -11
- package/node_modules/toolcraft-design/dist/explorer/render/pane.d.ts +5 -2
- package/node_modules/toolcraft-design/dist/explorer/render/pane.js +9 -6
- package/node_modules/toolcraft-design/dist/explorer/render/test-fixtures.js +2 -3
- package/node_modules/toolcraft-design/dist/explorer/runtime.js +115 -73
- package/node_modules/toolcraft-design/dist/explorer/runtime.test-helpers.d.ts +23 -37
- package/node_modules/toolcraft-design/dist/explorer/runtime.test-helpers.js +32 -83
- package/node_modules/toolcraft-design/dist/explorer/state.d.ts +79 -8
- package/node_modules/toolcraft-design/dist/explorer/state.js +70 -18
- package/node_modules/toolcraft-design/dist/index.d.ts +8 -2
- package/node_modules/toolcraft-design/dist/index.js +5 -1
- package/node_modules/toolcraft-design/dist/screen/ansi-text.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/screen/ansi-text.js +35 -0
- package/node_modules/toolcraft-design/dist/screen/screen.d.ts +40 -0
- package/node_modules/toolcraft-design/dist/screen/screen.js +122 -0
- package/node_modules/toolcraft-design/dist/screen/style.d.ts +16 -0
- package/node_modules/toolcraft-design/dist/screen/style.js +55 -0
- package/node_modules/toolcraft-design/dist/terminal/driver.d.ts +34 -0
- package/node_modules/toolcraft-design/dist/terminal/driver.js +65 -0
- package/node_modules/toolcraft-design/dist/terminal/input.d.ts +25 -0
- package/node_modules/toolcraft-design/dist/terminal/input.js +181 -0
- package/node_modules/toolcraft-design/dist/terminal/output.d.ts +10 -0
- package/node_modules/toolcraft-design/dist/terminal/output.js +44 -0
- package/package.json +3 -2
- package/node_modules/toolcraft-design/dist/explorer/two-pane.d.ts +0 -102
- package/node_modules/toolcraft-design/dist/explorer/two-pane.js +0 -510
- package/node_modules/toolcraft-design/dist/run-two-pane-explorer.d.ts +0 -2
- package/node_modules/toolcraft-design/dist/run-two-pane-explorer.js +0 -1
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { type ResolvedBindings } from "./keymap.js";
|
|
2
2
|
export type Tone = "success" | "warning" | "error" | "info" | "muted";
|
|
3
|
+
export interface ConfirmPromptOptions {
|
|
4
|
+
title: string;
|
|
5
|
+
message: string;
|
|
6
|
+
confirmLabel?: string;
|
|
7
|
+
cancelLabel?: string;
|
|
8
|
+
destructive?: boolean;
|
|
9
|
+
}
|
|
3
10
|
export interface Row {
|
|
4
11
|
id: string;
|
|
5
12
|
title: string;
|
|
@@ -37,7 +44,9 @@ export interface Action<R> {
|
|
|
37
44
|
id: string;
|
|
38
45
|
label: string | (() => string);
|
|
39
46
|
key?: string | string[];
|
|
47
|
+
accelerator?: string;
|
|
40
48
|
predicate?: (ctx: ActionContext<R>) => boolean;
|
|
49
|
+
visible?: (row: Row) => boolean;
|
|
41
50
|
handler: (ctx: ActionContext<R>) => void | Promise<void>;
|
|
42
51
|
destructive?: boolean;
|
|
43
52
|
primary?: boolean;
|
|
@@ -57,8 +66,40 @@ export interface ActionContext<R> {
|
|
|
57
66
|
content: string;
|
|
58
67
|
}) => void;
|
|
59
68
|
toast: (msg: string, tone?: Tone) => void;
|
|
60
|
-
confirm: (prompt: string) => Promise<boolean>;
|
|
69
|
+
confirm: (prompt: string | ConfirmPromptOptions) => Promise<boolean>;
|
|
70
|
+
promptText: (options: {
|
|
71
|
+
title: string;
|
|
72
|
+
label: string;
|
|
73
|
+
initialValue?: string;
|
|
74
|
+
placeholder?: string;
|
|
75
|
+
}) => Promise<string | null>;
|
|
61
76
|
exit: (after?: () => void | Promise<void>) => void;
|
|
77
|
+
activePane?: PaneRuntimeState;
|
|
78
|
+
inactivePane?: PaneRuntimeState;
|
|
79
|
+
}
|
|
80
|
+
export interface ListPaneConfig {
|
|
81
|
+
id: string;
|
|
82
|
+
kind: "list";
|
|
83
|
+
title: string;
|
|
84
|
+
rows: () => Promise<Row[]>;
|
|
85
|
+
emptyHint?: string;
|
|
86
|
+
multiSelect?: boolean;
|
|
87
|
+
}
|
|
88
|
+
export interface DetailPaneConfig {
|
|
89
|
+
id: string;
|
|
90
|
+
kind: "detail";
|
|
91
|
+
title: string;
|
|
92
|
+
titleForRow?: (row: Row | undefined) => string;
|
|
93
|
+
render: (row: Row | undefined, ctx: DetailCtx) => string | Promise<string>;
|
|
94
|
+
}
|
|
95
|
+
export type PaneConfig = ListPaneConfig | DetailPaneConfig;
|
|
96
|
+
export interface PaneRuntimeState {
|
|
97
|
+
id: string;
|
|
98
|
+
title: string;
|
|
99
|
+
rows: Row[];
|
|
100
|
+
cursor: number;
|
|
101
|
+
selected: Set<string>;
|
|
102
|
+
filter: string;
|
|
62
103
|
}
|
|
63
104
|
export interface ReorderContext {
|
|
64
105
|
refresh: () => Promise<void>;
|
|
@@ -66,9 +107,10 @@ export interface ReorderContext {
|
|
|
66
107
|
}
|
|
67
108
|
export interface ExplorerConfig<R> {
|
|
68
109
|
title: string;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
110
|
+
panes?: PaneConfig[];
|
|
111
|
+
rows?: () => Promise<Row[]>;
|
|
112
|
+
refresh?: () => void | Promise<void>;
|
|
113
|
+
detail?: Detail<R>;
|
|
72
114
|
actions: Action<R>[];
|
|
73
115
|
reorder?: {
|
|
74
116
|
onReorder: (orderedIds: string[], ctx?: ReorderContext) => void | Promise<void>;
|
|
@@ -79,7 +121,14 @@ export interface ExplorerConfig<R> {
|
|
|
79
121
|
initialFilter?: string;
|
|
80
122
|
/** Synchronous first paint rows; still refreshed via `rows()` after start. */
|
|
81
123
|
initialRows?: Row[];
|
|
124
|
+
/** Disable terminal mouse reporting when native text selection is more useful than wheel input. */
|
|
125
|
+
mouse?: boolean;
|
|
82
126
|
}
|
|
127
|
+
export type NormalizedExplorerConfig<R> = ExplorerConfig<R> & {
|
|
128
|
+
rows: () => Promise<Row[]>;
|
|
129
|
+
detail: Detail<R>;
|
|
130
|
+
};
|
|
131
|
+
export declare function normalizeExplorerConfig<R>(config: ExplorerConfig<R>): NormalizedExplorerConfig<R>;
|
|
83
132
|
export declare const REGION_HEADER: number;
|
|
84
133
|
export declare const REGION_LIST: number;
|
|
85
134
|
export declare const REGION_DETAIL: number;
|
|
@@ -97,6 +146,7 @@ export interface ExplorerState {
|
|
|
97
146
|
title: string;
|
|
98
147
|
emptyHint: string;
|
|
99
148
|
rows: Row[];
|
|
149
|
+
rowsLoading: boolean;
|
|
100
150
|
filtered: number[];
|
|
101
151
|
matchPositions: Map<number, number[]>;
|
|
102
152
|
cursor: number;
|
|
@@ -106,6 +156,8 @@ export interface ExplorerState {
|
|
|
106
156
|
detail: {
|
|
107
157
|
rowId: string | null;
|
|
108
158
|
items: DetailItem[] | null;
|
|
159
|
+
allItems?: DetailItem[] | null;
|
|
160
|
+
filter?: string;
|
|
109
161
|
cursor: number;
|
|
110
162
|
scroll: number;
|
|
111
163
|
token: number;
|
|
@@ -117,9 +169,21 @@ export interface ExplorerState {
|
|
|
117
169
|
kind: "help";
|
|
118
170
|
} | {
|
|
119
171
|
kind: "confirm";
|
|
120
|
-
|
|
121
|
-
|
|
172
|
+
title: string;
|
|
173
|
+
message: string;
|
|
174
|
+
confirmLabel: string;
|
|
175
|
+
cancelLabel: string;
|
|
176
|
+
destructive: boolean;
|
|
122
177
|
resolver: (ok: boolean) => void;
|
|
178
|
+
action?: Action<unknown>;
|
|
179
|
+
rows?: Row[];
|
|
180
|
+
} | {
|
|
181
|
+
kind: "input";
|
|
182
|
+
title: string;
|
|
183
|
+
label: string;
|
|
184
|
+
value: string;
|
|
185
|
+
placeholder?: string;
|
|
186
|
+
resolver: (value: string | null) => void;
|
|
123
187
|
} | {
|
|
124
188
|
kind: "palette";
|
|
125
189
|
query: string;
|
|
@@ -140,13 +204,20 @@ export interface ExplorerState {
|
|
|
140
204
|
layout: ExplorerLayoutMode;
|
|
141
205
|
bindings: ResolvedBindings;
|
|
142
206
|
actionState: Map<string, ActionStateEntry>;
|
|
207
|
+
suspended: boolean;
|
|
208
|
+
paneDefinitions: Array<{
|
|
209
|
+
id: string;
|
|
210
|
+
title: string;
|
|
211
|
+
kind: "list" | "detail";
|
|
212
|
+
titleForRow?: (row: Row | undefined) => string;
|
|
213
|
+
}>;
|
|
143
214
|
}
|
|
144
215
|
export interface ActionStateEntry {
|
|
145
216
|
available: boolean;
|
|
146
217
|
label: string;
|
|
147
218
|
running?: boolean;
|
|
148
219
|
action?: Action<unknown>;
|
|
149
|
-
source?: "row" | "detail";
|
|
220
|
+
source?: "row" | "detail" | "both";
|
|
150
221
|
}
|
|
151
222
|
export declare function createInitialState<R>(config: ExplorerConfig<R>, size: ExplorerSize): ExplorerState;
|
|
152
|
-
export declare function resolveExplorerLayoutMode(cols: number): ExplorerLayoutMode;
|
|
223
|
+
export declare function resolveExplorerLayoutMode(cols: number, rows?: number): ExplorerLayoutMode;
|
|
@@ -1,29 +1,61 @@
|
|
|
1
1
|
import { resolveBindings } from "./keymap.js";
|
|
2
|
+
export function normalizeExplorerConfig(config) {
|
|
3
|
+
if (config.panes === undefined) {
|
|
4
|
+
if (config.rows === undefined || config.detail === undefined)
|
|
5
|
+
throw new Error("Explorer requires panes");
|
|
6
|
+
return config;
|
|
7
|
+
}
|
|
8
|
+
if (config.panes.length < 1 || config.panes.length > 3)
|
|
9
|
+
throw new Error("Explorer requires 1 to 3 panes");
|
|
10
|
+
const list = config.panes.find((pane) => pane.kind === "list");
|
|
11
|
+
if (list === undefined)
|
|
12
|
+
throw new Error("Explorer requires a list pane");
|
|
13
|
+
const companion = config.panes.find((pane) => pane !== list);
|
|
14
|
+
const detail = companion?.kind === "detail"
|
|
15
|
+
? {
|
|
16
|
+
items: async (row, ctx) => {
|
|
17
|
+
const content = await companion.render(row, ctx);
|
|
18
|
+
if (ctx.signal.aborted)
|
|
19
|
+
return [];
|
|
20
|
+
return [{ id: row.id, render: () => content, renderedContent: content }];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
: companion?.kind === "list"
|
|
24
|
+
? {
|
|
25
|
+
items: async () => (await companion.rows()).map((row) => ({ ...row, render: () => "" })),
|
|
26
|
+
actions: config.actions
|
|
27
|
+
}
|
|
28
|
+
: { items: async () => [] };
|
|
29
|
+
return {
|
|
30
|
+
...config,
|
|
31
|
+
rows: list.rows,
|
|
32
|
+
detail,
|
|
33
|
+
multiSelect: list.multiSelect ?? config.multiSelect,
|
|
34
|
+
emptyHint: list.emptyHint ?? config.emptyHint
|
|
35
|
+
};
|
|
36
|
+
}
|
|
2
37
|
export const REGION_HEADER = 1 << 0;
|
|
3
38
|
export const REGION_LIST = 1 << 1;
|
|
4
39
|
export const REGION_DETAIL = 1 << 2;
|
|
5
40
|
export const REGION_FOOTER = 1 << 3;
|
|
6
41
|
export const REGION_MODAL = 1 << 4;
|
|
7
42
|
export const REGION_TOAST = 1 << 5;
|
|
8
|
-
export const REGION_ALL = REGION_HEADER |
|
|
9
|
-
REGION_LIST |
|
|
10
|
-
REGION_DETAIL |
|
|
11
|
-
REGION_FOOTER |
|
|
12
|
-
REGION_MODAL |
|
|
13
|
-
REGION_TOAST;
|
|
43
|
+
export const REGION_ALL = REGION_HEADER | REGION_LIST | REGION_DETAIL | REGION_FOOTER | REGION_MODAL | REGION_TOAST;
|
|
14
44
|
export function createInitialState(config, size) {
|
|
45
|
+
const normalizedConfig = normalizeExplorerConfig(config);
|
|
15
46
|
const normalizedSize = {
|
|
16
47
|
cols: normalizeSize(size.cols),
|
|
17
48
|
rows: normalizeSize(size.rows)
|
|
18
49
|
};
|
|
19
|
-
const multiSelect =
|
|
20
|
-
const initialRows =
|
|
21
|
-
const initialFilter =
|
|
50
|
+
const multiSelect = normalizedConfig.multiSelect ?? true;
|
|
51
|
+
const initialRows = normalizedConfig.initialRows ?? [];
|
|
52
|
+
const initialFilter = normalizedConfig.initialFilter ?? "";
|
|
22
53
|
// Defer filtering to first rowsLoaded when empty; seed list immediately when provided.
|
|
23
54
|
return {
|
|
24
|
-
title:
|
|
25
|
-
emptyHint:
|
|
55
|
+
title: normalizedConfig.title,
|
|
56
|
+
emptyHint: normalizedConfig.emptyHint ?? "No detail",
|
|
26
57
|
rows: initialRows,
|
|
58
|
+
rowsLoading: true,
|
|
27
59
|
filtered: initialRows.map((_, index) => index),
|
|
28
60
|
matchPositions: new Map(),
|
|
29
61
|
cursor: 0,
|
|
@@ -33,6 +65,8 @@ export function createInitialState(config, size) {
|
|
|
33
65
|
detail: {
|
|
34
66
|
rowId: initialRows[0]?.id ?? null,
|
|
35
67
|
items: null,
|
|
68
|
+
allItems: null,
|
|
69
|
+
filter: "",
|
|
36
70
|
cursor: 0,
|
|
37
71
|
scroll: 0,
|
|
38
72
|
token: initialRows.length > 0 ? 1 : 0,
|
|
@@ -44,17 +78,35 @@ export function createInitialState(config, size) {
|
|
|
44
78
|
toast: null,
|
|
45
79
|
dirty: REGION_ALL,
|
|
46
80
|
size: normalizedSize,
|
|
47
|
-
layout: resolveExplorerLayoutMode(normalizedSize.cols),
|
|
48
|
-
bindings: resolveBindings(
|
|
49
|
-
actionState: createInitialActionState(
|
|
81
|
+
layout: resolveExplorerLayoutMode(normalizedSize.cols, normalizedSize.rows),
|
|
82
|
+
bindings: resolveBindings(normalizedConfig),
|
|
83
|
+
actionState: createInitialActionState(normalizedConfig),
|
|
84
|
+
suspended: false,
|
|
85
|
+
paneDefinitions: normalizedConfig.panes?.map(({ id, title, kind, ...pane }) => ({
|
|
86
|
+
id,
|
|
87
|
+
title,
|
|
88
|
+
kind,
|
|
89
|
+
...(kind === "detail" && "titleForRow" in pane ? { titleForRow: pane.titleForRow } : {})
|
|
90
|
+
})) ?? [
|
|
91
|
+
{ id: "list", title: normalizedConfig.title, kind: "list" },
|
|
92
|
+
{ id: "detail", title: "Preview", kind: "detail" }
|
|
93
|
+
]
|
|
50
94
|
};
|
|
51
95
|
}
|
|
52
96
|
function createInitialActionState(config) {
|
|
53
97
|
const state = new Map();
|
|
54
|
-
for (const [source, actions] of [
|
|
98
|
+
for (const [source, actions] of [
|
|
99
|
+
["row", config.actions],
|
|
100
|
+
["detail", config.detail?.actions ?? []]
|
|
101
|
+
]) {
|
|
55
102
|
for (const action of actions) {
|
|
56
103
|
if (state.has(action.id)) {
|
|
57
|
-
|
|
104
|
+
const isSharedListAction = config.panes?.some((pane) => pane.kind === "list" && pane !== config.panes?.[0]) === true;
|
|
105
|
+
if (!isSharedListAction)
|
|
106
|
+
throw new Error(`Duplicate explorer action id: ${action.id}`);
|
|
107
|
+
const existing = state.get(action.id);
|
|
108
|
+
state.set(action.id, { ...existing, source: "both" });
|
|
109
|
+
continue;
|
|
58
110
|
}
|
|
59
111
|
state.set(action.id, {
|
|
60
112
|
available: true,
|
|
@@ -66,8 +118,8 @@ function createInitialActionState(config) {
|
|
|
66
118
|
}
|
|
67
119
|
return state;
|
|
68
120
|
}
|
|
69
|
-
export function resolveExplorerLayoutMode(cols) {
|
|
70
|
-
if (cols <
|
|
121
|
+
export function resolveExplorerLayoutMode(cols, rows = 24) {
|
|
122
|
+
if (cols < 60 || rows < 8) {
|
|
71
123
|
return "too-narrow";
|
|
72
124
|
}
|
|
73
125
|
if (cols < 80) {
|
|
@@ -37,8 +37,8 @@ export * as dashboard from "./dashboard/index.js";
|
|
|
37
37
|
export { createDashboard, shouldUseInteractiveDashboard } from "./dashboard/index.js";
|
|
38
38
|
export type { Dashboard, DashboardOptions } from "./dashboard/index.js";
|
|
39
39
|
export * as explorer from "./explorer/index.js";
|
|
40
|
-
export { runExplorer,
|
|
41
|
-
export type { Row, DetailItem, Detail, DetailCtx, Action, ActionContext,
|
|
40
|
+
export { runExplorer, singleDetail, normalizeExplorerConfig } from "./explorer/index.js";
|
|
41
|
+
export type { Row, DetailItem, Detail, DetailCtx, Action, ActionContext, ConfirmPromptOptions, ExplorerConfig, PaneConfig, ListPaneConfig, DetailPaneConfig, PaneRuntimeState, ReorderContext, Tone } from "./explorer/index.js";
|
|
42
42
|
export * as prompts from "./prompts/index.js";
|
|
43
43
|
export { intro, introPlain, outro, note, select, multiselect, text as promptText, confirm, confirmOrCancel, password, spinner, withSpinner, isCancel, cancel, log, PromptCancelledError } from "./prompts/index.js";
|
|
44
44
|
export type { SelectOptions, MultiselectOptions, TextOptions, ConfirmOptions, PasswordOptions, SpinnerOptions, WithSpinnerOptions } from "./prompts/index.js";
|
|
@@ -54,3 +54,9 @@ export { configureTheme, getThemeConfig, resetTheme } from "./internal/theme-sta
|
|
|
54
54
|
export { stripAnsi } from "./internal/strip-ansi.js";
|
|
55
55
|
export { resolveOutputFormat, resetOutputFormatCache, withOutputFormat } from "./internal/output-format.js";
|
|
56
56
|
export type { OutputFormat } from "./internal/output-format.js";
|
|
57
|
+
export { createTerminalDriver } from "./terminal/driver.js";
|
|
58
|
+
export type { TerminalDriver, TerminalInputEvent, Size as TerminalSize } from "./terminal/driver.js";
|
|
59
|
+
export { Screen } from "./screen/screen.js";
|
|
60
|
+
export type { Cell as ScreenCell, ScreenSize, ScreenSurface } from "./screen/screen.js";
|
|
61
|
+
export { packStyle, styleToSgrDelta } from "./screen/style.js";
|
|
62
|
+
export type { PackedStyle } from "./screen/style.js";
|
|
@@ -29,7 +29,7 @@ export * as dashboard from "./dashboard/index.js";
|
|
|
29
29
|
export { createDashboard, shouldUseInteractiveDashboard } from "./dashboard/index.js";
|
|
30
30
|
// Explorer
|
|
31
31
|
export * as explorer from "./explorer/index.js";
|
|
32
|
-
export { runExplorer,
|
|
32
|
+
export { runExplorer, singleDetail, normalizeExplorerConfig } from "./explorer/index.js";
|
|
33
33
|
// Prompts
|
|
34
34
|
export * as prompts from "./prompts/index.js";
|
|
35
35
|
export { intro, introPlain, outro, note, select, multiselect, text as promptText, confirm, confirmOrCancel, password, spinner, withSpinner, isCancel, cancel, log, PromptCancelledError } from "./prompts/index.js";
|
|
@@ -44,3 +44,7 @@ export { getTheme, resolveThemeName, resetThemeCache } from "./internal/theme-de
|
|
|
44
44
|
export { configureTheme, getThemeConfig, resetTheme } from "./internal/theme-state.js";
|
|
45
45
|
export { stripAnsi } from "./internal/strip-ansi.js";
|
|
46
46
|
export { resolveOutputFormat, resetOutputFormatCache, withOutputFormat } from "./internal/output-format.js";
|
|
47
|
+
// Terminal and screen primitives
|
|
48
|
+
export { createTerminalDriver } from "./terminal/driver.js";
|
|
49
|
+
export { Screen } from "./screen/screen.js";
|
|
50
|
+
export { packStyle, styleToSgrDelta } from "./screen/style.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { parseAnsi } from "../dashboard/ansi.js";
|
|
2
|
+
import { graphemes, graphemeWidth } from "../dashboard/terminal-width.js";
|
|
3
|
+
import { packStyle } from "./style.js";
|
|
4
|
+
const COLORS = new Map([
|
|
5
|
+
["black", 1], ["red", 1], ["green", 2], ["yellow", 3], ["blue", 4],
|
|
6
|
+
["magenta", 5], ["cyan", 6], ["white", 7], ["gray", 8]
|
|
7
|
+
]);
|
|
8
|
+
export function ansiToCells(text) {
|
|
9
|
+
const output = [];
|
|
10
|
+
const lines = parseAnsi(text);
|
|
11
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
12
|
+
if (lineIndex > 0)
|
|
13
|
+
output.push(cell("\n", 1, 0));
|
|
14
|
+
for (const segment of lines[lineIndex].segments) {
|
|
15
|
+
const style = packed(segment.style);
|
|
16
|
+
for (const grapheme of graphemes(segment.text))
|
|
17
|
+
output.push(cell(grapheme, graphemeWidth(grapheme) > 1 ? 2 : 1, style));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return output;
|
|
21
|
+
}
|
|
22
|
+
function packed(style) {
|
|
23
|
+
return packStyle({
|
|
24
|
+
bold: style.bold,
|
|
25
|
+
dim: style.dim,
|
|
26
|
+
underline: style.underline,
|
|
27
|
+
inverse: style.inverse,
|
|
28
|
+
fg: colorNumber(style.fg),
|
|
29
|
+
bg: colorNumber(style.bg)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function colorNumber(value) { return value === undefined ? 0 : (COLORS.get(value) ?? 0); }
|
|
33
|
+
function cell(ch, width, style) {
|
|
34
|
+
return { ch, width, style, fg: (style >> 8) & 0xff, bg: (style >> 16) & 0xff };
|
|
35
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CellStyle, Rect } from "../dashboard/types.js";
|
|
2
|
+
import { type PackedStyle } from "./style.js";
|
|
3
|
+
export interface Cell {
|
|
4
|
+
ch: string;
|
|
5
|
+
width: 1 | 2;
|
|
6
|
+
style: PackedStyle;
|
|
7
|
+
fg: number;
|
|
8
|
+
bg: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ScreenSize {
|
|
11
|
+
cols: number;
|
|
12
|
+
rows: number;
|
|
13
|
+
}
|
|
14
|
+
export interface ScreenSurface {
|
|
15
|
+
readonly width: number;
|
|
16
|
+
readonly height: number;
|
|
17
|
+
put(x: number, y: number, text: string, style?: CellStyle | PackedStyle): void;
|
|
18
|
+
clearRect(rect: Rect, style?: CellStyle | PackedStyle): void;
|
|
19
|
+
}
|
|
20
|
+
export declare class Screen {
|
|
21
|
+
private cols;
|
|
22
|
+
private rows;
|
|
23
|
+
private front;
|
|
24
|
+
private back;
|
|
25
|
+
private outputStyle;
|
|
26
|
+
private readonly colors;
|
|
27
|
+
constructor(size?: ScreenSize, options?: {
|
|
28
|
+
colors?: boolean;
|
|
29
|
+
});
|
|
30
|
+
get width(): number;
|
|
31
|
+
get height(): number;
|
|
32
|
+
resize(size: ScreenSize): void;
|
|
33
|
+
cell(x: number, y: number, ch: string, style?: PackedStyle): void;
|
|
34
|
+
text(x: number, y: number, text: string, style?: PackedStyle): void;
|
|
35
|
+
put(x: number, y: number, text: string, style?: CellStyle | PackedStyle): void;
|
|
36
|
+
clearRect(rect: Rect, style?: CellStyle | PackedStyle): void;
|
|
37
|
+
flush(): string;
|
|
38
|
+
private index;
|
|
39
|
+
private inBounds;
|
|
40
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { graphemes, graphemeWidth } from "../dashboard/terminal-width.js";
|
|
2
|
+
import { background, foreground, styleToSgrDelta } from "./style.js";
|
|
3
|
+
const EMPTY = { ch: " ", width: 1, style: 0, fg: 0, bg: 0 };
|
|
4
|
+
export class Screen {
|
|
5
|
+
cols = 0;
|
|
6
|
+
rows = 0;
|
|
7
|
+
front = [];
|
|
8
|
+
back = [];
|
|
9
|
+
outputStyle = 0;
|
|
10
|
+
colors;
|
|
11
|
+
constructor(size = { cols: 0, rows: 0 }, options = {}) {
|
|
12
|
+
this.colors = options.colors ?? (process.env.NO_COLOR === undefined && process.env.TERM !== "dumb");
|
|
13
|
+
this.resize(size);
|
|
14
|
+
}
|
|
15
|
+
get width() { return this.cols; }
|
|
16
|
+
get height() { return this.rows; }
|
|
17
|
+
resize(size) {
|
|
18
|
+
this.cols = normalize(size.cols);
|
|
19
|
+
this.rows = normalize(size.rows);
|
|
20
|
+
this.front = cells(this.cols * this.rows);
|
|
21
|
+
this.back = cells(this.cols * this.rows);
|
|
22
|
+
}
|
|
23
|
+
cell(x, y, ch, style = 0) {
|
|
24
|
+
if (!this.inBounds(x, y) || ch.length === 0)
|
|
25
|
+
return;
|
|
26
|
+
const segment = graphemes(ch)[0];
|
|
27
|
+
if (segment === undefined)
|
|
28
|
+
return;
|
|
29
|
+
const measured = graphemeWidth(segment);
|
|
30
|
+
const width = measured > 1 ? 2 : 1;
|
|
31
|
+
this.back[this.index(x, y)] = makeCell(segment, width, style);
|
|
32
|
+
if (width === 2 && this.inBounds(x + 1, y))
|
|
33
|
+
this.back[this.index(x + 1, y)] = makeCell("", 1, style);
|
|
34
|
+
}
|
|
35
|
+
text(x, y, text, style = 0) {
|
|
36
|
+
let offset = 0;
|
|
37
|
+
for (const segment of graphemes(text)) {
|
|
38
|
+
const width = Math.max(0, graphemeWidth(segment));
|
|
39
|
+
if (width > 0)
|
|
40
|
+
this.cell(x + offset, y, segment, style);
|
|
41
|
+
offset += width;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
put(x, y, text, style = 0) {
|
|
45
|
+
this.text(x, y, text, typeof style === "number" ? style : packLegacyStyle(style));
|
|
46
|
+
}
|
|
47
|
+
clearRect(rect, style = 0) {
|
|
48
|
+
const packed = typeof style === "number" ? style : packLegacyStyle(style);
|
|
49
|
+
const startX = Math.max(0, rect.x);
|
|
50
|
+
const startY = Math.max(0, rect.y);
|
|
51
|
+
const endX = Math.min(this.cols, rect.x + Math.max(0, rect.width));
|
|
52
|
+
const endY = Math.min(this.rows, rect.y + Math.max(0, rect.height));
|
|
53
|
+
for (let y = startY; y < endY; y += 1)
|
|
54
|
+
for (let x = startX; x < endX; x += 1)
|
|
55
|
+
this.cell(x, y, " ", packed);
|
|
56
|
+
}
|
|
57
|
+
flush() {
|
|
58
|
+
const changed = new Set();
|
|
59
|
+
for (let index = 0; index < this.back.length; index += 1) {
|
|
60
|
+
if (!equal(this.front[index], this.back[index])) {
|
|
61
|
+
changed.add(index);
|
|
62
|
+
const old = this.front[index];
|
|
63
|
+
const next = this.back[index];
|
|
64
|
+
if ((old.width === 2 || next.width === 2) && (index % this.cols) + 1 < this.cols)
|
|
65
|
+
changed.add(index + 1);
|
|
66
|
+
if (old.ch === "" && index % this.cols > 0)
|
|
67
|
+
changed.add(index - 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
let output = "";
|
|
71
|
+
let currentStyle = this.outputStyle;
|
|
72
|
+
const ordered = [...changed].sort((left, right) => left - right);
|
|
73
|
+
let cursor = 0;
|
|
74
|
+
while (cursor < ordered.length) {
|
|
75
|
+
const start = ordered[cursor];
|
|
76
|
+
const y = Math.floor(start / this.cols);
|
|
77
|
+
let end = start;
|
|
78
|
+
while (cursor + 1 < ordered.length && ordered[cursor + 1] === end + 1 && Math.floor(ordered[cursor + 1] / this.cols) === y) {
|
|
79
|
+
cursor += 1;
|
|
80
|
+
end += 1;
|
|
81
|
+
}
|
|
82
|
+
output += `\u001b[${y + 1};${(start % this.cols) + 1}H`;
|
|
83
|
+
for (let index = start; index <= end; index += 1) {
|
|
84
|
+
const cell = this.back[index];
|
|
85
|
+
const delta = styleToSgrDelta(currentStyle, cell.style, this.colors);
|
|
86
|
+
output += delta;
|
|
87
|
+
currentStyle = cell.style;
|
|
88
|
+
output += cell.ch.length === 0 ? "" : cell.ch;
|
|
89
|
+
}
|
|
90
|
+
cursor += 1;
|
|
91
|
+
}
|
|
92
|
+
this.front = this.back;
|
|
93
|
+
this.back = cells(this.cols * this.rows);
|
|
94
|
+
this.outputStyle = currentStyle;
|
|
95
|
+
return output;
|
|
96
|
+
}
|
|
97
|
+
index(x, y) { return y * this.cols + x; }
|
|
98
|
+
inBounds(x, y) { return x >= 0 && y >= 0 && x < this.cols && y < this.rows; }
|
|
99
|
+
}
|
|
100
|
+
function makeCell(ch, width, style) {
|
|
101
|
+
return { ch, width, style, fg: foreground(style), bg: background(style) };
|
|
102
|
+
}
|
|
103
|
+
function cells(length) { return Array.from({ length }, () => ({ ...EMPTY })); }
|
|
104
|
+
function equal(left, right) { return left.ch === right.ch && left.width === right.width && left.style === right.style; }
|
|
105
|
+
function normalize(value) { return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0; }
|
|
106
|
+
function packLegacyStyle(style) {
|
|
107
|
+
return (style.bold ? 1 : 0)
|
|
108
|
+
| (style.dim ? 2 : 0)
|
|
109
|
+
| (style.underline ? 4 : 0)
|
|
110
|
+
| (style.inverse ? 8 : 0)
|
|
111
|
+
| (legacyColor(style.fg) << 8)
|
|
112
|
+
| (legacyColor(style.bg) << 16);
|
|
113
|
+
}
|
|
114
|
+
function legacyColor(value) {
|
|
115
|
+
if (value === undefined)
|
|
116
|
+
return 0;
|
|
117
|
+
const names = ["", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "gray"];
|
|
118
|
+
const exact = names.indexOf(value);
|
|
119
|
+
if (exact >= 0)
|
|
120
|
+
return exact;
|
|
121
|
+
return value.startsWith("#") ? 7 : 0;
|
|
122
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const STYLE_BOLD: number;
|
|
2
|
+
export declare const STYLE_DIM: number;
|
|
3
|
+
export declare const STYLE_UNDERLINE: number;
|
|
4
|
+
export declare const STYLE_INVERSE: number;
|
|
5
|
+
export type PackedStyle = number;
|
|
6
|
+
export declare function packStyle(style: {
|
|
7
|
+
bold?: boolean;
|
|
8
|
+
dim?: boolean;
|
|
9
|
+
underline?: boolean;
|
|
10
|
+
inverse?: boolean;
|
|
11
|
+
fg?: number;
|
|
12
|
+
bg?: number;
|
|
13
|
+
}): PackedStyle;
|
|
14
|
+
export declare function foreground(style: PackedStyle): number;
|
|
15
|
+
export declare function background(style: PackedStyle): number;
|
|
16
|
+
export declare function styleToSgrDelta(previous: PackedStyle, next: PackedStyle, colors?: boolean): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const STYLE_BOLD = 1 << 0;
|
|
2
|
+
export const STYLE_DIM = 1 << 1;
|
|
3
|
+
export const STYLE_UNDERLINE = 1 << 2;
|
|
4
|
+
export const STYLE_INVERSE = 1 << 3;
|
|
5
|
+
const FG_SHIFT = 8;
|
|
6
|
+
const BG_SHIFT = 16;
|
|
7
|
+
const COLOR_MASK = 0xff;
|
|
8
|
+
export function packStyle(style) {
|
|
9
|
+
let packed = 0;
|
|
10
|
+
if (style.bold)
|
|
11
|
+
packed |= STYLE_BOLD;
|
|
12
|
+
if (style.dim)
|
|
13
|
+
packed |= STYLE_DIM;
|
|
14
|
+
if (style.underline)
|
|
15
|
+
packed |= STYLE_UNDERLINE;
|
|
16
|
+
if (style.inverse)
|
|
17
|
+
packed |= STYLE_INVERSE;
|
|
18
|
+
packed |= ((style.fg ?? 0) & COLOR_MASK) << FG_SHIFT;
|
|
19
|
+
packed |= ((style.bg ?? 0) & COLOR_MASK) << BG_SHIFT;
|
|
20
|
+
return packed;
|
|
21
|
+
}
|
|
22
|
+
export function foreground(style) { return (style >> FG_SHIFT) & COLOR_MASK; }
|
|
23
|
+
export function background(style) { return (style >> BG_SHIFT) & COLOR_MASK; }
|
|
24
|
+
export function styleToSgrDelta(previous, next, colors = process.env.NO_COLOR === undefined && process.env.TERM !== "dumb") {
|
|
25
|
+
if (!colors)
|
|
26
|
+
return "";
|
|
27
|
+
if (previous === next)
|
|
28
|
+
return "";
|
|
29
|
+
const codes = [];
|
|
30
|
+
const previousIntensity = previous & (STYLE_BOLD | STYLE_DIM);
|
|
31
|
+
const nextIntensity = next & (STYLE_BOLD | STYLE_DIM);
|
|
32
|
+
if (previousIntensity !== nextIntensity) {
|
|
33
|
+
if ((previousIntensity & ~nextIntensity) !== 0)
|
|
34
|
+
codes.push(22);
|
|
35
|
+
if ((nextIntensity & STYLE_BOLD) !== 0 && ((previousIntensity & ~nextIntensity) !== 0 || (previousIntensity & STYLE_BOLD) === 0))
|
|
36
|
+
codes.push(1);
|
|
37
|
+
if ((nextIntensity & STYLE_DIM) !== 0 && ((previousIntensity & ~nextIntensity) !== 0 || (previousIntensity & STYLE_DIM) === 0))
|
|
38
|
+
codes.push(2);
|
|
39
|
+
}
|
|
40
|
+
flagDelta(codes, previous, next, STYLE_UNDERLINE, 4, 24);
|
|
41
|
+
flagDelta(codes, previous, next, STYLE_INVERSE, 7, 27);
|
|
42
|
+
const previousFg = foreground(previous);
|
|
43
|
+
const nextFg = foreground(next);
|
|
44
|
+
if (previousFg !== nextFg)
|
|
45
|
+
codes.push(nextFg === 0 ? 39 : 30 + nextFg);
|
|
46
|
+
const previousBg = background(previous);
|
|
47
|
+
const nextBg = background(next);
|
|
48
|
+
if (previousBg !== nextBg)
|
|
49
|
+
codes.push(nextBg === 0 ? 49 : 40 + nextBg);
|
|
50
|
+
return codes.length === 0 ? "" : `\u001b[${[...new Set(codes)].join(";")}m`;
|
|
51
|
+
}
|
|
52
|
+
function flagDelta(codes, previous, next, flag, on, off) {
|
|
53
|
+
if ((previous & flag) !== (next & flag))
|
|
54
|
+
codes.push((next & flag) === 0 ? off : on);
|
|
55
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type TerminalInputEvent } from "./input.js";
|
|
2
|
+
export interface Size {
|
|
3
|
+
cols: number;
|
|
4
|
+
rows: number;
|
|
5
|
+
}
|
|
6
|
+
export interface TerminalDriver {
|
|
7
|
+
start(): void;
|
|
8
|
+
stop(): void;
|
|
9
|
+
onEvent(fn: (event: TerminalInputEvent) => void): () => void;
|
|
10
|
+
onResize(fn: (size: Size) => void): () => void;
|
|
11
|
+
getSize(): Size;
|
|
12
|
+
writeFrame(ansi: string): void;
|
|
13
|
+
}
|
|
14
|
+
interface InputStream {
|
|
15
|
+
setRawMode?: (enabled: boolean) => void;
|
|
16
|
+
resume(): void;
|
|
17
|
+
pause(): void;
|
|
18
|
+
on(event: "data", listener: (chunk: Buffer | string) => void): unknown;
|
|
19
|
+
off(event: "data", listener: (chunk: Buffer | string) => void): unknown;
|
|
20
|
+
}
|
|
21
|
+
interface OutputStream {
|
|
22
|
+
columns?: number;
|
|
23
|
+
rows?: number;
|
|
24
|
+
write(value: string): boolean;
|
|
25
|
+
on(event: "resize", listener: () => void): unknown;
|
|
26
|
+
off(event: "resize", listener: () => void): unknown;
|
|
27
|
+
}
|
|
28
|
+
export declare function createTerminalDriver(options?: {
|
|
29
|
+
input?: InputStream;
|
|
30
|
+
output?: OutputStream;
|
|
31
|
+
escTimeoutMs?: number;
|
|
32
|
+
mouse?: boolean;
|
|
33
|
+
}): TerminalDriver;
|
|
34
|
+
export type { TerminalInputEvent } from "./input.js";
|