pi-x-ide 1.2.0 → 1.4.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.
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "pi-x-ide",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Pi extension package for IDE selection context integration.",
5
5
  "files": [
6
6
  "dist",
7
+ "schemas",
7
8
  "src"
8
9
  ],
9
10
  "keywords": [
10
11
  "pi-package",
11
12
  "pi",
12
13
  "ide",
13
- "vscode"
14
+ "vscode",
15
+ "zed"
14
16
  ],
15
17
  "license": "Apache-2.0",
16
18
  "repository": {
@@ -21,6 +23,9 @@
21
23
  "url": "https://github.com/balaenis/pi-x-ide/issues"
22
24
  },
23
25
  "homepage": "https://github.com/balaenis/pi-x-ide#readme",
26
+ "engines": {
27
+ "node": ">=26"
28
+ },
24
29
  "type": "commonjs",
25
30
  "main": "./dist/src/pi/index.js",
26
31
  "dependencies": {
@@ -32,7 +37,7 @@
32
37
  "devDependencies": {
33
38
  "@earendil-works/pi-coding-agent": "^0.79.0",
34
39
  "@eslint/js": "^10.0.1",
35
- "@types/node": "^22.10.0",
40
+ "@types/node": "^25.9.2",
36
41
  "@types/ws": "^8.5.13",
37
42
  "@vscode/vsce": "^3.2.2",
38
43
  "esbuild": "^0.27.0",
@@ -51,7 +56,9 @@
51
56
  "scripts": {
52
57
  "build": "tsc -p tsconfig.json && pnpm --filter './vscode' compile",
53
58
  "typecheck": "tsc -p tsconfig.json --noEmit && pnpm --filter './vscode' typecheck",
54
- "test": "pnpm build && node --test dist/test/*.test.js",
59
+ "generate:config-schema": "node scripts/generate-config-schema.cjs",
60
+ "check:config-schema": "node scripts/generate-config-schema.cjs --check",
61
+ "test": "pnpm check:config-schema && pnpm build && node --test dist/test/*.test.js",
55
62
  "lint": "eslint .",
56
63
  "lint:fix": "eslint . --fix",
57
64
  "format": "prettier --write .",
@@ -0,0 +1,120 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/balaenis/pi-x-ide/schemas/config.json",
4
+ "title": "Pi config.json",
5
+ "description": "Schema for Pi-side configuration read from ~/.pi/config.json.",
6
+ "type": "object",
7
+ "additionalProperties": true,
8
+ "properties": {
9
+ "env": {
10
+ "type": "object",
11
+ "description": "Pi-side environment variables. Real environment variables override these values.",
12
+ "additionalProperties": {
13
+ "type": [
14
+ "string",
15
+ "number",
16
+ "boolean"
17
+ ]
18
+ },
19
+ "properties": {
20
+ "PI_X_IDE_LOCK_DIR": {
21
+ "type": "string",
22
+ "description": "Directory containing Pi x IDE lock files. Defaults to ~/.pi/pi-x-ide/lock."
23
+ },
24
+ "PI_X_IDE_AUTO_INSTALL": {
25
+ "type": [
26
+ "string",
27
+ "number",
28
+ "boolean"
29
+ ],
30
+ "description": "Controls VS Code-family extension auto-install. Values 0, false, and off disable it."
31
+ },
32
+ "PI_X_IDE_ZED_DB": {
33
+ "type": "string",
34
+ "description": "Override path to Zed's SQLite database."
35
+ },
36
+ "TERM_PROGRAM": {
37
+ "type": "string",
38
+ "description": "Terminal program marker used to detect VS Code, Cursor, Windsurf, or Zed."
39
+ },
40
+ "VSCODE_CWD": {
41
+ "type": "string",
42
+ "description": "VS Code-family cwd marker and IDE path hint."
43
+ },
44
+ "VSCODE_PID": {
45
+ "type": [
46
+ "string",
47
+ "number"
48
+ ],
49
+ "description": "VS Code-family process marker."
50
+ },
51
+ "VSCODE_IPC_HOOK_CLI": {
52
+ "type": "string",
53
+ "description": "VS Code-family IPC marker and IDE path hint."
54
+ },
55
+ "VSCODE_GIT_IPC_HANDLE": {
56
+ "type": "string",
57
+ "description": "VS Code-family Git IPC marker and IDE path hint."
58
+ },
59
+ "ZED_TERM": {
60
+ "type": [
61
+ "string",
62
+ "boolean"
63
+ ],
64
+ "description": "Zed terminal marker. Pi x IDE detects Zed when this is true."
65
+ },
66
+ "WSL_DISTRO_NAME": {
67
+ "type": "string",
68
+ "description": "WSL distribution name used for WSL path normalization and Zed database discovery."
69
+ },
70
+ "WSL_INTEROP": {
71
+ "type": "string",
72
+ "description": "WSL interop marker used for WSL path normalization and Zed database discovery."
73
+ },
74
+ "LOCALAPPDATA": {
75
+ "type": "string",
76
+ "description": "Windows local application data directory used to find Zed's database."
77
+ },
78
+ "USERPROFILE": {
79
+ "type": "string",
80
+ "description": "Windows user profile directory used to find Zed's database when LOCALAPPDATA is unavailable."
81
+ },
82
+ "PATH": {
83
+ "type": "string",
84
+ "description": "Executable search path used to find code, cursor, and windsurf CLIs."
85
+ },
86
+ "Path": {
87
+ "type": "string",
88
+ "description": "Windows-style executable search path used to find code, cursor, and windsurf CLIs."
89
+ },
90
+ "path": {
91
+ "type": "string",
92
+ "description": "Lowercase executable search path used to find code, cursor, and windsurf CLIs."
93
+ },
94
+ "PATHEXT": {
95
+ "type": "string",
96
+ "description": "Windows executable extensions used when searching for IDE CLIs."
97
+ }
98
+ },
99
+ "patternProperties": {
100
+ "^(CURSOR|WINDSURF|CODEIUM).*": {
101
+ "type": [
102
+ "string",
103
+ "number",
104
+ "boolean"
105
+ ],
106
+ "description": "IDE-specific marker used to detect Cursor or Windsurf terminals."
107
+ }
108
+ }
109
+ }
110
+ },
111
+ "examples": [
112
+ {
113
+ "env": {
114
+ "PI_X_IDE_LOCK_DIR": "/home/user/.pi/pi-x-ide/lock",
115
+ "PI_X_IDE_AUTO_INSTALL": "0",
116
+ "PI_X_IDE_ZED_DB": "/home/user/.local/share/zed/db/0-stable/db.sqlite"
117
+ }
118
+ }
119
+ ]
120
+ }
package/src/pi/index.ts CHANGED
@@ -17,6 +17,7 @@ import { registerIdeCommand } from "./commands";
17
17
  import { clearLatestSelection, registerContextHandlers, setLatestSelection } from "./context";
18
18
  import { createRuntime, type PiIdeRuntime } from "./state";
19
19
  import { clearIdeUi, updateIdeUi } from "./ui";
20
+ import { startZedPolling, stopZedPolling } from "./zed";
20
21
 
21
22
  const RECONNECT_DELAY_MS = 2_000;
22
23
  const INSTALL_RECONNECT_RETRY_MS = 1_500;
@@ -28,7 +29,7 @@ export default function (pi: ExtensionAPI): void {
28
29
  registerContextHandlers(pi, runtime);
29
30
  registerIdeCommand(pi, runtime, {
30
31
  refreshCandidates: (ctx) => refreshCandidates(runtime, ctx),
31
- connectAuto: (ctx) => connectAuto(runtime, ctx),
32
+ connectAuto: (ctx) => connectAutoWithZedFallback(runtime, ctx),
32
33
  connectCandidate: (candidate, ctx) => connectCandidate(runtime, candidate, ctx),
33
34
  disconnect: (ctx, disabled) => disconnect(runtime, ctx, disabled),
34
35
  installExtension: (ctx) => installExtension(runtime, ctx),
@@ -39,18 +40,20 @@ export default function (pi: ExtensionAPI): void {
39
40
  const generation = runtime.sessionGeneration;
40
41
  runtime.ctx = ctx;
41
42
  runtime.cwd = ctx.cwd;
43
+ stopZedPolling(runtime);
42
44
  if (!runtime.enabled) {
43
45
  runtime.connectionStatus = "disabled";
44
46
  updateIdeUi(runtime, ctx);
45
47
  return;
46
48
  }
47
49
  void maybeAutoInstallAndReconnect(runtime, ctx, generation);
48
- await connectAuto(runtime, ctx);
50
+ await connectAutoWithZedFallback(runtime, ctx, generation);
49
51
  });
50
52
 
51
53
  pi.on("session_shutdown", (_event, ctx) => {
52
54
  runtime.sessionGeneration += 1;
53
55
  runtime.ctx = ctx;
56
+ stopZedPolling(runtime);
54
57
  if (runtime.reconnectTimer) clearTimeout(runtime.reconnectTimer);
55
58
  runtime.reconnectTimer = undefined;
56
59
  runtime.connection?.disconnect();
@@ -228,6 +231,17 @@ async function refreshCandidates(
228
231
  return runtime.candidates;
229
232
  }
230
233
 
234
+ async function connectAutoWithZedFallback(
235
+ runtime: PiIdeRuntime,
236
+ ctx: ExtensionContext | ExtensionCommandContext,
237
+ generation = runtime.sessionGeneration,
238
+ ): Promise<void> {
239
+ await connectAuto(runtime, ctx);
240
+ if (runtime.connectionStatus !== "connected") {
241
+ startZedPolling(runtime, ctx, { generation });
242
+ }
243
+ }
244
+
231
245
  async function connectAuto(runtime: PiIdeRuntime, ctx: ExtensionContext | ExtensionCommandContext): Promise<void> {
232
246
  runtime.enabled = true;
233
247
  const candidates = await refreshCandidates(runtime, ctx);
@@ -258,6 +272,7 @@ async function connectCandidate(
258
272
  runtime.ctx = ctx;
259
273
  runtime.cwd = ctx.cwd;
260
274
  runtime.enabled = true;
275
+ stopZedPolling(runtime);
261
276
  if (runtime.reconnectTimer) clearTimeout(runtime.reconnectTimer);
262
277
  runtime.reconnectTimer = undefined;
263
278
 
@@ -346,6 +361,7 @@ function isCurrentConnection(runtime: PiIdeRuntime, connection: IdeConnection |
346
361
 
347
362
  function disconnect(runtime: PiIdeRuntime, ctx: ExtensionContext | ExtensionCommandContext, disabled = false): void {
348
363
  runtime.ctx = ctx;
364
+ stopZedPolling(runtime);
349
365
  if (runtime.reconnectTimer) clearTimeout(runtime.reconnectTimer);
350
366
  runtime.reconnectTimer = undefined;
351
367
  const connection = runtime.connection;
@@ -370,7 +386,7 @@ function scheduleReconnect(runtime: PiIdeRuntime): void {
370
386
  runtime.reconnectTimer = undefined;
371
387
  const ctx = runtime.ctx;
372
388
  if (!ctx || !runtime.enabled) return;
373
- connectAuto(runtime, ctx).catch((error: unknown) => {
389
+ connectAutoWithZedFallback(runtime, ctx).catch((error: unknown) => {
374
390
  runtime.connectionStatus = "error";
375
391
  runtime.connectionMessage = error instanceof Error ? error.message : String(error);
376
392
  updateIdeUi(runtime);
package/src/pi/install.ts CHANGED
@@ -3,6 +3,7 @@ import { access } from "node:fs/promises";
3
3
  import { delimiter, isAbsolute, join } from "node:path";
4
4
  import { promisify } from "node:util";
5
5
  import packageJson from "../../package.json";
6
+ import { resolvePiConfigEnv } from "../shared/config";
6
7
  import type { PiIdeRuntime } from "./state";
7
8
 
8
9
  const execFileAsync = promisify(execFile);
@@ -61,7 +62,8 @@ export const SUPPORTED_IDE_CLI_PROFILES: IdeCliProfile[] = [
61
62
  ];
62
63
 
63
64
  export function isAutoInstallEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
64
- const value = env[PI_X_IDE_AUTO_INSTALL_ENV];
65
+ const configuredEnv = resolvePiConfigEnv(env);
66
+ const value = configuredEnv[PI_X_IDE_AUTO_INSTALL_ENV];
65
67
  if (value === undefined) return true;
66
68
  return !["0", "false", "off"].includes(value.trim().toLowerCase());
67
69
  }
@@ -96,10 +98,11 @@ export function compareExtensionVersions(installed: string | undefined, target:
96
98
  }
97
99
 
98
100
  export function inferCurrentIdeFromEnv(env: NodeJS.ProcessEnv = process.env): SupportedIdeId | undefined {
101
+ const configuredEnv = resolvePiConfigEnv(env);
99
102
  const matches = new Set<SupportedIdeId>();
100
- const hasWindsurfMarker = hasWindsurfEnvMarker(env);
101
- const hasCursorMarker = hasCursorEnvMarker(env);
102
- const hasVscodeMarker = hasVscodeEnvMarker(env);
103
+ const hasWindsurfMarker = hasWindsurfEnvMarker(configuredEnv);
104
+ const hasCursorMarker = hasCursorEnvMarker(configuredEnv);
105
+ const hasVscodeMarker = hasVscodeEnvMarker(configuredEnv);
103
106
 
104
107
  if (hasWindsurfMarker) matches.add("windsurf");
105
108
  if (hasCursorMarker) matches.add("cursor");
@@ -146,10 +149,11 @@ export async function findExecutable(
146
149
  command: string,
147
150
  env: NodeJS.ProcessEnv = process.env,
148
151
  ): Promise<string | undefined> {
149
- const pathEnv = env.PATH ?? env.Path ?? env.path;
152
+ const configuredEnv = resolvePiConfigEnv(env);
153
+ const pathEnv = configuredEnv.PATH ?? configuredEnv.Path ?? configuredEnv.path;
150
154
  if (!pathEnv) return undefined;
151
155
 
152
- const extensions = process.platform === "win32" ? parsePathExt(env) : [""];
156
+ const extensions = process.platform === "win32" ? parsePathExt(configuredEnv) : [""];
153
157
  const candidates = isAbsolute(command)
154
158
  ? [command]
155
159
  : pathEnv
@@ -184,7 +188,7 @@ export async function runCli(
184
188
  export async function discoverInstallCandidates(
185
189
  options: DiscoverInstallCandidatesOptions = {},
186
190
  ): Promise<IdeInstallCandidate[]> {
187
- const env = options.env ?? process.env;
191
+ const env = resolvePiConfigEnv(options.env ?? process.env);
188
192
  const currentIde = inferCurrentIdeFromEnv(env);
189
193
  const includeLowConfidence = options.includeLowConfidence ?? false;
190
194
  const timeoutMs = options.timeoutMs ?? 15_000;
@@ -228,7 +232,8 @@ export function selectAutoInstallCandidate(
228
232
  candidates: IdeInstallCandidate[],
229
233
  env: NodeJS.ProcessEnv = process.env,
230
234
  ): IdeInstallCandidate | undefined {
231
- const currentIde = inferCurrentIdeFromEnv(env);
235
+ const configuredEnv = resolvePiConfigEnv(env);
236
+ const currentIde = inferCurrentIdeFromEnv(configuredEnv);
232
237
  const highConfidence = candidates.filter(
233
238
  (candidate) => candidate.confidence === "current-terminal" && (!currentIde || candidate.id === currentIde),
234
239
  );
package/src/pi/state.ts CHANGED
@@ -17,6 +17,9 @@ export interface PiIdeRuntime {
17
17
  attachState: AttachState;
18
18
  turnSelection?: EditorSelectionSnapshot;
19
19
  reconnectTimer?: NodeJS.Timeout;
20
+ zedPollTimer?: NodeJS.Timeout;
21
+ zedPollSelectionKey?: string;
22
+ zedPollWalMtimeMs?: number;
20
23
  installingIdeIds: Set<string>;
21
24
  sessionGeneration: number;
22
25
  }