@wringer/terminal 0.0.0-stage → 0.0.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/LICENSE +21 -0
- package/README.md +99 -2
- package/dist/build-text-keys.d.ts +2 -0
- package/dist/call-sessions.d.ts +3 -0
- package/dist/create-ghostty-engine.d.ts +7 -0
- package/dist/create-raw-log.d.ts +5 -0
- package/dist/create-terminal-session.d.ts +43 -0
- package/dist/create-wasm-bridge.d.ts +10 -0
- package/dist/encode-ghostty-key.d.ts +8 -0
- package/dist/find-screen-match.d.ts +12 -0
- package/dist/index.d.ts +3 -0
- package/dist/key-names.d.ts +1 -0
- package/dist/parse-ghostty-layout.d.ts +5 -0
- package/dist/parse-key.d.ts +2 -0
- package/dist/pick-session.d.ts +13 -0
- package/dist/pick-unwrapped-text.d.ts +1 -0
- package/dist/pick-visible-text.d.ts +1 -0
- package/dist/read-ghostty-module.d.ts +12 -0
- package/dist/read-ghostty-text.d.ts +2 -0
- package/dist/screen-engine.d.ts +30 -0
- package/dist/session-service-contract.d.ts +116 -0
- package/dist/start-terminal-sessions.d.ts +18 -0
- package/dist/terminal.d.ts +99 -0
- package/dist/to-session-service-methods.d.ts +6 -0
- package/ghostty/ghostty-vt.json +7 -0
- package/ghostty/ghostty-vt.wasm +0 -0
- package/package.json +45 -4
- package/src/build-text-keys.ts +66 -0
- package/src/call-sessions.ts +22 -0
- package/src/create-ghostty-engine.ts +104 -0
- package/src/create-raw-log.ts +49 -0
- package/src/create-terminal-session.ts +231 -0
- package/src/create-wasm-bridge.ts +85 -0
- package/src/encode-ghostty-key.ts +94 -0
- package/src/find-screen-match.ts +37 -0
- package/src/index.ts +11 -0
- package/src/key-names.ts +47 -0
- package/src/parse-ghostty-layout.ts +39 -0
- package/src/parse-key.ts +83 -0
- package/src/pick-session.ts +47 -0
- package/src/pick-unwrapped-text.ts +47 -0
- package/src/pick-visible-text.ts +8 -0
- package/src/read-ghostty-module.ts +26 -0
- package/src/read-ghostty-text.ts +56 -0
- package/src/screen-engine.ts +41 -0
- package/src/session-service-contract.ts +83 -0
- package/src/start-terminal-sessions.ts +152 -0
- package/src/terminal.ts +323 -0
- package/src/to-session-service-methods.ts +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Geoff Whatley
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,100 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>@wringer/terminal</h1>
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
<p>
|
|
5
|
+
<a href="https://www.npmjs.com/package/@wringer/terminal"><img src="https://img.shields.io/npm/v/@wringer/terminal" alt="npm version"></a>
|
|
6
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
|
|
7
|
+
</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
The terminal driver for wringer: it runs a target's text programs in a pseudo-terminal that the run
|
|
11
|
+
keeps open, sends them keys, and reads their screen.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun add @wringer/core @wringer/terminal
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The package runs on [Bun](https://bun.sh) 1.4 or newer, which provides the pseudo-terminal.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { createWringer, local } from '@wringer/core';
|
|
25
|
+
import { terminal } from '@wringer/terminal';
|
|
26
|
+
|
|
27
|
+
export const registry = createWringer({ name: 'shop' })
|
|
28
|
+
.use(terminal({ cols: 120, rows: 40 }))
|
|
29
|
+
.target('local', {
|
|
30
|
+
mode: 'attach',
|
|
31
|
+
access: 'read-write',
|
|
32
|
+
host: local(),
|
|
33
|
+
drivers: {
|
|
34
|
+
terminal: ({ run }) => ({
|
|
35
|
+
programs: {
|
|
36
|
+
tui: {
|
|
37
|
+
command: ['bun', 'src/cli.ts', 'tui'],
|
|
38
|
+
env: { SHOP_HOME: run.paths.directory },
|
|
39
|
+
cwd: '.',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
.build();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`cols` and `rows` default to 120 and 40. The target names each program an agent may start, so an
|
|
49
|
+
agent never passes a command line. A program's `cwd` starts at the folder where the run's supervisor
|
|
50
|
+
runs, which is the repo root, and its `env` adds to the supervisor's environment. The programs run
|
|
51
|
+
on the machine where the run lives, so use the driver with a `local()` host.
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
| Command | Effect | Answer |
|
|
56
|
+
| ------------------------------------------- | ------ | --------------------------------- |
|
|
57
|
+
| `terminal start <program>` | write | `{ session, pid }` |
|
|
58
|
+
| `terminal type --text <text>` | write | types the text as key events |
|
|
59
|
+
| `terminal type --secret-env <NAME>` | write | types the value of `$NAME` |
|
|
60
|
+
| `terminal press <key>...` | write | presses each key in order |
|
|
61
|
+
| `terminal wait --text <t>` or `--regex <r>` | read | `{ match: { row, col }, screen }` |
|
|
62
|
+
| `terminal screen` | read | `{ text, cursor, cols, rows }` |
|
|
63
|
+
| `terminal stop` | write | `{ exitCode }` |
|
|
64
|
+
| `terminal list` | read | the sessions of the run |
|
|
65
|
+
|
|
66
|
+
Every command except `start` and `list` takes `--session <id>`. It is optional while one session
|
|
67
|
+
runs, and required while more than one runs. Sessions are named `<program>-<n>`, such as `tui-1`.
|
|
68
|
+
|
|
69
|
+
- Rows and columns count from 0. `col` counts characters of the line. A `match` counts the screen's
|
|
70
|
+
rows, and `screen.text` joins the rows a program wrapped into one line, so a secret that wrapped
|
|
71
|
+
is still redacted. Its line numbers differ from the rows after a wrap.
|
|
72
|
+
- A key is a name such as `enter`, `esc`, `tab`, `up`, `f5`, `ctrl+c` or `alt+x`. The driver turns
|
|
73
|
+
it into the bytes that the program's current modes expect, so `ctrl+c` is `\x03` for a plain
|
|
74
|
+
program and `\x1b[99;5u` after the program turns on the kitty keyboard protocol. `press` refuses
|
|
75
|
+
`shift+1` and other shifted symbols, which depend on the layout: use `type --text '!'`.
|
|
76
|
+
- `wait` checks the screen after each chunk of output. It fails with `check-failed` when nothing
|
|
77
|
+
matches within `--timeout-ms` (10 s by default), and keeps the last screen as evidence.
|
|
78
|
+
- `screen` and a failed `wait` write `screen.txt` as evidence. The raw bytes of each session go to
|
|
79
|
+
`children/terminal-<session>.log` in the run folder, outside the evidence that a run uploads.
|
|
80
|
+
|
|
81
|
+
## Secrets
|
|
82
|
+
|
|
83
|
+
`type --secret-env <NAME>` reads the variable from the environment of the command, and adds its
|
|
84
|
+
value to the command's secrets before it sends a key. The value is then redacted from the envelope
|
|
85
|
+
and from every evidence file, including the screen.
|
|
86
|
+
|
|
87
|
+
## Read-only targets
|
|
88
|
+
|
|
89
|
+
A read-only target refuses `start`, `type`, `press` and `stop` with exit code 3 and the target's
|
|
90
|
+
hint. `screen`, `wait` and `list` still run.
|
|
91
|
+
|
|
92
|
+
## Screen engine
|
|
93
|
+
|
|
94
|
+
The screen comes from [libghostty-vt](https://github.com/ghostty-org/ghostty) 1.0, compiled to WASM
|
|
95
|
+
and committed as `ghostty/ghostty-vt.wasm`. `ghostty/ghostty-vt.json` records the tag, the commit,
|
|
96
|
+
the Zig version and the sha256 of the file. `bun run build:ghostty` rebuilds both, and needs `zig`
|
|
97
|
+
0.17.0 on the path.
|
|
98
|
+
|
|
99
|
+
The WASM has no imports, so the engine cannot answer a program's queries to the terminal, such as a
|
|
100
|
+
cursor position request. A program that waits for such an answer stalls.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ServiceCaller } from '@wringer/core';
|
|
2
|
+
import type { SessionArgs, SessionMethod, SessionResult } from './session-service-contract.ts';
|
|
3
|
+
export declare function callSessions<Method extends SessionMethod>(sessions: ServiceCaller, method: Method, args: SessionArgs<Method>): Promise<SessionResult<Method>>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ScreenEngine } from './screen-engine.ts';
|
|
2
|
+
export interface GhosttyEngineOptions {
|
|
3
|
+
readonly module: WebAssembly.Module;
|
|
4
|
+
readonly cols: number;
|
|
5
|
+
readonly rows: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function createGhosttyEngine(options: GhosttyEngineOptions): ScreenEngine;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ScreenMatch, ScreenPattern } from './find-screen-match.ts';
|
|
2
|
+
import type { ScreenEngine } from './screen-engine.ts';
|
|
3
|
+
interface Screen {
|
|
4
|
+
readonly text: string;
|
|
5
|
+
readonly cursor: {
|
|
6
|
+
readonly row: number;
|
|
7
|
+
readonly col: number;
|
|
8
|
+
readonly visible: boolean;
|
|
9
|
+
};
|
|
10
|
+
readonly cols: number;
|
|
11
|
+
readonly rows: number;
|
|
12
|
+
}
|
|
13
|
+
interface WaitResult {
|
|
14
|
+
readonly match: ScreenMatch | null;
|
|
15
|
+
readonly screen: Screen;
|
|
16
|
+
}
|
|
17
|
+
export interface TerminalSession {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly program: string;
|
|
20
|
+
readonly pid: number;
|
|
21
|
+
readonly isRunning: () => boolean;
|
|
22
|
+
readonly readExitCode: () => number | null;
|
|
23
|
+
readonly readScreen: () => Screen;
|
|
24
|
+
readonly writeText: (text: string) => void;
|
|
25
|
+
readonly writeKeys: (names: readonly string[]) => void;
|
|
26
|
+
readonly waitForScreen: (pattern: ScreenPattern, timeoutMs: number, signal: AbortSignal) => Promise<WaitResult>;
|
|
27
|
+
readonly stop: () => Promise<number>;
|
|
28
|
+
readonly dispose: () => Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export interface TerminalSessionOptions {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly program: string;
|
|
33
|
+
readonly argv: readonly string[];
|
|
34
|
+
readonly cwd: string;
|
|
35
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
36
|
+
readonly cols: number;
|
|
37
|
+
readonly rows: number;
|
|
38
|
+
readonly engine: ScreenEngine;
|
|
39
|
+
readonly rawLogPath: string;
|
|
40
|
+
readonly reportError: (message: string) => void;
|
|
41
|
+
}
|
|
42
|
+
export declare function createTerminalSession(options: TerminalSessionOptions): Promise<TerminalSession>;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GhosttyLayout } from './parse-ghostty-layout.ts';
|
|
2
|
+
export interface WasmBridge {
|
|
3
|
+
readonly layout: GhosttyLayout;
|
|
4
|
+
readonly memory: WebAssembly.Memory;
|
|
5
|
+
readonly call: (name: string, ...args: number[]) => number;
|
|
6
|
+
readonly withScratch: <Result>(size: number, use: (pointer: number) => Result) => Result;
|
|
7
|
+
readonly expectSuccess: (result: number, what: string) => void;
|
|
8
|
+
readonly createHandle: (create: (slot: number) => number, what: string) => number;
|
|
9
|
+
}
|
|
10
|
+
export declare function createWasmBridge(module: WebAssembly.Module): WasmBridge;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WasmBridge } from './create-wasm-bridge.ts';
|
|
2
|
+
import type { KeyInput } from './screen-engine.ts';
|
|
3
|
+
export interface KeyHandles {
|
|
4
|
+
readonly encoder: number;
|
|
5
|
+
readonly keyEvent: number;
|
|
6
|
+
readonly terminal: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function encodeGhosttyKey(bridge: WasmBridge, handles: KeyHandles, key: KeyInput): Uint8Array;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ScreenPattern = {
|
|
2
|
+
readonly kind: 'text';
|
|
3
|
+
readonly text: string;
|
|
4
|
+
} | {
|
|
5
|
+
readonly kind: 'regex';
|
|
6
|
+
readonly source: string;
|
|
7
|
+
};
|
|
8
|
+
export interface ScreenMatch {
|
|
9
|
+
readonly row: number;
|
|
10
|
+
readonly col: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function findScreenMatch(text: string, pattern: ScreenPattern): ScreenMatch | null;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const keyNames: Readonly<Record<string, string>>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface SessionSummary {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly running: boolean;
|
|
4
|
+
}
|
|
5
|
+
export type SessionPick = {
|
|
6
|
+
readonly kind: 'found';
|
|
7
|
+
readonly id: string;
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: 'refused';
|
|
10
|
+
readonly message: string;
|
|
11
|
+
readonly hint: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function pickSession(sessions: readonly SessionSummary[], requested: string | undefined): SessionPick;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function pickUnwrappedText(wrappedText: string, logicalLines: readonly string[], totalRows: number, rows: number): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function pickVisibleText(text: string, totalRows: number, rows: number): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const ghosttyWasmURL: URL;
|
|
3
|
+
declare const manifestSchema: z.ZodObject<{
|
|
4
|
+
tag: z.ZodString;
|
|
5
|
+
commit: z.ZodString;
|
|
6
|
+
zig: z.ZodString;
|
|
7
|
+
optimize: z.ZodString;
|
|
8
|
+
sha256: z.ZodString;
|
|
9
|
+
}, z.core.$strict>;
|
|
10
|
+
export declare function readGhosttyManifest(): Promise<z.infer<typeof manifestSchema>>;
|
|
11
|
+
export declare function readGhosttyModule(): Promise<WebAssembly.Module>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface KeyMods {
|
|
2
|
+
readonly shift: boolean;
|
|
3
|
+
readonly ctrl: boolean;
|
|
4
|
+
readonly alt: boolean;
|
|
5
|
+
readonly super: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface KeyInput {
|
|
8
|
+
readonly key: string;
|
|
9
|
+
readonly mods: KeyMods;
|
|
10
|
+
readonly text?: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface CursorState {
|
|
13
|
+
readonly row: number;
|
|
14
|
+
readonly col: number;
|
|
15
|
+
readonly visible: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface ScreenSize {
|
|
18
|
+
readonly cols: number;
|
|
19
|
+
readonly rows: number;
|
|
20
|
+
}
|
|
21
|
+
export interface ScreenEngine {
|
|
22
|
+
readonly write: (bytes: Uint8Array) => void;
|
|
23
|
+
readonly resize: (size: ScreenSize) => void;
|
|
24
|
+
readonly text: () => string;
|
|
25
|
+
readonly unwrappedText: () => string;
|
|
26
|
+
readonly cursor: () => CursorState;
|
|
27
|
+
readonly encodeKey: (key: KeyInput) => Uint8Array;
|
|
28
|
+
readonly dispose: () => void;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const screenSchema: z.ZodObject<{
|
|
3
|
+
text: z.ZodString;
|
|
4
|
+
cursor: z.ZodObject<{
|
|
5
|
+
row: z.ZodNumber;
|
|
6
|
+
col: z.ZodNumber;
|
|
7
|
+
visible: z.ZodBoolean;
|
|
8
|
+
}, z.core.$strict>;
|
|
9
|
+
cols: z.ZodNumber;
|
|
10
|
+
rows: z.ZodNumber;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
export declare const sessionInfoSchema: z.ZodObject<{
|
|
13
|
+
session: z.ZodString;
|
|
14
|
+
program: z.ZodString;
|
|
15
|
+
pid: z.ZodNumber;
|
|
16
|
+
running: z.ZodBoolean;
|
|
17
|
+
exitCode: z.ZodNullable<z.ZodNumber>;
|
|
18
|
+
}, z.core.$strict>;
|
|
19
|
+
export declare const sessionServiceContract: {
|
|
20
|
+
start: {
|
|
21
|
+
args: z.ZodObject<{
|
|
22
|
+
program: z.ZodString;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
result: z.ZodObject<{
|
|
25
|
+
session: z.ZodString;
|
|
26
|
+
pid: z.ZodNumber;
|
|
27
|
+
}, z.core.$strict>;
|
|
28
|
+
};
|
|
29
|
+
type: {
|
|
30
|
+
args: z.ZodObject<{
|
|
31
|
+
session: z.ZodOptional<z.ZodString>;
|
|
32
|
+
text: z.ZodString;
|
|
33
|
+
}, z.core.$strict>;
|
|
34
|
+
result: z.ZodObject<{
|
|
35
|
+
session: z.ZodString;
|
|
36
|
+
}, z.core.$strict>;
|
|
37
|
+
};
|
|
38
|
+
press: {
|
|
39
|
+
args: z.ZodObject<{
|
|
40
|
+
session: z.ZodOptional<z.ZodString>;
|
|
41
|
+
keys: z.ZodArray<z.ZodString>;
|
|
42
|
+
}, z.core.$strict>;
|
|
43
|
+
result: z.ZodObject<{
|
|
44
|
+
session: z.ZodString;
|
|
45
|
+
}, z.core.$strict>;
|
|
46
|
+
};
|
|
47
|
+
wait: {
|
|
48
|
+
args: z.ZodObject<{
|
|
49
|
+
session: z.ZodOptional<z.ZodString>;
|
|
50
|
+
text: z.ZodOptional<z.ZodString>;
|
|
51
|
+
regex: z.ZodOptional<z.ZodString>;
|
|
52
|
+
timeoutMs: z.ZodNumber;
|
|
53
|
+
}, z.core.$strict>;
|
|
54
|
+
result: z.ZodObject<{
|
|
55
|
+
session: z.ZodString;
|
|
56
|
+
match: z.ZodNullable<z.ZodObject<{
|
|
57
|
+
row: z.ZodNumber;
|
|
58
|
+
col: z.ZodNumber;
|
|
59
|
+
}, z.core.$strict>>;
|
|
60
|
+
screen: z.ZodObject<{
|
|
61
|
+
text: z.ZodString;
|
|
62
|
+
cursor: z.ZodObject<{
|
|
63
|
+
row: z.ZodNumber;
|
|
64
|
+
col: z.ZodNumber;
|
|
65
|
+
visible: z.ZodBoolean;
|
|
66
|
+
}, z.core.$strict>;
|
|
67
|
+
cols: z.ZodNumber;
|
|
68
|
+
rows: z.ZodNumber;
|
|
69
|
+
}, z.core.$strict>;
|
|
70
|
+
}, z.core.$strict>;
|
|
71
|
+
};
|
|
72
|
+
screen: {
|
|
73
|
+
args: z.ZodObject<{
|
|
74
|
+
session: z.ZodOptional<z.ZodString>;
|
|
75
|
+
}, z.core.$strict>;
|
|
76
|
+
result: z.ZodObject<{
|
|
77
|
+
session: z.ZodString;
|
|
78
|
+
screen: z.ZodObject<{
|
|
79
|
+
text: z.ZodString;
|
|
80
|
+
cursor: z.ZodObject<{
|
|
81
|
+
row: z.ZodNumber;
|
|
82
|
+
col: z.ZodNumber;
|
|
83
|
+
visible: z.ZodBoolean;
|
|
84
|
+
}, z.core.$strict>;
|
|
85
|
+
cols: z.ZodNumber;
|
|
86
|
+
rows: z.ZodNumber;
|
|
87
|
+
}, z.core.$strict>;
|
|
88
|
+
}, z.core.$strict>;
|
|
89
|
+
};
|
|
90
|
+
stop: {
|
|
91
|
+
args: z.ZodObject<{
|
|
92
|
+
session: z.ZodOptional<z.ZodString>;
|
|
93
|
+
}, z.core.$strict>;
|
|
94
|
+
result: z.ZodObject<{
|
|
95
|
+
session: z.ZodString;
|
|
96
|
+
exitCode: z.ZodNumber;
|
|
97
|
+
}, z.core.$strict>;
|
|
98
|
+
};
|
|
99
|
+
list: {
|
|
100
|
+
args: z.ZodNull;
|
|
101
|
+
result: z.ZodObject<{
|
|
102
|
+
sessions: z.ZodArray<z.ZodObject<{
|
|
103
|
+
session: z.ZodString;
|
|
104
|
+
program: z.ZodString;
|
|
105
|
+
pid: z.ZodNumber;
|
|
106
|
+
running: z.ZodBoolean;
|
|
107
|
+
exitCode: z.ZodNullable<z.ZodNumber>;
|
|
108
|
+
}, z.core.$strict>>;
|
|
109
|
+
}, z.core.$strict>;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
type SessionServiceContract = typeof sessionServiceContract;
|
|
113
|
+
export type SessionMethod = keyof SessionServiceContract;
|
|
114
|
+
export type SessionArgs<Method extends SessionMethod> = z.output<SessionServiceContract[Method]['args']>;
|
|
115
|
+
export type SessionResult<Method extends SessionMethod> = z.output<SessionServiceContract[Method]['result']>;
|
|
116
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ServiceInstance } from '@wringer/core';
|
|
2
|
+
interface ProgramConfig {
|
|
3
|
+
readonly command: readonly string[];
|
|
4
|
+
readonly env?: Readonly<Record<string, string>> | undefined;
|
|
5
|
+
readonly cwd?: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export interface TerminalSessionsOptions {
|
|
8
|
+
readonly programs: Readonly<Record<string, ProgramConfig>>;
|
|
9
|
+
readonly cols: number;
|
|
10
|
+
readonly rows: number;
|
|
11
|
+
readonly module: WebAssembly.Module;
|
|
12
|
+
readonly root: string;
|
|
13
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
14
|
+
readonly rawLogPath: (sessionID: string) => string;
|
|
15
|
+
readonly reportError: (message: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function startTerminalSessions(options: TerminalSessionsOptions): ServiceInstance;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { screenSchema, sessionInfoSchema } from './session-service-contract.ts';
|
|
3
|
+
declare const startArgsSchema: z.ZodObject<{
|
|
4
|
+
program: z.ZodString;
|
|
5
|
+
}, z.core.$strict>;
|
|
6
|
+
declare const typeArgsSchema: z.ZodObject<{
|
|
7
|
+
text: z.ZodOptional<z.ZodString>;
|
|
8
|
+
secretEnv: z.ZodOptional<z.ZodString>;
|
|
9
|
+
session: z.ZodOptional<z.ZodString>;
|
|
10
|
+
}, z.core.$strict>;
|
|
11
|
+
declare const pressArgsSchema: z.ZodObject<{
|
|
12
|
+
keys: z.ZodArray<z.ZodString>;
|
|
13
|
+
session: z.ZodOptional<z.ZodString>;
|
|
14
|
+
}, z.core.$strict>;
|
|
15
|
+
declare const waitArgsSchema: z.ZodObject<{
|
|
16
|
+
text: z.ZodOptional<z.ZodString>;
|
|
17
|
+
regex: z.ZodOptional<z.ZodString>;
|
|
18
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
19
|
+
session: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, z.core.$strict>;
|
|
21
|
+
declare const sessionArgsSchema: z.ZodObject<{
|
|
22
|
+
session: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
declare const sessionDataSchema: z.ZodObject<{
|
|
25
|
+
session: z.ZodString;
|
|
26
|
+
}, z.core.$strict>;
|
|
27
|
+
declare const startDataSchema: z.ZodObject<{
|
|
28
|
+
session: z.ZodString;
|
|
29
|
+
pid: z.ZodNumber;
|
|
30
|
+
}, z.core.$strict>;
|
|
31
|
+
declare const pressDataSchema: z.ZodObject<{
|
|
32
|
+
session: z.ZodString;
|
|
33
|
+
keys: z.ZodArray<z.ZodString>;
|
|
34
|
+
}, z.core.$strict>;
|
|
35
|
+
declare const waitDataSchema: z.ZodObject<{
|
|
36
|
+
session: z.ZodString;
|
|
37
|
+
match: z.ZodObject<{
|
|
38
|
+
row: z.ZodNumber;
|
|
39
|
+
col: z.ZodNumber;
|
|
40
|
+
}, z.core.$strict>;
|
|
41
|
+
screen: z.ZodObject<{
|
|
42
|
+
text: z.ZodString;
|
|
43
|
+
cursor: z.ZodObject<{
|
|
44
|
+
row: z.ZodNumber;
|
|
45
|
+
col: z.ZodNumber;
|
|
46
|
+
visible: z.ZodBoolean;
|
|
47
|
+
}, z.core.$strict>;
|
|
48
|
+
cols: z.ZodNumber;
|
|
49
|
+
rows: z.ZodNumber;
|
|
50
|
+
}, z.core.$strict>;
|
|
51
|
+
}, z.core.$strict>;
|
|
52
|
+
declare const screenDataSchema: z.ZodObject<{
|
|
53
|
+
text: z.ZodString;
|
|
54
|
+
cursor: z.ZodObject<{
|
|
55
|
+
row: z.ZodNumber;
|
|
56
|
+
col: z.ZodNumber;
|
|
57
|
+
visible: z.ZodBoolean;
|
|
58
|
+
}, z.core.$strict>;
|
|
59
|
+
cols: z.ZodNumber;
|
|
60
|
+
rows: z.ZodNumber;
|
|
61
|
+
session: z.ZodString;
|
|
62
|
+
}, z.core.$strict>;
|
|
63
|
+
declare const stopDataSchema: z.ZodObject<{
|
|
64
|
+
session: z.ZodString;
|
|
65
|
+
exitCode: z.ZodNumber;
|
|
66
|
+
}, z.core.$strict>;
|
|
67
|
+
declare const listDataSchema: z.ZodObject<{
|
|
68
|
+
sessions: z.ZodArray<z.ZodObject<{
|
|
69
|
+
session: z.ZodString;
|
|
70
|
+
program: z.ZodString;
|
|
71
|
+
pid: z.ZodNumber;
|
|
72
|
+
running: z.ZodBoolean;
|
|
73
|
+
exitCode: z.ZodNullable<z.ZodNumber>;
|
|
74
|
+
}, z.core.$strict>>;
|
|
75
|
+
}, z.core.$strict>;
|
|
76
|
+
export type TerminalScreen = z.infer<typeof screenSchema>;
|
|
77
|
+
export type TerminalSessionInfo = z.infer<typeof sessionInfoSchema>;
|
|
78
|
+
export type TerminalWaitRequest = z.input<typeof waitArgsSchema>;
|
|
79
|
+
export type TerminalStartRequest = z.infer<typeof startArgsSchema>;
|
|
80
|
+
export interface TerminalClient {
|
|
81
|
+
readonly start: (request: TerminalStartRequest) => Promise<z.infer<typeof startDataSchema>>;
|
|
82
|
+
readonly type: (request: z.input<typeof typeArgsSchema>) => Promise<z.infer<typeof sessionDataSchema>>;
|
|
83
|
+
readonly press: (request: z.input<typeof pressArgsSchema>) => Promise<z.infer<typeof pressDataSchema>>;
|
|
84
|
+
readonly wait: (request: TerminalWaitRequest) => Promise<z.infer<typeof waitDataSchema>>;
|
|
85
|
+
readonly screen: (request?: z.input<typeof sessionArgsSchema>) => Promise<z.infer<typeof screenDataSchema>>;
|
|
86
|
+
readonly stop: (request?: z.input<typeof sessionArgsSchema>) => Promise<z.infer<typeof stopDataSchema>>;
|
|
87
|
+
readonly list: () => Promise<z.infer<typeof listDataSchema>>;
|
|
88
|
+
}
|
|
89
|
+
export declare const terminal: import("@wringer/core").DriverFactory<"terminal", {
|
|
90
|
+
cols?: number | undefined;
|
|
91
|
+
rows?: number | undefined;
|
|
92
|
+
}, {
|
|
93
|
+
programs: Record<string, {
|
|
94
|
+
command: string[];
|
|
95
|
+
env?: Record<string, string> | undefined;
|
|
96
|
+
cwd?: string | undefined;
|
|
97
|
+
}>;
|
|
98
|
+
}, TerminalClient>;
|
|
99
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ServiceMethod } from '@wringer/core';
|
|
2
|
+
import type { SessionArgs, SessionMethod, SessionResult } from './session-service-contract.ts';
|
|
3
|
+
export type SessionHandlers = {
|
|
4
|
+
readonly [Method in SessionMethod]: (args: SessionArgs<Method>, signal: AbortSignal) => Promise<SessionResult<Method>>;
|
|
5
|
+
};
|
|
6
|
+
export declare function toSessionServiceMethods(handlers: SessionHandlers): Readonly<Record<SessionMethod, ServiceMethod>>;
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wringer/terminal",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "The terminal driver for wringer: drives a text program of a target's app in a pseudo-terminal and reads its screen",
|
|
5
|
+
"homepage": "https://github.com/zgeoff/wringer/tree/main/packages/terminal#readme",
|
|
6
|
+
"bugs": "https://github.com/zgeoff/wringer/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/zgeoff/wringer.git",
|
|
11
|
+
"directory": "packages/terminal"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src",
|
|
15
|
+
"!src/**/*.test.ts",
|
|
16
|
+
"dist",
|
|
17
|
+
"!src/test-utils",
|
|
18
|
+
"ghostty"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./src/index.ts"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "rm -rf dist && tsrs -p tsconfig.build.json",
|
|
32
|
+
"build:ghostty": "bash scripts/build-ghostty-vt.sh",
|
|
33
|
+
"typecheck": "tsrs --noEmit"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@wringer/core": "0.2.0",
|
|
37
|
+
"@zgeoff/testing": "0.3.0",
|
|
38
|
+
"zod": "4.6.7"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@wringer/core": "0.2.0",
|
|
42
|
+
"zod": "^4.0.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"bun": ">=1.4"
|
|
46
|
+
}
|
|
47
|
+
}
|