Termora
Create, configure and launch dedicated terminals (frontend, backend, Electron, Git, database, …) from a graphical panel in VS Code. You never have to edit Features
Quick start
Keyboard: Working directory rules
The directory must exist when you save and when you launch. The folder browser stores workspace-relative paths automatically for workspace-scoped terminals. Where things are stored
Example stored entry (written for you by the UI):
Only these keys are written. All other user and workspace settings and all other terminal profiles are preserved (this is covered by the integration tests). Launching vs. terminal profiles
Shells and platforms
Nothing is hard-coded to a drive letter or install directory. If a shell is missing you get a clear message and the terminal is not launched. Workspace TrustWorkspace-scoped terminals run commands that come from the repository, so they are disabled in untrusted workspaces (the Workspace option is greyed out with an explanation). Global terminals keep working. Upgrading from "Terminal Manager"This extension used to be called Terminal Manager. On first start, Terminal Pilot copies terminals saved under the old Running the extension from source (F5)
Open this folder in VS Code and press F5 (Run Extension). A new Extension Development Host window opens with the extension loaded; open a folder in it, then open the Terminal Pilot tab in the bottom panel. Packaging a
|
| Suite | Command | What it covers |
|---|---|---|
| Unit | npm run test:unit |
validation, message parsing, path resolution, quoting, shell lookup, profile argument building |
| Webview | npm run test:webview |
the real media/main.js driven in jsdom: rendering, form, keyboard, escaping, message payloads |
| Integration | npm run test:integration |
a real VS Code instance: create/edit/delete/duplicate, settings preservation, terminal launch in the right folder, ordered commands, focus-instead-of-duplicate, stop, re-attach, profile sync |
The integration suite uses an installed VS Code (the default Windows location, or VSCODE_EXECUTABLE_PATH) and otherwise downloads one. It runs with an isolated user-data directory, so it never touches your own settings.
Architecture
src/
├── extension.ts wiring only: creates the objects below, registers commands/view
├── models/
│ ├── TerminalConfig.ts domain types, limits, constants (no vscode imports)
│ └── messages.ts typed webview <-> host protocol
├── controllers/
│ └── TerminalPilotController.ts use-cases (save, delete+undo, duplicate, run, stop, bulk) -> Outcome
├── services/
│ ├── ConfigurationService.ts persistence via the Configuration API; serialised writes; scopes
│ ├── TerminalService.ts create / track / focus / stop terminals; re-attach after reload
│ └── ProfileService.ts mirrors terminals into terminal.integrated.profiles.* (owned keys only)
├── shells/
│ ├── registry.ts one adapter per shell (the single place to add a shell)
│ ├── bootstrap.ts quoting + profile argument building per shell family
│ ├── pathLookup.ts PATH / PATHEXT lookup without spawning processes
│ └── vscodeContext.ts builds the environment description from the live VS Code
├── providers/
│ ├── TerminalPilotProvider.ts webview transport: validate messages, call controller, push state
│ └── webviewHtml.ts pure HTML template with the Content Security Policy
├── utils/ validation, paths, errors, logger, async queue
└── test/ unit + integration tests
media/ main.js, styles.css, icon.svg (webview UI, no framework)
test/webview.test.js jsdom tests for media/main.js
Design rules that keep it manageable:
- Layering: UI (provider +
media/) → controller (use-cases) → services (config, terminals, profiles) → pure helpers (models,utils,shells). Lower layers never import upper ones. - Pure core: validation, path resolution, shell lookup and quoting do not import
vscode, so they are unit-tested without an editor. - One protocol file:
models/messages.tsdefines every message;parseWebviewMessagevalidates each incoming message strictly (types, lengths, enums) before anything is acted on. - Stable ids: terminals are tracked by id, not name, so renaming never loses track of a running terminal.
- Ownership markers: the extension only edits settings it created (
terminalPilot.terminals, profile keys starting withTP:).
Extending
- Add a shell: add its id to
SHELL_IDS(models/TerminalConfig.ts), add it to theenuminpackage.json, and add one adapter inshells/registry.ts. The form's drop-down is populated from the registry. - Add a field (for example environment variables): extend
TerminalConfig/TerminalDraft,validateDraft/parseDraft/sanitizeStoredConfigs, pass it tocreateTerminalinTerminalService.launch, and add the input inwebviewHtml.ts/media/main.js. - Add an action: add a message to
models/messages.ts, handle it inTerminalPilotProvider.onMessage, implement it in the controller. Palette commands reuse the same controller methods.
Security
- Strict CSP:
default-src 'none', scripts only with a per-load nonce, styles and images only from the extension's media folder; no inline handlers. - The webview never uses
innerHTMLwith dynamic data; terminal names and commands are rendered as text. - Every message from the webview is validated, and unknown messages are dropped and logged.
- Stored settings are re-validated on read, because users can edit
settings.jsonby hand. - Workspace-scoped commands are ignored until the workspace is trusted.
Known limitations
- Commands are typed into the terminal one after another. A long-running first command (for example a dev server) delays the next ones until it exits, which is the same as typing them yourself. Use separate terminals for separate long-running processes.
- A relative working directory is resolved against the first folder of a multi-root workspace.
- Running Run all does not switch the panel to the Terminal tab (that would hide Terminal Pilot); the rows simply turn to Running. Click a row's button to jump to its terminal.
- "Re-attach after restart" relies on VS Code reviving terminals (
terminal.integrated.enablePersistentSessions, on by default). Terminals VS Code does not revive are simply shown as stopped.
License
MIT