Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/terminal-mode-custom-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"git-work-grove": minor
---

Add terminal execution mode for custom commands
133 changes: 127 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,27 +122,148 @@ Open VS Code Settings (`Cmd+,` / `Ctrl+,`) and search for `git-work-grove`:

### Custom Commands

Define custom commands that appear in the tree view context menu. Two settings are available — one for directory items (repository/worktree), one for workspace file items:
Define custom commands that appear in the tree view context menu. Two settings are available:

- `git-work-grove.customCommands.directory` — for repository/worktree items
- `git-work-grove.customCommands.workspace` — for workspace file items

#### Entry Schema

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `label` | `string` | Yes | Display text in context menu and QuickPick |
| `command` | `string[]` | Yes | Command as `[bin, ...args]` — supports template variables |
| `env` | `Record<string, string>` | No | Environment variables — values support template variables |
| `mode` | `"spawn"` \| `"terminal"` | No | Execution mode (default: `"spawn"`) |

**Execution modes:**

- **`"spawn"`** (default) — Runs the command as a detached background process (fire-and-forget). The process outlives the extension and runs silently.
- **`"terminal"`** — Runs the command in a VS Code integrated terminal. The terminal stays open so you can see output and interact with the process. Ideal for dev servers, REPLs, and interactive CLI tools.

#### Template Variables

Directory items (`customCommands.directory`): `{name}`, `{branch}`, `{ref}`, `{head}`, `{path}`

Workspace items (`customCommands.workspace`): all of the above, plus `{dir}` (parent directory) and `{worktree}` (parent worktree folder name)

Both `command` and `env` values support template variables with fallback syntax (`{branch|detached}`) and conditional sections (`{?branch}...{/branch}`). See [Template Customization](https://github.com/vp-tw/vscode-extension-git-work-grove/blob/main/docs/templates.md) for syntax reference.

#### Examples

**Run a dev server in terminal:**

```json
{
"git-work-grove.customCommands.directory": [
{
"command": ["npm", "run", "dev"],
"env": { "NODE_ENV": "development" },
"label": "Run Dev Server"
"label": "Run Dev Server",
"mode": "terminal"
}
],
]
}
```

**Open in an external terminal emulator (macOS):**

```json
{
"git-work-grove.customCommands.directory": [
{
"command": ["open", "-a", "Terminal", "{path}"],
"label": "Open in Terminal.app"
}
]
}
```

Other terminal emulators:

| App | `command` |
|-----|-----------|
| iTerm2 | `["open", "-a", "iTerm", "{path}"]` |
| Ghostty | `["open", "-a", "Ghostty", "--args", "--working-directory={path}", "--window-inherit-working-directory=false"]` |
| WezTerm | `["wezterm", "start", "--cwd", "{path}"]` |
| Alacritty | `["alacritty", "--working-directory", "{path}"]` |
| Kitty | `["kitty", "--directory", "{path}"]` |

**Open in an external editor:**

```json
{
"git-work-grove.customCommands.directory": [
{
"command": ["zed", "{path}"],
"label": "Open in Zed"
}
]
}
```

**Environment variables with template variables:**

```json
{
"git-work-grove.customCommands.directory": [
{
"command": ["open", "-a", "Ghostty", "--args", "--working-directory={path}"],
"env": { "GHOSTTY_TITLE": "{ref}" },
"label": "Open in Ghostty"
}
]
}
```

**Multiple commands (shown as QuickPick):**

```json
{
"git-work-grove.customCommands.directory": [
{
"command": ["npm", "run", "dev"],
"label": "Run Dev Server",
"mode": "terminal"
},
{
"command": ["zed", "{path}"],
"label": "Open in Zed"
}
]
}
```

**Start Claude Code in a worktree:**

```json
{
"git-work-grove.customCommands.directory": [
{
"command": ["claude"],
"label": "Claude Code",
"mode": "terminal"
}
]
}
```

**Workspace file commands** — use `{dir}` for the parent directory or `{path}` for the file itself:

```json
{
"git-work-grove.customCommands.workspace": [
{
"command": ["code", "--goto", "{path}"],
"label": "Open in Terminal"
"command": ["open", "-a", "Terminal", "{dir}"],
"label": "Open dir in Terminal.app"
}
]
}
```

Each entry has a `label` (shown in QuickPick), a `command` array (`[bin, ...args]`), and an optional `env` object. Both `command` and `env` values support template variables (`{name}`, `{branch}`, `{ref}`, `{head}`, `{path}` — workspace items also have `{dir}` and `{worktree}`).
#### Known Limitation

Terminal mode (`"mode": "terminal"`) uses the `shell-quote` library for POSIX-style shell quoting. This may not work correctly in PowerShell terminals (Windows, macOS, or Linux) for commands with arguments containing spaces or special characters. If your VS Code terminal profile uses PowerShell, prefer `"mode": "spawn"` for such commands.

### Template Customization

Expand Down
45 changes: 44 additions & 1 deletion docs/spec/custom-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Each entry in the array:
| `label` | `string` | Yes | Display text in context menu and QuickPick |
| `command` | `string[]` | Yes | Command as `[bin, ...args]` — supports template variables |
| `env` | `Record<string, string>` | No | Environment variables — values support template variables |
| `mode` | `"spawn" \| "terminal"` | No | Execution mode — `"spawn"` (default): detached background process. `"terminal"`: VS Code integrated terminal |

## Template Variables

Expand Down Expand Up @@ -81,7 +82,7 @@ The menu items are only visible when the corresponding setting array is non-empt
3. A QuickPick appears listing all configured commands for that item type
4. Each QuickPick item shows the `label` and the rendered command as detail
5. User selects a command
6. The command is spawned detached in the background
6. The command is executed based on the entry's `mode`: spawned detached (`"spawn"`) or run in an integrated terminal (`"terminal"`)

## Execution

Expand All @@ -93,6 +94,20 @@ The menu items are only visible when the corresponding setting array is non-empt
- **Command not found**: On macOS, VS Code launched from Finder has a limited PATH. Use full binary paths or `open -a` for GUI apps.
- **Logging**: Spawned command is logged to the output channel

## Terminal Mode

When `mode` is `"terminal"`, the command runs in a VS Code integrated terminal instead of a detached background process.

- **Terminal name**: Uses the entry's `label`
- **Command**: `sendText(quote([bin, ...args]))` — shell-quoted via the `shell-quote` library
- **Environment**: Only custom `env` is passed (VS Code merges with its inherited environment automatically)
- **Lifecycle**: The terminal remains open after the command finishes — the user can interact or close it manually
- **Error handling**: try/catch around `createTerminal` + `sendText` — shows error via `showErrorMessage` and logs via `logError`

### Known Limitation

`shell-quote` produces POSIX-style quoting which may not work correctly in PowerShell terminals (on any platform). Users whose VS Code terminal profile uses PowerShell should prefer `"mode": "spawn"` for commands with arguments containing spaces or special characters.

## CWD Resolution

Same logic as Open in Terminal (see [open-in-terminal.md](open-in-terminal.md)):
Expand Down Expand Up @@ -189,6 +204,34 @@ Common terminal emulators:
}
```

### Run an interactive CLI tool in terminal

```json
{
"git-work-grove.customCommands.directory": [
{
"command": ["npm", "run", "dev"],
"label": "Run Dev Server",
"mode": "terminal"
}
]
}
```

### Start Claude Code in a worktree

```json
{
"git-work-grove.customCommands.directory": [
{
"command": ["claude"],
"label": "Claude Code",
"mode": "terminal"
}
]
}
```

### Multiple commands

```json
Expand Down
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,12 @@
"type": "string"
},
"description": "Environment variables. Values support template variables."
},
"mode": {
"type": "string",
"enum": ["spawn", "terminal"],
"default": "spawn",
"description": "Execution mode. \"spawn\" runs detached in background (default). \"terminal\" runs in VS Code integrated terminal."
}
}
},
Expand Down Expand Up @@ -484,6 +490,12 @@
"type": "string"
},
"description": "Environment variables. Values support template variables."
},
"mode": {
"type": "string",
"enum": ["spawn", "terminal"],
"default": "spawn",
"description": "Execution mode. \"spawn\" runs detached in background (default). \"terminal\" runs in VS Code integrated terminal."
}
}
},
Expand Down Expand Up @@ -523,6 +535,7 @@
"devDependencies": {
"@antfu/eslint-config": "^7.4.3",
"@changesets/cli": "^2.29.8",
"@types/shell-quote": "^1.7.5",
"@types/vscode": "^1.95.0",
"@vp-tw/eslint-config": "latest",
"@vp-tw/tsconfig": "latest",
Expand All @@ -534,5 +547,8 @@
"ovsx": "^0.10.9",
"typescript": "latest",
"vitest": "latest"
},
"dependencies": {
"shell-quote": "^1.8.3"
}
}
18 changes: 18 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading