pi-direnv 0.1.0

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.
Files changed (3) hide show
  1. package/README.md +25 -0
  2. package/index.ts +90 -0
  3. package/package.json +37 -0
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # pi-direnv
2
+
3
+ Auto-load [direnv](https://direnv.net/) environment at session start. Ensures bash commands have project-specific env vars from `.envrc` files.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pi install npm:pi-direnv
9
+ ```
10
+
11
+ ## What it does
12
+
13
+ On `session_start`:
14
+
15
+ 1. Checks if `direnv` is installed (skips silently if not)
16
+ 2. Searches for `.envrc` from cwd up to the git root
17
+ 3. Runs `direnv export json` and applies variables to `process.env`
18
+ 4. Shows a notification with the count of loaded vars
19
+ 5. Warns if `.envrc` is blocked (needs `direnv allow`)
20
+
21
+ ## Why
22
+
23
+ Nix flakes + direnv is a common pattern. Without this extension, `pi exec` commands miss project-specific PATH entries, env vars, and tool versions defined in your flake's `devShell`.
24
+
25
+ Inspired by [simonwjackson/opencode-direnv](https://github.com/simonwjackson/opencode-direnv).
package/index.ts ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * pi-direnv Extension
3
+ *
4
+ * Automatically loads direnv environment variables at session start.
5
+ * Ensures bash commands have access to project-specific env vars
6
+ * defined in .envrc files (commonly used with Nix flakes).
7
+ *
8
+ * Behavior:
9
+ * - Searches for .envrc from cwd up to git root
10
+ * - Runs `direnv export json` and applies vars to process.env
11
+ * - Shows notification if .envrc is blocked or env loaded
12
+ * - Silently skips if direnv missing or no .envrc exists
13
+ *
14
+ * Inspired by https://github.com/simonwjackson/opencode-direnv
15
+ */
16
+
17
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
18
+ import { existsSync } from "node:fs";
19
+ import { dirname, join } from "node:path";
20
+
21
+ export default function (pi: ExtensionAPI) {
22
+ pi.on("session_start", async (_event, ctx) => {
23
+ const cwd = process.cwd();
24
+
25
+ // Check direnv is available
26
+ try {
27
+ await pi.exec("which", ["direnv"]);
28
+ } catch {
29
+ return; // direnv not installed, skip silently
30
+ }
31
+
32
+ // Find .envrc searching upward to git root
33
+ const gitRoot = await findGitRoot(pi);
34
+ const envrcPath = findEnvrc(cwd, gitRoot);
35
+ if (!envrcPath) return;
36
+
37
+ const envrcDir = dirname(envrcPath);
38
+
39
+ try {
40
+ const result = await pi.exec("direnv", ["export", "json"], { cwd: envrcDir });
41
+ const output = result.stdout?.trim();
42
+ if (!output) return;
43
+
44
+ const envVars = JSON.parse(output) as Record<string, string>;
45
+ const count = Object.keys(envVars).length;
46
+
47
+ // Apply to process.env so child processes inherit them
48
+ Object.assign(process.env, envVars);
49
+
50
+ ctx.ui.notify(`direnv: loaded ${count} env vars`, "info");
51
+ } catch (err: unknown) {
52
+ const stderr =
53
+ err && typeof err === "object" && "stderr" in err
54
+ ? String((err as { stderr: unknown }).stderr)
55
+ : "";
56
+
57
+ if (stderr.includes("is blocked")) {
58
+ ctx.ui.notify("direnv: .envrc is blocked. Run `direnv allow` to enable.", "warning");
59
+ }
60
+ // Other errors (parse failures, etc.) — skip silently
61
+ }
62
+ });
63
+ }
64
+
65
+ async function findGitRoot(pi: ExtensionAPI): Promise<string | null> {
66
+ try {
67
+ const result = await pi.exec("git", ["rev-parse", "--show-toplevel"]);
68
+ return result.stdout?.trim() || null;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ function findEnvrc(startDir: string, stopAt: string | null): string | null {
75
+ const boundary = stopAt || "/";
76
+ let current = startDir;
77
+
78
+ while (true) {
79
+ const envrcPath = join(current, ".envrc");
80
+ if (existsSync(envrcPath)) return envrcPath;
81
+
82
+ if (current === boundary || current === "/") break;
83
+
84
+ const parent = dirname(current);
85
+ if (parent === current) break; // filesystem root
86
+ current = parent;
87
+ }
88
+
89
+ return null;
90
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "pi-direnv",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Auto-load direnv environment at session start — ensures bash commands have project-specific env vars from .envrc",
6
+ "main": "./index.ts",
7
+ "keywords": [
8
+ "pi",
9
+ "pi-extension",
10
+ "pi-package",
11
+ "direnv",
12
+ "nix",
13
+ "envrc"
14
+ ],
15
+ "author": "Edmund Miller <edmundmiller@hey.com>",
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/edmundmiller/dotfiles.git",
20
+ "directory": "pi-packages/pi-direnv"
21
+ },
22
+ "files": [
23
+ "index.ts",
24
+ "README.md"
25
+ ],
26
+ "peerDependencies": {
27
+ "@mariozechner/pi-coding-agent": "*"
28
+ },
29
+ "pi": {
30
+ "extensions": [
31
+ "./index.ts"
32
+ ]
33
+ },
34
+ "scripts": {
35
+ "typecheck": "tsc --noEmit"
36
+ }
37
+ }