zerogterm 0.5.0-alpha.1 → 0.7.0-alpha1
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 -21
- package/README.md +232 -193
- package/bin/zerogterm.cjs +23 -23
- package/dist/main/main/external-links.js +79 -0
- package/dist/main/main/local-fs.js +115 -0
- package/dist/main/main/main.js +91 -1
- package/dist/main/main/preload.cjs +58 -33
- package/dist/main/main/session-service.js +38 -7
- package/dist/main/main/sftp-protocol.js +279 -0
- package/dist/main/main/sftp-service.js +465 -0
- package/dist/main/main/shell-catalog.js +45 -0
- package/dist/main/shared/ansi.js +34 -0
- package/dist/main/shared/files.js +88 -0
- package/dist/renderer/assets/index-D5eoFnzh.css +1 -0
- package/dist/renderer/assets/index-EEjSxqRY.js +19 -0
- package/dist/renderer/index.html +4 -4
- package/package.json +67 -67
- package/dist/renderer/assets/index-BJsoL2QL.js +0 -17
- package/dist/renderer/assets/index-BPfYUrvm.css +0 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Stephen Phillips
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stephen Phillips
|
|
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,193 +1,232 @@
|
|
|
1
|
-
# ZeroG Terminal
|
|
2
|
-
|
|
3
|
-
ZeroG Terminal is an Electron workspace manager for persistent terminal and SSH sessions on Linux and Windows, with smart features to help keep you productive. It is designed with AI tooling in mind: leave Codex, Claude Code, or other terminal-based agents running in persistent sessions, then return to them without repeating setup and resume commands.
|
|
4
|
-
|
|
5
|
-
ZeroG Terminal is an alpha project, but it is already useful as a multi-session terminal and SSH workspace. It uses `screen` where available for resumable sessions and falls back to a direct shell PTY when `screen` is not installed.
|
|
6
|
-
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
- Multi-pane workspaces with stack, vertical split, horizontal split, and four-pane grid layouts.
|
|
10
|
-
- Draggable dividers between panes and beside the sidebar, so a split does not have to be an even one. Sizes are remembered between launches.
|
|
11
|
-
- Maximize a focused pane and cycle between sessions without losing the other panes.
|
|
12
|
-
- Local sessions powered by Bash, PowerShell, WSL, and other supported shell backends; persistent sessions use `screen` where available, with a process-only fallback when it is unavailable.
|
|
13
|
-
- SSH sessions for hosts, `user@host`, and `user@host:port` targets.
|
|
14
|
-
- SSH configuration discovery from known connections, including remote `screen` session discovery.
|
|
15
|
-
- Reconnect to existing local or remote `screen` sessions from the Screens view.
|
|
16
|
-
- Remote screen attachment that waits for SSH readiness before sending commands, including host and port-aware matching.
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
- A
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- `Ctrl+Shift+
|
|
42
|
-
- `Ctrl+
|
|
43
|
-
- `Ctrl+
|
|
44
|
-
- `Ctrl+Shift+
|
|
45
|
-
- `Ctrl+Shift+
|
|
46
|
-
- `Ctrl+Shift+
|
|
47
|
-
- `Ctrl+Shift
|
|
48
|
-
- `
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
`
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
##
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
1
|
+
# ZeroG Terminal
|
|
2
|
+
|
|
3
|
+
ZeroG Terminal is an Electron workspace manager for persistent terminal and SSH sessions on Linux and Windows, with smart features to help keep you productive. It is designed with AI tooling in mind: leave Codex, Claude Code, or other terminal-based agents running in persistent sessions, then return to them without repeating setup and resume commands.
|
|
4
|
+
|
|
5
|
+
ZeroG Terminal is an alpha project, but it is already useful as a multi-session terminal and SSH workspace. It uses `screen` where available for resumable sessions and falls back to a direct shell PTY when `screen` is not installed.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Multi-pane workspaces with stack, vertical split, horizontal split, and four-pane grid layouts.
|
|
10
|
+
- Draggable dividers between panes and beside the sidebar, so a split does not have to be an even one. Sizes are remembered between launches.
|
|
11
|
+
- Maximize a focused pane and cycle between sessions without losing the other panes.
|
|
12
|
+
- Local sessions powered by Bash, PowerShell, WSL, and other supported shell backends; persistent sessions use `screen` where available, with a process-only fallback when it is unavailable.
|
|
13
|
+
- SSH sessions for hosts, `user@host`, and `user@host:port` targets.
|
|
14
|
+
- SSH configuration discovery from known connections, including remote `screen` session discovery.
|
|
15
|
+
- Reconnect to existing local or remote `screen` sessions from the Screens view.
|
|
16
|
+
- Remote screen attachment that waits for SSH readiness before sending commands, including host and port-aware matching.
|
|
17
|
+
- An SFTP transfer panel, opened from the ⇅ button above the panes: local files on the left, the active SSH session's host on the right, with upload, download, new folder, rename, and delete. It connects to the host that session is already using and opens at the directory its shell is standing in, so a file can go straight to the project being worked on.
|
|
18
|
+
- Session history for reconnecting to sessions after a relaunch, with bounded structured history and no stored secrets.
|
|
19
|
+
- Workspaces for grouping sessions and quickly switching between projects or tasks.
|
|
20
|
+
- Session overview, collapsible sidebar, keyboard shortcuts, and light/dark themes.
|
|
21
|
+
- xterm.js terminal rendering with scrollback preservation while changing layouts.
|
|
22
|
+
- Local voice input, either with Whisper ONNX inside the app through Transformers.js or through a transcription server on this machine; transcribed text is typed into the selected terminal without automatic execution.
|
|
23
|
+
- A per-pane proceed button that sends a configurable phrase — `OK, proceed` by default — for waving an agent on without typing the same reply again.
|
|
24
|
+
- A settings panel for appearance, terminal behaviour, session defaults, and speech recognition, including a built-in recognition test.
|
|
25
|
+
- AI command suggestion and approval UI, keeping command execution explicit.
|
|
26
|
+
- Sandboxed Electron renderer, context isolation, disabled Node integration, and a narrow typed preload API.
|
|
27
|
+
- Safe argument-array handling and validation around SSH and `screen` session operations.
|
|
28
|
+
|
|
29
|
+
The project is particularly useful for terminal-based AI development workflows: start an agent in a persistent session, disconnect or suffer an interrupted connection, and reconnect later to see what it has done and continue working.
|
|
30
|
+
|
|
31
|
+
See the project walkthrough on [YouTube](https://youtu.be/4aJZCxLHD14).
|
|
32
|
+
|
|
33
|
+
## Release status
|
|
34
|
+
|
|
35
|
+
ZeroG Terminal is currently a public alpha. The current release is `0.6.0-alpha.1`; the version history is tracked in [versions.txt](versions.txt).
|
|
36
|
+
|
|
37
|
+
The npm package contains the built Electron application and project documentation. It is intended for early adopters and testing rather than production use.
|
|
38
|
+
|
|
39
|
+
## Terminal shortcuts
|
|
40
|
+
|
|
41
|
+
- `Ctrl+Shift+C` — copy selected terminal text
|
|
42
|
+
- `Ctrl+Shift+V` — paste into the active terminal
|
|
43
|
+
- `Ctrl+C` remains the interrupt signal (not copy)
|
|
44
|
+
- `Ctrl+Shift+N` — new workspace
|
|
45
|
+
- `Ctrl+Shift+T` — new local terminal in the current workspace
|
|
46
|
+
- `Ctrl+Shift+O` — session overview
|
|
47
|
+
- `Ctrl+Shift+B` — toggle sessions sidebar
|
|
48
|
+
- `Ctrl+Shift+,` — settings
|
|
49
|
+
- `Esc` — close overview / dialogs, cancel voice recording
|
|
50
|
+
|
|
51
|
+
Clicking a link in a pane opens it in your own browser rather than in a window
|
|
52
|
+
of ZeroG. Hovering one first shows where it actually goes in the status bar,
|
|
53
|
+
which matters because a terminal hyperlink can be labelled with anything. Only
|
|
54
|
+
web links and `mailto:` addresses are opened; anything else says so in the
|
|
55
|
+
status bar instead, since a link in terminal output can name a scheme that would
|
|
56
|
+
start local software.
|
|
57
|
+
|
|
58
|
+
Selecting text with the mouse also copies it, and programs running inside a
|
|
59
|
+
terminal can copy to the system clipboard themselves through the OSC 52 escape
|
|
60
|
+
sequence — this is how TUI tools such as CLI coding agents, tmux and Neovim put
|
|
61
|
+
text on the clipboard, including over SSH. Reading the clipboard through OSC 52
|
|
62
|
+
is refused, so a program on a remote host cannot see what you last copied.
|
|
63
|
+
|
|
64
|
+
## Resizing panes and the sidebar
|
|
65
|
+
|
|
66
|
+
Drag the line between two panes, or the sidebar's right edge, to change how the
|
|
67
|
+
space is shared. Sizes are remembered between launches and clamped so that no
|
|
68
|
+
pane can be dragged down to nothing.
|
|
69
|
+
|
|
70
|
+
The dividers take keyboard focus as well: the arrow keys nudge one two percent at
|
|
71
|
+
a time, and Enter or a double-click puts it back in the middle. One divider
|
|
72
|
+
position is shared by every layout, so a split you set up in the vertical split
|
|
73
|
+
is the same split you get in the four-pane grid.
|
|
74
|
+
|
|
75
|
+
## Transferring files over SFTP
|
|
76
|
+
|
|
77
|
+
The ⇅ button above the panes opens a two-pane transfer panel: this computer on
|
|
78
|
+
the left, the active SSH session's host on the right. It is only available while
|
|
79
|
+
an SSH session is selected, and the button says why when it is not.
|
|
80
|
+
|
|
81
|
+
The connection is made with the system `sftp` client, so it uses the same
|
|
82
|
+
`~/.ssh/config`, agent, keys, and `known_hosts` as the terminal beside it. A
|
|
83
|
+
password, a key passphrase, or an unknown host key is asked for inside the
|
|
84
|
+
panel — ZeroG never answers a host-key question on your behalf, and the
|
|
85
|
+
fingerprint is shown with the question. Nothing typed there is stored.
|
|
86
|
+
|
|
87
|
+
The remote side opens at the directory the terminal's shell is currently in,
|
|
88
|
+
where that can be known without disturbing the session. ZeroG reads it from
|
|
89
|
+
OSC 7 — the sequence a shell emits to report its directory — and otherwise from
|
|
90
|
+
the path in the prompt; it never types `pwd` into your session to find out. When
|
|
91
|
+
neither is available the panel opens at the login directory.
|
|
92
|
+
|
|
93
|
+
Select files with a click, or several with Ctrl-click, then Upload or Download.
|
|
94
|
+
Double-click a folder to open it, or type a path into the folder box. New folder,
|
|
95
|
+
rename, and delete act on one selected item; deleting asks first, and a folder
|
|
96
|
+
must be empty, so a single click can never remove a tree. Folders themselves are
|
|
97
|
+
not transferred: a recursive copy is a different job with different failure
|
|
98
|
+
modes, and half-copying one silently would be worse than not offering it.
|
|
99
|
+
|
|
100
|
+
Filenames containing quotes, backslashes, or the wildcard characters `* ? [ ]`
|
|
101
|
+
are refused with a message rather than acted on. The `sftp` client re-reads its
|
|
102
|
+
own arguments through a glob pass, and there is no encoding of those characters
|
|
103
|
+
that is provably correct for every command — being approximately right about
|
|
104
|
+
which file to delete is not good enough.
|
|
105
|
+
|
|
106
|
+
## Settings
|
|
107
|
+
|
|
108
|
+
Settings open from the gear at the bottom of the left rail, the avatar in the
|
|
109
|
+
title bar, or `Ctrl+Shift+,`. Changes apply immediately and are remembered
|
|
110
|
+
between launches; each page can be reset on its own.
|
|
111
|
+
|
|
112
|
+
- **Appearance** — theme, terminal font, size, line height and letter spacing,
|
|
113
|
+
with a live preview. Panes restyle in place and keep their scrollback.
|
|
114
|
+
- **Terminal** — scrollback lines, cursor style and blink, and copy-on-select.
|
|
115
|
+
- **Sessions** — default shell and WSL distribution for new terminals, the
|
|
116
|
+
layout to start in, and whether the sidebar starts collapsed.
|
|
117
|
+
- **AI & voice** — whether AI suggestions need approval before running, and
|
|
118
|
+
whether a transcript is typed straight into the pane or shown for review
|
|
119
|
+
first. Neither option presses Enter for you. Also the phrase the pane's
|
|
120
|
+
proceed button sends, described below.
|
|
121
|
+
- **Speech recognition** — engine, model and tuning, described below.
|
|
122
|
+
|
|
123
|
+
### Proceed button
|
|
124
|
+
|
|
125
|
+
Each pane's title bar carries a tick beside the microphone. Clicking it sends
|
|
126
|
+
`OK, proceed` and presses Enter — for the common case of an AI coding agent
|
|
127
|
+
pausing to ask whether it should carry on. The phrase is editable under
|
|
128
|
+
**Settings ▸ AI & voice**, so an agent that responds better to different wording
|
|
129
|
+
can have it.
|
|
130
|
+
|
|
131
|
+
This is the one control that presses Enter for you; voice transcripts and AI
|
|
132
|
+
suggestions deliberately do not. It sends to the pane it belongs to, so a pane
|
|
133
|
+
sitting at a shell prompt rather than in an agent will simply try to run the
|
|
134
|
+
phrase as a command.
|
|
135
|
+
|
|
136
|
+
### Speech recognition
|
|
137
|
+
|
|
138
|
+
Two engines are available.
|
|
139
|
+
|
|
140
|
+
**Built-in** runs Whisper as ONNX inside the app through Transformers.js, with
|
|
141
|
+
nothing else to install. Choose the model (tiny, base or small; English-only or
|
|
142
|
+
multilingual), the weight precision, and whether to compute on CPU (WASM) or
|
|
143
|
+
GPU (WebGPU) — WebGPU falls back to WASM when it is unavailable. The panel shows
|
|
144
|
+
the download for the chosen combination, from about 41 MB for tiny at q8 to
|
|
145
|
+
about 968 MB for small at full precision; models are cached after first use.
|
|
146
|
+
Multilingual models add language and transcribe/translate options, which
|
|
147
|
+
English-only checkpoints reject and so do not show.
|
|
148
|
+
|
|
149
|
+
**Local server** posts the recorded audio as a WAV file to a transcription
|
|
150
|
+
server on this machine, using the OpenAI `/v1/audio/transcriptions` shape that
|
|
151
|
+
whisper.cpp's server, LM Studio, faster-whisper-server and similar tools speak.
|
|
152
|
+
This is the way to use a model the built-in engine cannot load — a GGUF build
|
|
153
|
+
such as `unslothai/Qwen3-ASR-0.6B-GGUF` needs a llama.cpp-family runtime, so
|
|
154
|
+
something else has to host it. The URL must be on this machine; a non-loopback
|
|
155
|
+
address is refused rather than sent.
|
|
156
|
+
|
|
157
|
+
Both engines share the maximum utterance length and the silence threshold, and
|
|
158
|
+
the **Try it** button on that page records a phrase and shows the transcript,
|
|
159
|
+
the recording level and how long transcription took, without typing into a
|
|
160
|
+
terminal. It transcribes even below the silence threshold and says so, which is
|
|
161
|
+
how the threshold gets tuned for a particular microphone.
|
|
162
|
+
|
|
163
|
+
## Installation and usage
|
|
164
|
+
|
|
165
|
+
The simplest way to try the published package is through `npx`:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
npx zerogterm
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The package downloads the application and launches it. To use the launcher repeatedly without downloading on each invocation, install it globally:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npm install --global zerogterm
|
|
175
|
+
zerogterm
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npm install
|
|
182
|
+
npm run typecheck
|
|
183
|
+
npm test
|
|
184
|
+
npm run build
|
|
185
|
+
npm start
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Node.js is the only prerequisite every host needs. The scripts above run on Linux
|
|
189
|
+
and Windows; npm runs them through `cmd.exe` on Windows, so no POSIX shell is
|
|
190
|
+
required whichever shell you start them from.
|
|
191
|
+
|
|
192
|
+
`node-pty` provides terminal I/O on every platform. It ships prebuilt binaries
|
|
193
|
+
for Windows and macOS, so a C/C++ toolchain is a Linux requirement rather than a
|
|
194
|
+
general one — there, `npm install` compiles it.
|
|
195
|
+
|
|
196
|
+
### Linux
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
sudo dnf install screen make gcc-c++ python3
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`make`, `gcc-c++` and `python3` build `node-pty`. `screen` is optional but worth
|
|
203
|
+
having: with it, local sessions are persistent and rediscovered after relaunch.
|
|
204
|
+
Without it, ZeroG falls back to a direct PTY on the chosen shell and labels the
|
|
205
|
+
session process-only; that shell is lost when the application exits.
|
|
206
|
+
|
|
207
|
+
### Windows
|
|
208
|
+
|
|
209
|
+
No tooling beyond Node.js. The new-terminal dialog offers the shells it finds on
|
|
210
|
+
PATH — Windows PowerShell, PowerShell 7, Command Prompt, WSL (with a distribution
|
|
211
|
+
picker), and Git Bash where Git for Windows is installed.
|
|
212
|
+
|
|
213
|
+
`screen` does not exist on Windows, so local sessions are always process-only and
|
|
214
|
+
do not survive app exit. Remote SSH sessions are unaffected by the local shell
|
|
215
|
+
backend, and a remote host with `screen` still gives persistent sessions there.
|
|
216
|
+
|
|
217
|
+
## Verification
|
|
218
|
+
|
|
219
|
+
The current main branch has the following local verification coverage:
|
|
220
|
+
|
|
221
|
+
- `npm run typecheck`: passes.
|
|
222
|
+
- `npm test`: passes (251 tests covering session service behaviour and PTY sizing, shell discovery, SSH inventory and argument validation, remote-screen parsing and prompt readiness, session history, the session dialog, settings, terminal clipboard and OSC 52 handling, dialog dismissal, the speech and voice helpers, external link handling, and the SFTP transfer path — command quoting, listing and error parsing, authentication prompts, local filesystem operations, and working-directory detection; one further test needs a real `screen` and is opt-in through `ZEROG_LIVE_SCREEN=1`).
|
|
223
|
+
- `npm run build`: passes and writes `dist/main` plus `dist/renderer`.
|
|
224
|
+
- `npm audit --omit=dev`: production dependency auditing is part of the project quality checks.
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
ZeroG Terminal is released under the MIT License. See [LICENSE](LICENSE).
|
|
229
|
+
|
|
230
|
+
## Contributing and security
|
|
231
|
+
|
|
232
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development, testing, and pull-request guidance. Please report security vulnerabilities privately through GitHub; see [SECURITY.md](SECURITY.md).
|
package/bin/zerogterm.cjs
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { spawn } = require('node:child_process');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const electron = require('electron');
|
|
6
|
-
|
|
7
|
-
const packageRoot = path.resolve(__dirname, '..');
|
|
8
|
-
const child = spawn(electron, [packageRoot, ...process.argv.slice(2)], {
|
|
9
|
-
stdio: 'inherit'
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
child.on('error', (error) => {
|
|
13
|
-
console.error(`Failed to start ZeroG Terminal: ${error.message}`);
|
|
14
|
-
process.exitCode = 1;
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
child.on('close', (code, signal) => {
|
|
18
|
-
if (signal) {
|
|
19
|
-
process.kill(process.pid, signal);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
process.exitCode = code ?? 1;
|
|
23
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const electron = require('electron');
|
|
6
|
+
|
|
7
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
8
|
+
const child = spawn(electron, [packageRoot, ...process.argv.slice(2)], {
|
|
9
|
+
stdio: 'inherit'
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
child.on('error', (error) => {
|
|
13
|
+
console.error(`Failed to start ZeroG Terminal: ${error.message}`);
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
child.on('close', (code, signal) => {
|
|
18
|
+
if (signal) {
|
|
19
|
+
process.kill(process.pid, signal);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
process.exitCode = code ?? 1;
|
|
23
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Deciding which links may leave the application, and by which door.
|
|
2
|
+
//
|
|
3
|
+
// A link in a pane is not something the user wrote: it arrives in terminal
|
|
4
|
+
// output, which SECURITY.md is explicit about treating as untrusted. Worse, OSC 8
|
|
5
|
+
// lets a remote host display one thing and link to another, so the URL handed
|
|
6
|
+
// over here has no relationship to the text the user clicked on.
|
|
7
|
+
//
|
|
8
|
+
// The operating system will do a great deal with a URL. `file:` reaches the local
|
|
9
|
+
// disk; on Windows `smb:` reaches a network share and leaks credentials to it;
|
|
10
|
+
// and installed applications register their own schemes, some of which take a
|
|
11
|
+
// path or a command. Handing any of those to the shell on a single click would
|
|
12
|
+
// make a remote host's output into a way of starting local software. So this is
|
|
13
|
+
// an allowlist, not a blocklist: the schemes a person means when they click a
|
|
14
|
+
// link in a terminal, and nothing else.
|
|
15
|
+
//
|
|
16
|
+
// Kept pure and free of Electron so the rule can be tested directly.
|
|
17
|
+
/** Schemes worth opening: the web, and an address to write to. */
|
|
18
|
+
const ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'mailto:']);
|
|
19
|
+
/**
|
|
20
|
+
* A URL is only long enough to be suspicious once it is absurd. The limit is
|
|
21
|
+
* here because the string is passed to the operating system, not because a real
|
|
22
|
+
* link is ever near it.
|
|
23
|
+
*/
|
|
24
|
+
const MAX_URL = 2048;
|
|
25
|
+
/** Written as escapes: a literal control character in source is invisible. */
|
|
26
|
+
const CONTROL_CHARACTER = /[\u0000-\u001f\u007f-\u009f]/;
|
|
27
|
+
/**
|
|
28
|
+
* Should this link be handed to the system browser?
|
|
29
|
+
*
|
|
30
|
+
* The URL is re-parsed rather than pattern-matched: a scheme is only what the
|
|
31
|
+
* parser says it is, and `java\tscript:` or a leading space are not the checks a
|
|
32
|
+
* regex tends to get right. The parsed form is what gets returned, so the value
|
|
33
|
+
* that reaches the shell is the one that was inspected.
|
|
34
|
+
*/
|
|
35
|
+
export function decideExternalLink(value) {
|
|
36
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
37
|
+
return { open: false, reason: 'That link is empty.' };
|
|
38
|
+
}
|
|
39
|
+
if (value.length > MAX_URL) {
|
|
40
|
+
return { open: false, reason: 'That link is too long to open.' };
|
|
41
|
+
}
|
|
42
|
+
// A control character cannot appear in a URL, and its presence means the value
|
|
43
|
+
// was assembled to be read one way and used another.
|
|
44
|
+
if (CONTROL_CHARACTER.test(value)) {
|
|
45
|
+
return { open: false, reason: 'That link contains control characters.' };
|
|
46
|
+
}
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = new URL(value);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return { open: false, reason: 'That is not a link this can open.' };
|
|
53
|
+
}
|
|
54
|
+
if (!ALLOWED_PROTOCOLS.has(parsed.protocol)) {
|
|
55
|
+
// Name the scheme: the user clicked something that looked like a link, and
|
|
56
|
+
// "refused" without saying why is not enough to judge what just happened.
|
|
57
|
+
return { open: false, reason: `ZeroG only opens web links; this one is ${parsed.protocol.replace(':', '')}.` };
|
|
58
|
+
}
|
|
59
|
+
return { open: true, url: parsed.href };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Is this URL the application's own window content?
|
|
63
|
+
*
|
|
64
|
+
* Used to tell a genuine outward link from the app navigating itself — the dev
|
|
65
|
+
* server and the packaged `file:` index are the only two things the window is
|
|
66
|
+
* ever meant to be showing.
|
|
67
|
+
*/
|
|
68
|
+
export function isApplicationUrl(value, current) {
|
|
69
|
+
if (value === current)
|
|
70
|
+
return true;
|
|
71
|
+
try {
|
|
72
|
+
const target = new URL(value);
|
|
73
|
+
const app = new URL(current);
|
|
74
|
+
return target.origin === app.origin && target.pathname === app.pathname;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|