pi-pwsh-native 0.1.1 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to this project will be documented here.
4
4
 
5
+ ## 0.1.3 — 2026-08-01
6
+
7
+ ### Fixed
8
+
9
+ - Respect the final PowerShell command's success state instead of allowing a stale native `$LASTEXITCODE` to override it.
10
+
11
+ ### Changed
12
+
13
+ - Reduced recurring model prompt overhead by shortening the shell description and consolidating PowerShell guidance.
14
+ - Deferred PowerShell runtime probing until a session starts, avoiding unnecessary subprocesses in Pi invocations that only load extensions.
15
+ - Declared the supported Pi and Node.js version ranges explicitly.
16
+ - Added minimum/latest Pi compatibility coverage and prompt-budget regression tests.
17
+
18
+ ## 0.1.2 — 2026-07-30
19
+
20
+ ### Added
21
+
22
+ - `pi` npm keyword for broader package-gallery discoverability.
23
+ - README comparison with the audited `@4fu/pi-pwsh@0.6.2` baseline.
24
+
5
25
  ## 0.1.1 — 2026-07-30
6
26
 
7
27
  ### Added
package/README.md CHANGED
@@ -7,6 +7,29 @@ Native PowerShell 7 tooling for the [Pi coding agent](https://pi.dev/) on Window
7
7
 
8
8
  `pi-pwsh-native` replaces Pi's model-callable `bash` tool with a tool named `pwsh` and routes interactive `!`/`!!` commands through the same verified PowerShell executable. It does not translate Bash syntax and does not silently fall back to another shell.
9
9
 
10
+ ![PowerShell 7 preview](https://raw.githubusercontent.com/takomine/pi-pwsh-native/main/public/img.png)
11
+
12
+ ## How this differs from `@4fu/pi-pwsh`
13
+
14
+ This package derives from [`@4fu/pi-pwsh`](https://github.com/4fuu/pi-pwsh), but it is a deliberately leaner shell replacement rather than a drop-in feature superset. The comparison below is against the audited upstream baseline, `@4fu/pi-pwsh@0.6.2`.
15
+
16
+ | Area | `pi-pwsh-native` | `@4fu/pi-pwsh@0.6.2` |
17
+ |---|---|---|
18
+ | Primary focus | Predictable, dependency-free foreground PowerShell execution | Feature-rich PowerShell execution with persistent jobs and interactive sessions |
19
+ | Model shell tool | Replaces `bash` with `pwsh` | Replaces `bash` with `pwsh` |
20
+ | Pi discovery tools | Preserves `ls`, `find`, and `grep` by default | Disables `ls`, `find`, and `grep` in favor of PowerShell |
21
+ | Interactive `!` / `!!` | Routes both through the verified PowerShell runtime | Does not replace Pi's `user_bash` handling |
22
+ | PowerShell executable | Resolves, verifies, and retains an absolute PowerShell 7 path | Probes PowerShell 7, while foreground execution invokes `pwsh` by name |
23
+ | Execution policy | No override by default; `Bypass` must be explicitly configured | Passes `-ExecutionPolicy Bypass` |
24
+ | Command transport | UTF-8 base64 source over stdin, avoiding Windows command-line limits | Injected source passed through `-Command` |
25
+ | `.cmd` fallback | No retry through `cmd.exe` | Can retry failed direct command-shim execution through `cmd /c` |
26
+ | Background jobs and PTYs | Intentionally omitted from the lean core | Includes detached jobs, ConPTY sessions, and user-request helpers |
27
+ | Runtime dependencies | None | Uses `node-pty` and `@xterm/headless` |
28
+ | Configuration | Strict JSON configuration plus environment overrides | Primarily opinionated built-in behavior |
29
+ | Missing/invalid PowerShell | Fails visibly without silently restoring Bash | Leaves the built-in Bash tool active when PowerShell is unavailable |
30
+
31
+ Choose `pi-pwsh-native` when you want a small, auditable PowerShell 7 replacement with dedicated Pi file/search tools left intact. Choose `@4fu/pi-pwsh` when persistent background jobs, PTYs, and its interactive helper layer are more important. Do not enable competing shell-adapter extensions simultaneously.
32
+
10
33
  ## Design
11
34
 
12
35
  - Registers a real `pwsh` tool so models are prompted to write PowerShell, not POSIX shell syntax.
@@ -24,7 +47,8 @@ Native PowerShell 7 tooling for the [Pi coding agent](https://pi.dev/) on Window
24
47
 
25
48
  - Windows
26
49
  - PowerShell 7+
27
- - Pi with extension support
50
+ - Node.js 22.19 or newer
51
+ - Pi 0.83.0 or newer (before 1.0)
28
52
 
29
53
  The initial implementation is developed and tested against:
30
54
 
@@ -36,7 +60,7 @@ The initial implementation is developed and tested against:
36
60
  Install the pinned npm release:
37
61
 
38
62
  ```powershell
39
- pi install npm:pi-pwsh-native@0.1.1
63
+ pi install npm:pi-pwsh-native@0.1.3
40
64
  ```
41
65
 
42
66
  Alternatively, install the public GitHub package directly:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-pwsh-native",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Native PowerShell 7 tooling for the Pi coding agent on Windows",
5
5
  "license": "MIT",
6
6
  "author": "Tako (https://github.com/takomine)",
@@ -20,10 +20,11 @@
20
20
  "win32"
21
21
  ],
22
22
  "engines": {
23
- "node": ">=20"
23
+ "node": ">=22.19.0"
24
24
  },
25
25
  "keywords": [
26
26
  "pi-package",
27
+ "pi",
27
28
  "pi-extension",
28
29
  "powershell",
29
30
  "pwsh",
@@ -54,7 +55,7 @@
54
55
  "smoke": "npm run test:runtime && npm run test:exitcode && npm run test:timeout"
55
56
  },
56
57
  "peerDependencies": {
57
- "@earendil-works/pi-coding-agent": "*"
58
+ "@earendil-works/pi-coding-agent": ">=0.83.0 <1.0.0"
58
59
  },
59
60
  "devDependencies": {
60
61
  "@earendil-works/pi-coding-agent": "^0.83.0",
package/src/index.ts CHANGED
@@ -27,11 +27,17 @@ export function selectActiveTools(
27
27
  return [...new Set(selected)];
28
28
  }
29
29
 
30
- function toolDescription(runtime: PowerShellRuntime, config: PwshNativeConfig): string {
31
- const elevation = config.elevationGuidance
32
- ? " Elevation guidance is enabled, but elevate only after an explicit user request."
33
- : " Do not elevate unless the user explicitly requests it.";
34
- return `Execute a native PowerShell 7 command on Windows with PowerShell ${runtime.version} (${runtime.executable}). Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB, whichever is reached first; full truncated output is saved to a temporary file. Commands have no default timeout; set timeout in seconds when needed.${elevation}`;
30
+ export const PWSH_TOOL_DESCRIPTION =
31
+ "Execute native PowerShell 7 on Windows. Output is tail-truncated to 2,000 lines or 50 KB; full output is saved to a temporary file. Optional timeout is in seconds.";
32
+
33
+ export function createPromptGuidelines(config: PwshNativeConfig): string[] {
34
+ return [
35
+ "Use pwsh only when a shell is needed; use native PowerShell syntax and $env:NAME for environment variables.",
36
+ "Prefer dedicated file and search tools over pwsh; otherwise bound recursive output and prefer rg or fd when available.",
37
+ config.elevationGuidance
38
+ ? "In pwsh, elevation guidance is enabled, but elevate only after an explicit user request."
39
+ : "Do not elevate from pwsh unless the user explicitly requests it.",
40
+ ];
35
41
  }
36
42
 
37
43
  function unavailableUserCommand(reason: string) {
@@ -55,51 +61,51 @@ export default function piPwshNative(pi: ExtensionAPI): void {
55
61
 
56
62
  let config: PwshNativeConfig | undefined;
57
63
  let runtime: PowerShellRuntime | undefined;
64
+ let operations: ReturnType<typeof createPowerShellOperations> | undefined;
58
65
  let setupError: string | undefined;
59
66
  try {
60
67
  config = loadConfig({ agentDir: getAgentDir() }).config;
61
- runtime = resolvePowerShellRuntime(config);
62
68
  } catch (error) {
63
69
  setupError = errorMessage(error);
64
70
  }
65
71
 
66
- if (setupError || !config || !runtime) {
67
- const reason = setupError ?? "unknown setup failure";
68
- pi.on("session_start", (_event, ctx) => {
72
+ pi.on("session_start", (_event, ctx) => {
73
+ if (!setupError && config && !runtime) {
74
+ try {
75
+ runtime = resolvePowerShellRuntime(config);
76
+ operations = createPowerShellOperations(runtime, config);
77
+ } catch (error) {
78
+ setupError = errorMessage(error);
79
+ }
80
+ }
81
+
82
+ if (setupError || !config || !runtime || !operations) {
83
+ const reason = setupError ?? "unknown setup failure";
69
84
  pi.setActiveTools(selectActiveTools(pi.getActiveTools(), true, false));
70
85
  ctx.ui.notify(`pi-pwsh-native: ${reason} Bash was disabled; no shell fallback was activated.`, "error");
71
- });
72
- pi.on("user_bash", () => unavailableUserCommand(reason));
73
- return;
74
- }
75
-
76
- const resolvedConfig = config;
77
- const resolvedRuntime = runtime;
78
- const operations = createPowerShellOperations(resolvedRuntime, resolvedConfig);
86
+ return;
87
+ }
79
88
 
80
- pi.on("session_start", (_event, ctx) => {
81
89
  const definition = createBashToolDefinition(ctx.cwd, { operations });
82
90
  pi.registerTool({
83
91
  ...definition,
84
92
  name: "pwsh",
85
93
  label: "pwsh",
86
- description: toolDescription(resolvedRuntime, resolvedConfig),
87
- promptSnippet: "Execute native PowerShell 7 commands on Windows",
88
- promptGuidelines: [
89
- "Use pwsh only when a shell is needed; write native PowerShell syntax and avoid Bash-only syntax or implicit Bash translation.",
90
- "Prefer read, write, edit, ls, find, and grep over pwsh when a dedicated tool can complete the task more directly.",
91
- "In pwsh, use $env:NAME for environment variables; single quotes are literal, double quotes interpolate, and the backtick is the escape character.",
92
- "Prefer bounded rg or fd commands for repository search; tightly bound recursive Get-ChildItem and Select-String operations.",
93
- "Do not elevate pwsh commands unless the user explicitly requests elevation.",
94
- "Inspect PI_* environment variables in pwsh when current Pi model or session metadata is needed.",
95
- ],
94
+ description: PWSH_TOOL_DESCRIPTION,
95
+ promptSnippet: "Execute native PowerShell 7 commands",
96
+ promptGuidelines: createPromptGuidelines(config),
96
97
  });
97
98
  pi.setActiveTools(
98
- selectActiveTools(pi.getActiveTools(), resolvedConfig.preserveDiscoveryTools, true),
99
+ selectActiveTools(pi.getActiveTools(), config.preserveDiscoveryTools, true),
99
100
  );
100
101
  });
101
102
 
102
- if (resolvedConfig.replaceUserBash) {
103
- pi.on("user_bash", () => ({ operations }));
103
+ const replaceUserBash = config?.replaceUserBash ?? true;
104
+ if (replaceUserBash) {
105
+ pi.on("user_bash", () =>
106
+ operations
107
+ ? { operations }
108
+ : unavailableUserCommand(setupError ?? "PowerShell has not been initialized"),
109
+ );
104
110
  }
105
111
  }
@@ -10,15 +10,16 @@ export const UTF8_PREFIX = [
10
10
  ].join("; ") + "; ";
11
11
 
12
12
  /**
13
- * Preserve a native program's real exit code. If no native program set
14
- * LASTEXITCODE, map PowerShell's success state to 0/1.
13
+ * Report the final command's status. A successful final command wins over a
14
+ * stale LASTEXITCODE; a failed final native command retains its real code;
15
+ * other PowerShell failures map to 1.
15
16
  *
16
17
  * The leading newline detaches the epilogue from a trailing line comment. The
17
18
  * leading semicolon also prevents a trailing backtick from joining the
18
19
  * epilogue to the user's final command.
19
20
  */
20
21
  export const EXIT_EPILOGUE =
21
- "\n; $__pi_ok = $?; if ($null -ne $global:LASTEXITCODE) { exit $global:LASTEXITCODE } else { exit ($__pi_ok ? 0 : 1) }";
22
+ "\n; $__pi_ok = $?; if ($__pi_ok) { exit 0 } elseif ($null -ne $global:LASTEXITCODE -and $global:LASTEXITCODE -ne 0) { exit $global:LASTEXITCODE } else { exit 1 }";
22
23
 
23
24
  export function buildPowerShellSource(command: string, config: PwshNativeConfig): string {
24
25
  const strict = config.strictMode ? "$ErrorActionPreference = 'Stop'; " : "";