@carter-mcalister/pi-mise-toolchain 0.6.2
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/README.md +182 -0
- package/package.json +76 -0
- package/src/blockers/index.ts +23 -0
- package/src/blockers/package-manager.ts +62 -0
- package/src/blockers/python.ts +61 -0
- package/src/commands/settings-command.ts +72 -0
- package/src/config.ts +325 -0
- package/src/hooks/bash-integration.ts +35 -0
- package/src/hooks/rewrite-notifications.ts +42 -0
- package/src/hooks/session-start.ts +10 -0
- package/src/index.ts +47 -0
- package/src/project-config.ts +98 -0
- package/src/rewriters/git-rebase.ts +75 -0
- package/src/rewriters/index.ts +49 -0
- package/src/rewriters/package-manager.ts +152 -0
- package/src/rewriters/python.ts +130 -0
- package/src/rewriters/types.ts +12 -0
- package/src/toolchain.test.ts +322 -0
- package/src/utils/bash-composition.ts +28 -0
- package/src/utils/bash-source-mode.ts +5 -0
- package/src/utils/migration.ts +100 -0
- package/src/utils/shell-utils.ts +245 -0
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# `@carter-mcalister/pi-mise-toolchain`
|
|
2
|
+
|
|
3
|
+
Opinionated, mise-driven toolchain enforcement for Pi. Transparently rewrites commands to use preferred tools instead of blocking and forcing retries.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pi install npm:@carter-mcalister/pi-mise-toolchain
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install directly from this monorepo during local development:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pi install /absolute/path/to/pi-packages/packages/pi-mise-toolchain
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
### Rewriters (transparent, via spawn hook)
|
|
20
|
+
|
|
21
|
+
These features rewrite commands before shell execution. By default the agent does not see that the command was changed, but you can enable optional rewrite notifications in the config.
|
|
22
|
+
|
|
23
|
+
- **enforcePackageManager**: Rewrites `npm`/`yarn`/`bun` commands to the package manager derived from the nearest `mise.toml`. Also handles `npx` -> `pnpm dlx`/`bunx`. Activates only when exactly one of `bun`, `pnpm`, or `npm` is declared in `[tools]`.
|
|
24
|
+
- **rewritePython**: Rewrites `python`/`python3` to `uv run python` and `pip`/`pip3` to `uv pip`. Activates when `uv` is declared in the nearest `mise.toml` `[tools]` table.
|
|
25
|
+
- **gitRebaseEditor**: Injects `GIT_EDITOR=true` and `GIT_SEQUENCE_EDITOR=:` env vars for `git rebase` commands so they run non-interactively.
|
|
26
|
+
|
|
27
|
+
### Blockers (via tool_call hooks)
|
|
28
|
+
|
|
29
|
+
Blockers are still used when a rewrite is not safe to apply automatically.
|
|
30
|
+
|
|
31
|
+
- **python confirm** (part of `rewritePython`): When python/pip is used outside a uv project (no `pyproject.toml`), shows a confirmation dialog. Also blocks `poetry`/`pyenv`/`virtualenv` unconditionally.
|
|
32
|
+
|
|
33
|
+
Dangerous command presets such as `brew` and Docker secret blocking now live in `@aliou/pi-guardrails`.
|
|
34
|
+
|
|
35
|
+
## Settings Command
|
|
36
|
+
|
|
37
|
+
Run `/toolchain:settings` to open an interactive settings UI with two tabs:
|
|
38
|
+
|
|
39
|
+
- **Global**: edit global extension config (`~/.pi/agent/extensions/toolchain.json`)
|
|
40
|
+
- **Memory**: edit session-only overrides (not persisted)
|
|
41
|
+
|
|
42
|
+
Use `Tab` / `Shift+Tab` to switch tabs. The settings UI controls extension-owned settings such as `gitRebaseEditor` and `bash.sourceMode`.
|
|
43
|
+
|
|
44
|
+
Package-manager and Python rewrites are derived from the nearest `mise.toml` and are not editable in the settings UI.
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
Configuration is split by responsibility:
|
|
49
|
+
|
|
50
|
+
- **Global JSON config**: `~/.pi/agent/extensions/toolchain.json`
|
|
51
|
+
- **Memory config**: session-only overrides via `/toolchain:settings`
|
|
52
|
+
- **Project toolchain config**: nearest `mise.toml`
|
|
53
|
+
|
|
54
|
+
### Global JSON Configuration Schema
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"enabled": true,
|
|
59
|
+
"features": {
|
|
60
|
+
"gitRebaseEditor": "rewrite"
|
|
61
|
+
},
|
|
62
|
+
"bash": {
|
|
63
|
+
"sourceMode": "override-bash"
|
|
64
|
+
},
|
|
65
|
+
"ui": {
|
|
66
|
+
"showRewriteNotifications": false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
All fields are optional. Missing fields use the defaults shown above.
|
|
72
|
+
|
|
73
|
+
### Project `mise.toml` Rules
|
|
74
|
+
|
|
75
|
+
The nearest `mise.toml` controls project-level rewrites:
|
|
76
|
+
|
|
77
|
+
- If `[tools]` declares `uv`, Python rewrites are enabled.
|
|
78
|
+
- If `[tools]` declares exactly one of `bun`, `pnpm`, or `npm`, package-manager rewrites are enabled and that manager is selected.
|
|
79
|
+
- If `[tools]` declares zero or multiple supported package managers, package-manager rewrites are disabled.
|
|
80
|
+
|
|
81
|
+
### Defaults
|
|
82
|
+
|
|
83
|
+
| Setting | Default | Description |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| `features.gitRebaseEditor` | `"rewrite"` | On by default. Injects non-interactive env vars for git rebase. |
|
|
86
|
+
| `bash.sourceMode` | `"override-bash"` | Select how rewrite hooks reach bash at runtime. Only matters when at least one rewrite is active. |
|
|
87
|
+
| `ui.showRewriteNotifications` | `false` | Show a visible Pi notification each time a rewrite happens. |
|
|
88
|
+
| Python rewrites | derived from `mise.toml` | Enabled when `uv` is declared in `[tools]`. |
|
|
89
|
+
| Package-manager rewrites | derived from `mise.toml` | Enabled only when exactly one of `bun`, `pnpm`, or `npm` is declared in `[tools]`. |
|
|
90
|
+
|
|
91
|
+
### Examples
|
|
92
|
+
|
|
93
|
+
Use composed bash integration with an external bash composer:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"bash": {
|
|
98
|
+
"sourceMode": "composed-bash"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Enable rewrite notifications in global config:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"ui": {
|
|
108
|
+
"showRewriteNotifications": true
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Enable project-level rewrites through `mise.toml`:
|
|
114
|
+
|
|
115
|
+
```toml
|
|
116
|
+
[tools]
|
|
117
|
+
uv = "latest"
|
|
118
|
+
bun = "1.3.12"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## How It Works
|
|
122
|
+
|
|
123
|
+
### Rewriters vs Blockers
|
|
124
|
+
|
|
125
|
+
The extension uses two Pi mechanisms:
|
|
126
|
+
|
|
127
|
+
1. **Spawn hook** (`createBashTool` with `spawnHook`): rewrites commands before shell execution. The agent sees the original command in the tool call UI but gets the output of the rewritten command.
|
|
128
|
+
2. **tool_call event hooks**: block commands entirely. The agent sees a block reason and retries with the correct command. Used for commands that have no safe rewrite target.
|
|
129
|
+
3. **Optional rewrite notifications**: when `ui.showRewriteNotifications` is enabled, a warning-level Pi notification is shown before the rewritten command runs. The message is prefixed with `[override-bash]` or `[composed-bash]` for debug clarity.
|
|
130
|
+
|
|
131
|
+
### Execution Order
|
|
132
|
+
|
|
133
|
+
1. Guardrails `tool_call` hooks run first (permission gate, env protection)
|
|
134
|
+
2. Toolchain `tool_call` hooks run (blockers, optional rewrite notifications)
|
|
135
|
+
3. If not blocked and at least one feature is in `rewrite` mode, runtime routing depends on `bash.sourceMode`:
|
|
136
|
+
- `override-bash`: toolchain registers its own bash tool with spawn hook
|
|
137
|
+
- `composed-bash`: toolchain waits for an external composer to request and compose its spawn hook
|
|
138
|
+
4. Shell executes the rewritten command
|
|
139
|
+
|
|
140
|
+
### AST-Based Rewriting
|
|
141
|
+
|
|
142
|
+
All rewriters use structural shell parsing via `@aliou/sh` to identify command names in the AST. This avoids false positives where tool names appear in URLs, file paths, or strings. If the parser fails, the command passes through unchanged — a missed rewrite is safe, a false positive rewrite corrupts the command.
|
|
143
|
+
|
|
144
|
+
## Bash Source Mode
|
|
145
|
+
|
|
146
|
+
`bash.sourceMode` controls how rewrite hooks attach to bash at runtime.
|
|
147
|
+
|
|
148
|
+
- `override-bash` (default): toolchain registers bash when rewrite is active.
|
|
149
|
+
- `composed-bash`: toolchain contributes its rewrite hook to an external bash composer.
|
|
150
|
+
|
|
151
|
+
Important:
|
|
152
|
+
|
|
153
|
+
- Source mode matters only when at least one feature is set to `"rewrite"`.
|
|
154
|
+
- Features set to `"block"` are unaffected.
|
|
155
|
+
- In `override-bash` mode, Pi core still uses first-wins tool registration. If another extension earlier in load order already registered `bash`, toolchain cannot replace it.
|
|
156
|
+
- In `composed-bash` mode, rewrites run only if an external composer emits `ad:bash:spawn-hook:request` and collects contributors.
|
|
157
|
+
- If no composer exists, rewrites will not run in `composed-bash` mode by design.
|
|
158
|
+
|
|
159
|
+
## Migration from Guardrails
|
|
160
|
+
|
|
161
|
+
If you were using `preventBrew`, `preventPython`, or `enforcePackageManager` in your guardrails config:
|
|
162
|
+
|
|
163
|
+
1. Install `@carter-mcalister/pi-mise-toolchain`
|
|
164
|
+
2. Add the project tool choices to `mise.toml` (`uv` for Python, exactly one of `bun` / `pnpm` / `npm` for package-manager rewrites)
|
|
165
|
+
3. Optionally configure extension-owned settings in `~/.pi/agent/extensions/toolchain.json`
|
|
166
|
+
4. Remove the deprecated features from your guardrails config
|
|
167
|
+
|
|
168
|
+
The guardrails extension will continue to honor these features with a deprecation warning until they are removed in a future version.
|
|
169
|
+
|
|
170
|
+
## Credits
|
|
171
|
+
|
|
172
|
+
This package is a maintained fork of [`@aliou/pi-toolchain`](https://github.com/aliou/pi-toolchain).
|
|
173
|
+
|
|
174
|
+
Credit to Aliou for the original project, design, and implementation this fork builds on.
|
|
175
|
+
|
|
176
|
+
## Development
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
mise install
|
|
180
|
+
bun install
|
|
181
|
+
mise run check
|
|
182
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@carter-mcalister/pi-mise-toolchain",
|
|
3
|
+
"version": "0.6.2",
|
|
4
|
+
"description": "Mise-driven toolchain enforcement for Pi",
|
|
5
|
+
"author": "Carter McAlister",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/CarterMcAlister/pi-packages.git",
|
|
9
|
+
"directory": "packages/pi-mise-toolchain"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/CarterMcAlister/pi-packages/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/CarterMcAlister/pi-packages/tree/main/packages/pi-mise-toolchain#readme",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pi-package",
|
|
17
|
+
"pi-extension",
|
|
18
|
+
"pi",
|
|
19
|
+
"toolchain",
|
|
20
|
+
"mise",
|
|
21
|
+
"bun"
|
|
22
|
+
],
|
|
23
|
+
"pi": {
|
|
24
|
+
"extensions": [
|
|
25
|
+
"./src/index.ts"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"src",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"type": "module",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"private": false,
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@aliou/pi-utils-settings": "^0.13.0",
|
|
40
|
+
"@aliou/sh": "^0.1.0",
|
|
41
|
+
"@iarna/toml": "^2.2.5"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@mariozechner/pi-coding-agent": "0.61.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@biomejs/biome": "^2.3.13",
|
|
48
|
+
"@changesets/cli": "^2.27.11",
|
|
49
|
+
"@mariozechner/pi-coding-agent": "0.61.0",
|
|
50
|
+
"@mariozechner/pi-tui": "0.61.0",
|
|
51
|
+
"@sinclair/typebox": "^0.34.48",
|
|
52
|
+
"@types/node": "^25.0.10",
|
|
53
|
+
"bun-types": "latest",
|
|
54
|
+
"typescript": "^5.9.3"
|
|
55
|
+
},
|
|
56
|
+
"overrides": {
|
|
57
|
+
"@mariozechner/pi-ai": "$@mariozechner/pi-coding-agent",
|
|
58
|
+
"@mariozechner/pi-tui": "$@mariozechner/pi-coding-agent"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"format": "bunx @biomejs/biome check --write .",
|
|
62
|
+
"lint": "bunx @biomejs/biome check .",
|
|
63
|
+
"typecheck": "bunx tsc -p tsconfig.json --noEmit",
|
|
64
|
+
"test": "bun test",
|
|
65
|
+
"check": "bun run lint && bun run typecheck && bun run test",
|
|
66
|
+
"check:lockfile": "bun install --frozen-lockfile",
|
|
67
|
+
"changeset": "bunx changeset",
|
|
68
|
+
"version": "bunx changeset version",
|
|
69
|
+
"release": "bunx changeset publish"
|
|
70
|
+
},
|
|
71
|
+
"peerDependenciesMeta": {
|
|
72
|
+
"@mariozechner/pi-coding-agent": {
|
|
73
|
+
"optional": true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets up all blocking hooks (tool_call event handlers).
|
|
3
|
+
*
|
|
4
|
+
* Blockers run before the spawn hook. A blocked command never reaches
|
|
5
|
+
* the rewriters. Only features with mode "block" register a blocker.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionAPI } from '@mariozechner/pi-coding-agent'
|
|
9
|
+
import type { ResolvedToolchainConfig } from '../config'
|
|
10
|
+
import { setupPackageManagerBlocker } from './package-manager'
|
|
11
|
+
import { setupPythonBlocker } from './python'
|
|
12
|
+
|
|
13
|
+
export function setupBlockers(
|
|
14
|
+
pi: ExtensionAPI,
|
|
15
|
+
config: ResolvedToolchainConfig,
|
|
16
|
+
): void {
|
|
17
|
+
if (config.features.enforcePackageManager === 'block') {
|
|
18
|
+
setupPackageManagerBlocker(pi, config)
|
|
19
|
+
}
|
|
20
|
+
if (config.features.rewritePython === 'block') {
|
|
21
|
+
setupPythonBlocker(pi)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package manager blocker.
|
|
3
|
+
*
|
|
4
|
+
* Used when enforcePackageManager is set to "block" instead of "rewrite".
|
|
5
|
+
* Blocks commands using a non-selected package manager and tells the model
|
|
6
|
+
* to use the configured one instead.
|
|
7
|
+
*
|
|
8
|
+
* Uses AST-based matching. Falls back to regex on parse failure.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { parse } from '@aliou/sh'
|
|
12
|
+
import type { ExtensionAPI } from '@mariozechner/pi-coding-agent'
|
|
13
|
+
import type { ResolvedToolchainConfig } from '../config'
|
|
14
|
+
import { walkCommands, wordToString } from '../utils/shell-utils'
|
|
15
|
+
|
|
16
|
+
const ALL_MANAGERS = new Set(['bun', 'pnpm', 'npm', 'npx', 'yarn'])
|
|
17
|
+
|
|
18
|
+
export function setupPackageManagerBlocker(
|
|
19
|
+
pi: ExtensionAPI,
|
|
20
|
+
config: ResolvedToolchainConfig,
|
|
21
|
+
): void {
|
|
22
|
+
const selected = config.packageManager.selected
|
|
23
|
+
|
|
24
|
+
pi.on('tool_call', async (event, ctx) => {
|
|
25
|
+
if (event.toolName !== 'bash') return
|
|
26
|
+
|
|
27
|
+
const command = String(event.input.command ?? '')
|
|
28
|
+
|
|
29
|
+
let detected: string | null = null
|
|
30
|
+
try {
|
|
31
|
+
const { ast } = parse(command)
|
|
32
|
+
walkCommands(ast, (cmd) => {
|
|
33
|
+
const name = cmd.words?.[0] ? wordToString(cmd.words[0]) : undefined
|
|
34
|
+
if (name && ALL_MANAGERS.has(name) && name !== selected) {
|
|
35
|
+
detected = name
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
return false
|
|
39
|
+
})
|
|
40
|
+
} catch {
|
|
41
|
+
for (const mgr of ALL_MANAGERS) {
|
|
42
|
+
if (mgr !== selected && new RegExp(`\\b${mgr}\\b`).test(command)) {
|
|
43
|
+
detected = mgr
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!detected) return
|
|
50
|
+
|
|
51
|
+
ctx.ui.notify(
|
|
52
|
+
`Blocked ${detected} command. Use ${selected} instead.`,
|
|
53
|
+
'warning',
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const reason =
|
|
57
|
+
`This project uses ${selected} as the package manager. ` +
|
|
58
|
+
`Replace \`${detected}\` with \`${selected}\` (or \`${selected} dlx\` for npx-style commands).`
|
|
59
|
+
|
|
60
|
+
return { block: true, reason }
|
|
61
|
+
})
|
|
62
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python command blocker.
|
|
3
|
+
*
|
|
4
|
+
* Used when rewritePython is set to "block" instead of "rewrite".
|
|
5
|
+
* Blocks python/pip/poetry/pyenv/virtualenv commands and tells the model
|
|
6
|
+
* to use uv instead.
|
|
7
|
+
*
|
|
8
|
+
* Uses AST-based matching. Falls back to regex on parse failure.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { parse } from '@aliou/sh'
|
|
12
|
+
import type { ExtensionAPI } from '@mariozechner/pi-coding-agent'
|
|
13
|
+
import { walkCommands, wordToString } from '../utils/shell-utils'
|
|
14
|
+
|
|
15
|
+
const BLOCKED_COMMANDS = new Set([
|
|
16
|
+
'python',
|
|
17
|
+
'python3',
|
|
18
|
+
'pip',
|
|
19
|
+
'pip3',
|
|
20
|
+
'poetry',
|
|
21
|
+
'pyenv',
|
|
22
|
+
'virtualenv',
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
const FALLBACK_PATTERN = /\b(python|python3|pip|pip3|poetry|pyenv|virtualenv)\b/
|
|
26
|
+
|
|
27
|
+
export function setupPythonBlocker(pi: ExtensionAPI): void {
|
|
28
|
+
pi.on('tool_call', async (event, ctx) => {
|
|
29
|
+
if (event.toolName !== 'bash') return
|
|
30
|
+
|
|
31
|
+
const command = String(event.input.command ?? '')
|
|
32
|
+
|
|
33
|
+
let detected: string | null = null
|
|
34
|
+
try {
|
|
35
|
+
const { ast } = parse(command)
|
|
36
|
+
walkCommands(ast, (cmd) => {
|
|
37
|
+
const name = cmd.words?.[0] ? wordToString(cmd.words[0]) : undefined
|
|
38
|
+
if (name && BLOCKED_COMMANDS.has(name)) {
|
|
39
|
+
detected = name
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
return false
|
|
43
|
+
})
|
|
44
|
+
} catch {
|
|
45
|
+
const match = FALLBACK_PATTERN.exec(command)
|
|
46
|
+
if (match?.[1]) detected = match[1]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!detected) return
|
|
50
|
+
|
|
51
|
+
ctx.ui.notify(`Blocked ${detected} command. Use uv instead.`, 'warning')
|
|
52
|
+
|
|
53
|
+
const reason =
|
|
54
|
+
'Use uv for Python package management instead. ' +
|
|
55
|
+
'Run `uv init` to create a new Python project, ' +
|
|
56
|
+
'`uv run python` to run Python scripts, ' +
|
|
57
|
+
'or `uv add` to install packages (replaces pip/poetry).'
|
|
58
|
+
|
|
59
|
+
return { block: true, reason }
|
|
60
|
+
})
|
|
61
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
registerSettingsCommand,
|
|
3
|
+
type SettingsSection,
|
|
4
|
+
} from '@aliou/pi-utils-settings'
|
|
5
|
+
import type { ExtensionAPI } from '@mariozechner/pi-coding-agent'
|
|
6
|
+
import type {
|
|
7
|
+
BashSourceMode,
|
|
8
|
+
FeatureMode,
|
|
9
|
+
ResolvedExtensionConfig,
|
|
10
|
+
ToolchainConfig,
|
|
11
|
+
} from '../config'
|
|
12
|
+
import { configLoader } from '../config'
|
|
13
|
+
|
|
14
|
+
type FeatureKey = keyof ResolvedExtensionConfig['features']
|
|
15
|
+
|
|
16
|
+
const FEATURE_UI: Record<
|
|
17
|
+
FeatureKey,
|
|
18
|
+
{ label: string; description: string; modes: FeatureMode[] }
|
|
19
|
+
> = {
|
|
20
|
+
gitRebaseEditor: {
|
|
21
|
+
label: 'Git rebase editor',
|
|
22
|
+
description:
|
|
23
|
+
'Inject GIT_EDITOR and GIT_SEQUENCE_EDITOR for non-interactive rebase (rewrite only)',
|
|
24
|
+
modes: ['disabled', 'rewrite'],
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const BASH_SOURCE_MODES: BashSourceMode[] = ['override-bash', 'composed-bash']
|
|
29
|
+
|
|
30
|
+
export function registerToolchainSettings(pi: ExtensionAPI): void {
|
|
31
|
+
registerSettingsCommand<ToolchainConfig, ResolvedExtensionConfig>(pi, {
|
|
32
|
+
commandName: 'toolchain:settings',
|
|
33
|
+
title: 'Toolchain Settings',
|
|
34
|
+
configStore: configLoader,
|
|
35
|
+
buildSections: (
|
|
36
|
+
tabConfig: ToolchainConfig | null,
|
|
37
|
+
resolved: ResolvedExtensionConfig,
|
|
38
|
+
_ctx,
|
|
39
|
+
): SettingsSection[] => {
|
|
40
|
+
const featureItems = (Object.keys(FEATURE_UI) as FeatureKey[]).map(
|
|
41
|
+
(key) => ({
|
|
42
|
+
id: `features.${key}`,
|
|
43
|
+
label: FEATURE_UI[key].label,
|
|
44
|
+
description: FEATURE_UI[key].description,
|
|
45
|
+
currentValue: tabConfig?.features?.[key] ?? resolved.features[key],
|
|
46
|
+
values: FEATURE_UI[key].modes,
|
|
47
|
+
}),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
label: 'Features',
|
|
53
|
+
items: featureItems,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: 'Bash Integration',
|
|
57
|
+
items: [
|
|
58
|
+
{
|
|
59
|
+
id: 'bash.sourceMode',
|
|
60
|
+
label: 'Source mode',
|
|
61
|
+
description:
|
|
62
|
+
'override-bash: toolchain registers bash when rewrite is active. composed-bash: toolchain contributes rewrite hook to external bash composer.',
|
|
63
|
+
currentValue:
|
|
64
|
+
tabConfig?.bash?.sourceMode ?? resolved.bash.sourceMode,
|
|
65
|
+
values: [...BASH_SOURCE_MODES],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
}
|