@xynogen/pix-core 0.4.50 → 0.4.52

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 CHANGED
@@ -39,6 +39,12 @@ Pi activates extensions per installed package via each package's `pi.extensions`
39
39
  | `pix-todo` | `todo` — durable execution checklist |
40
40
  | `pix-ask` | `ask_user` — structured TUI questionnaire |
41
41
 
42
+ **Shell integration**
43
+
44
+ | Feature | Description |
45
+ |---|---|
46
+ | `$SHELL` respect | User `!` commands run through `$SHELL` instead of Pi's default `/bin/bash`. For zsh, `.zshrc` is sourced and aliases expand correctly via `eval`. Other non-bash shells get the shell redirect without rc wrapping. |
47
+
42
48
  **Shared data + behaviour**
43
49
 
44
50
  | Package | Description |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xynogen/pix-core",
3
- "version": "0.4.50",
4
- "description": "Pi extension bundle — installs and activates all core pix-* extensions",
3
+ "version": "0.4.52",
4
+ "description": "Pi extension bundle \u2014 installs and activates all core pix-* extensions",
5
5
  "type": "module",
6
6
  "main": "src/extension.ts",
7
7
  "pi": {
@@ -38,31 +38,31 @@
38
38
  "access": "public"
39
39
  },
40
40
  "dependencies": {
41
- "@xynogen/pix-runtime": "^0.3.1",
42
- "@xynogen/pix-data": "^0.4.0",
43
- "@xynogen/pix-pretty": "^1.8.0",
44
- "@xynogen/pix-welcome": "^0.1.17",
45
- "@xynogen/pix-footer": "^0.1.19",
46
- "@xynogen/pix-diagnostics": "^0.1.9",
41
+ "@xynogen/pix-runtime": "^0.4.0",
42
+ "@xynogen/pix-data": "^0.4.1",
43
+ "@xynogen/pix-pretty": "^1.8.1",
44
+ "@xynogen/pix-welcome": "^0.1.18",
45
+ "@xynogen/pix-footer": "^0.1.20",
46
+ "@xynogen/pix-diagnostics": "^0.1.10",
47
47
  "@xynogen/pix-display": "^0.1.13",
48
- "@xynogen/pix-prompts": "^0.3.4",
49
- "@xynogen/pix-skills": "^0.7.4",
50
- "@xynogen/pix-models": "^0.1.23",
51
- "@xynogen/pix-update": "^0.1.16",
52
- "@xynogen/pix-commands": "^0.2.4",
53
- "@xynogen/pix-nudge": "^0.1.12",
54
- "@xynogen/pix-read": "^0.1.19",
55
- "@xynogen/pix-write": "^0.1.18",
56
- "@xynogen/pix-edit": "^0.1.20",
57
- "@xynogen/pix-find": "^0.1.19",
58
- "@xynogen/pix-grep": "^0.1.19",
59
- "@xynogen/pix-ls": "^0.1.19",
60
- "@xynogen/pix-bash": "^0.1.17",
61
- "@xynogen/pix-todo": "^0.1.19",
62
- "@xynogen/pix-ask": "^0.2.12",
48
+ "@xynogen/pix-prompts": "^0.3.5",
49
+ "@xynogen/pix-skills": "^0.7.5",
50
+ "@xynogen/pix-models": "^0.1.24",
51
+ "@xynogen/pix-update": "^0.1.17",
52
+ "@xynogen/pix-commands": "^0.2.5",
53
+ "@xynogen/pix-nudge": "^0.1.13",
54
+ "@xynogen/pix-read": "^0.1.20",
55
+ "@xynogen/pix-write": "^0.1.19",
56
+ "@xynogen/pix-edit": "^0.1.21",
57
+ "@xynogen/pix-find": "^0.1.20",
58
+ "@xynogen/pix-grep": "^0.1.20",
59
+ "@xynogen/pix-ls": "^0.1.20",
60
+ "@xynogen/pix-bash": "^0.1.18",
61
+ "@xynogen/pix-todo": "^0.1.20",
62
+ "@xynogen/pix-ask": "^0.2.13",
63
63
  "@xynogen/pix-optimizer": "^1.1.20",
64
- "@xynogen/pix-gate": "^0.2.0",
65
- "@xynogen/pix-subagent": "^0.6.2"
64
+ "@xynogen/pix-gate": "^0.2.1",
65
+ "@xynogen/pix-subagent": "^0.6.3"
66
66
  },
67
67
  "peerDependencies": {
68
68
  "@earendil-works/pi-coding-agent": "*",
package/src/extension.ts CHANGED
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
15
+ import { createLocalBashOperations } from "@earendil-works/pi-coding-agent";
15
16
  import registerAsk from "@xynogen/pix-ask/src/index.ts";
16
17
  import registerBash from "@xynogen/pix-bash/src/extension.ts";
17
18
  import registerCommands from "@xynogen/pix-commands/src/extension.ts";
@@ -81,4 +82,57 @@ export default function (pi: ExtensionAPI): void {
81
82
  for (const register of MEMBERS) {
82
83
  (register as (pi: ExtensionAPI) => void)(pi);
83
84
  }
85
+
86
+ // ── Respect $SHELL for user ! commands ──────────────────────────────────
87
+ // Pi defaults to /bin/bash for ! commands. When the user's login shell
88
+ // differs (zsh, fish, nushell, …), redirect ! execution through $SHELL
89
+ // in interactive mode so aliases, functions, and rc config all work.
90
+ const userShell = process.env.SHELL;
91
+ if (userShell && !/\bbash$/.test(userShell)) {
92
+ const ops = createInteractiveShellOps(userShell);
93
+ pi.on("user_bash", () => ({ operations: ops }));
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Build BashOperations that run commands through the user's real shell with
99
+ * rc files loaded and alias expansion enabled.
100
+ *
101
+ * Problem: `zsh -c "gss"` is non-interactive — it skips `.zshrc` and disables
102
+ * alias expansion. Even `zsh -c ". ~/.zshrc; gss"` fails because aliases
103
+ * are resolved at parse-time, before `.zshrc` is sourced.
104
+ *
105
+ * Solution: source the rc file, then `eval` the command. `eval` creates a
106
+ * new parse boundary, so aliases defined by the source are available.
107
+ */
108
+ function createInteractiveShellOps(
109
+ shellPath: string,
110
+ ): import("@earendil-works/pi-coding-agent").BashOperations {
111
+ const inner = createLocalBashOperations({ shellPath });
112
+ const name = shellPath.split("/").pop() ?? "";
113
+ const home = process.env.HOME ?? "~";
114
+
115
+ return {
116
+ exec(command, cwd, options) {
117
+ return inner.exec(wrapForShell(name, home, command), cwd, options);
118
+ },
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Wrap a command so it runs inside the user's shell with rc and alias support.
124
+ */
125
+ function wrapForShell(shell: string, home: string, command: string): string {
126
+ if (shell === "zsh") {
127
+ // 1. source .zshrc for aliases/functions/PATH
128
+ // 2. eval to create a fresh parse boundary so aliases expand
129
+ return `. ${home}/.zshrc 2>/dev/null; eval ${escapeForShell(command)}`;
130
+ }
131
+ // Other shells: run as-is, rely on shellPath alone
132
+ return command;
133
+ }
134
+
135
+ /** Single-quote a string for safe shell embedding. */
136
+ function escapeForShell(s: string): string {
137
+ return `'${s.replace(/'/g, "'\\''")}'`;
84
138
  }