shariq-pi-extensions 0.3.2 → 0.3.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/README.md CHANGED
@@ -73,9 +73,9 @@ Runtime files use Pi's active agent directory rather than a fixed home or checko
73
73
 
74
74
  ```bash
75
75
  mise install --locked
76
- mise exec --locked -- npm ci
77
- mise exec --locked -- npm run validate
78
- mise exec --locked -- npm run pack:inspect
76
+ mise exec --locked -- bun install
77
+ mise exec --locked -- bun run validate
78
+ mise exec --locked -- bun run pack:inspect
79
79
  ```
80
80
 
81
81
  See [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) for repository workflow and release checks, and [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for package boundaries.
@@ -8,11 +8,11 @@ Last verified: 2026-09-04
8
8
  - Pi matching the pinned development dependency version
9
9
  - platform build support required by `@lydell/node-pty`
10
10
 
11
- Install the exact Node toolchain and dependencies:
11
+ Install the exact toolchain and dependencies:
12
12
 
13
13
  ```bash
14
14
  mise install --locked
15
- mise exec --locked -- npm ci
15
+ mise exec --locked -- bun install
16
16
  ```
17
17
 
18
18
  ## Validation
@@ -20,8 +20,8 @@ mise exec --locked -- npm ci
20
20
  Validate the complete suite and inspect the package payload:
21
21
 
22
22
  ```bash
23
- mise exec --locked -- npm run validate
24
- mise exec --locked -- npm pack --dry-run
23
+ mise exec --locked -- bun run validate
24
+ mise exec --locked -- bun run pack:inspect
25
25
  ```
26
26
 
27
27
  The root validation checks TypeScript, runtime tests, declared extension and skill entrypoints, and forbidden runtime files. Inspect the package file list before committing. It must not contain credentials, `.env`, first-party caches, databases, sessions, logs, or `node_modules`.
@@ -74,8 +74,8 @@ gh repo view shariqriazz/shariq-pi-extensions --json visibility,url
74
74
  Before publishing a new npm version:
75
75
 
76
76
  1. Update `package.json#version` using semantic versioning.
77
- 2. Run `mise exec --locked -- npm ci` and `mise exec --locked -- npm run validate`.
78
- 3. Inspect `mise exec --locked -- npm run pack:inspect` for secrets, runtime state, and accidental files.
77
+ 2. Run `mise exec --locked -- bun install` and `mise exec --locked -- bun run validate`.
78
+ 3. Inspect `mise exec --locked -- bun run pack:inspect` for secrets, runtime state, and accidental files.
79
79
  4. Run the `Publish npm package` workflow. npm trusted publishing authenticates it through GitHub OIDC and records provenance without a long-lived token.
80
80
  5. Verify the registry version and install it with `pi install npm:shariq-pi-extensions`.
81
81
 
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { spawn as spawnPty, type IDisposable, type IPty } from "@lydell/node-pty";
4
+ import { spawnUniversalPty, type UniversalPty } from "./pty.ts";
5
5
  import { OutputBuffer } from "./output-buffer.ts";
6
6
  import type {
7
7
  StartTerminalOptions,
@@ -37,11 +37,11 @@ interface MutableSnapshot {
37
37
 
38
38
  interface Entry {
39
39
  readonly snapshot: MutableSnapshot;
40
- readonly pty: IPty;
40
+ readonly pty: UniversalPty;
41
41
  readonly output: OutputBuffer;
42
42
  readonly spill: fs.WriteStream;
43
- readonly dataSubscription: IDisposable;
44
- readonly exitSubscription: IDisposable;
43
+ readonly dataSubscription: { dispose(): void };
44
+ readonly exitSubscription: { dispose(): void };
45
45
  readonly settled: Promise<TerminalSnapshot>;
46
46
  resolveSettled(snapshot: TerminalSnapshot): void;
47
47
  killRequested: boolean;
@@ -202,7 +202,7 @@ export class TerminalManager {
202
202
  let spillBytes = 0;
203
203
  let spillTruncated = false;
204
204
  let pausedForSpill = false;
205
- let pty: IPty | undefined;
205
+ let pty: UniversalPty | undefined;
206
206
  let snapshot: MutableSnapshot | undefined;
207
207
  const markSpillTruncated = () => {
208
208
  if (spillTruncated) return;
@@ -223,7 +223,7 @@ export class TerminalManager {
223
223
  if (payload.length < bytes.length || spillBytes >= this.maxFullLogBytes) markSpillTruncated();
224
224
  if (!accepted && pty && !pausedForSpill) {
225
225
  pausedForSpill = true;
226
- pty.pause();
226
+ pty.pause?.();
227
227
  }
228
228
  });
229
229
  output.spillPath = spillPath;
@@ -231,7 +231,7 @@ export class TerminalManager {
231
231
  if (!pausedForSpill) return;
232
232
  pausedForSpill = false;
233
233
  try {
234
- pty?.resume();
234
+ pty?.resume?.();
235
235
  } catch {
236
236
  // Exit may have won the drain race.
237
237
  }
@@ -242,7 +242,7 @@ export class TerminalManager {
242
242
  if (pausedForSpill) {
243
243
  pausedForSpill = false;
244
244
  try {
245
- pty?.resume();
245
+ pty?.resume?.();
246
246
  } catch {
247
247
  // Exit may have won the error race.
248
248
  }
@@ -253,8 +253,9 @@ export class TerminalManager {
253
253
 
254
254
  const shell = shellInvocation(options.command);
255
255
  try {
256
- pty = spawnPty(shell.file, shell.args, {
257
- name: "xterm-256color",
256
+ pty = spawnUniversalPty({
257
+ file: shell.file,
258
+ args: shell.args,
258
259
  cols,
259
260
  rows,
260
261
  cwd: options.cwd,
@@ -0,0 +1,165 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { spawn as spawnNodePty, type IDisposable, type IPty } from "@lydell/node-pty";
4
+
5
+ export interface UniversalPty {
6
+ readonly pid: number;
7
+ onData(listener: (chunk: string) => void): { dispose(): void };
8
+ onExit(listener: (event: { exitCode: number; signal?: number }) => void): { dispose(): void };
9
+ write(data: string): void;
10
+ resize(cols: number, rows: number): void;
11
+ pause?(): void;
12
+ resume?(): void;
13
+ kill(signal?: string): void;
14
+ }
15
+
16
+ export interface SpawnUniversalPtyOptions {
17
+ file: string;
18
+ args: string[];
19
+ cols: number;
20
+ rows: number;
21
+ cwd: string;
22
+ env: Record<string, string | undefined>;
23
+ }
24
+
25
+ declare const Bun: any;
26
+
27
+ class BunPtyAdapter implements UniversalPty {
28
+ readonly pid: number;
29
+ private readonly proc: any;
30
+ private readonly dataListeners = new Set<(chunk: string) => void>();
31
+ private readonly exitListeners = new Set<(event: { exitCode: number; signal?: number }) => void>();
32
+ private exited = false;
33
+
34
+ constructor(options: SpawnUniversalPtyOptions) {
35
+ const cmd = [options.file, ...options.args];
36
+ this.proc = Bun.spawn(cmd, {
37
+ cwd: options.cwd,
38
+ env: options.env,
39
+ terminal: {
40
+ cols: options.cols,
41
+ rows: options.rows,
42
+ data: (_term: unknown, chunk: Uint8Array | string) => {
43
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
44
+ for (const listener of this.dataListeners) {
45
+ try {
46
+ listener(text);
47
+ } catch {
48
+ // Ignore listener errors
49
+ }
50
+ }
51
+ },
52
+ },
53
+ });
54
+
55
+ this.pid = this.proc.pid;
56
+
57
+ void this.proc.exited.then((exitCode: number) => {
58
+ if (this.exited) return;
59
+ this.exited = true;
60
+ const signal = this.proc.signalCode
61
+ ? typeof this.proc.signalCode === "number"
62
+ ? this.proc.signalCode
63
+ : undefined
64
+ : undefined;
65
+ for (const listener of this.exitListeners) {
66
+ try {
67
+ listener({ exitCode: typeof exitCode === "number" ? exitCode : 0, signal });
68
+ } catch {
69
+ // Ignore listener errors
70
+ }
71
+ }
72
+ });
73
+ }
74
+
75
+ onData(listener: (chunk: string) => void) {
76
+ this.dataListeners.add(listener);
77
+ return {
78
+ dispose: () => {
79
+ this.dataListeners.delete(listener);
80
+ },
81
+ };
82
+ }
83
+
84
+ onExit(listener: (event: { exitCode: number; signal?: number }) => void) {
85
+ this.exitListeners.add(listener);
86
+ return {
87
+ dispose: () => {
88
+ this.exitListeners.delete(listener);
89
+ },
90
+ };
91
+ }
92
+
93
+ write(data: string): void {
94
+ try {
95
+ this.proc.terminal.write(data);
96
+ } catch {
97
+ // Best-effort write
98
+ }
99
+ }
100
+
101
+ resize(cols: number, rows: number): void {
102
+ try {
103
+ this.proc.terminal.resize(cols, rows);
104
+ } catch {
105
+ // Best-effort resize
106
+ }
107
+ }
108
+
109
+ kill(signal?: string): void {
110
+ try {
111
+ this.proc.kill(signal ?? "SIGTERM");
112
+ } catch {
113
+ // Best-effort kill
114
+ }
115
+ }
116
+ }
117
+
118
+ class NodePtyAdapter implements UniversalPty {
119
+ readonly pid: number;
120
+ private readonly pty: IPty;
121
+
122
+ constructor(pty: IPty) {
123
+ this.pty = pty;
124
+ this.pid = pty.pid;
125
+ }
126
+
127
+ onData(listener: (chunk: string) => void) {
128
+ return this.pty.onData(listener);
129
+ }
130
+
131
+ onExit(listener: (event: { exitCode: number; signal?: number }) => void) {
132
+ return this.pty.onExit(listener);
133
+ }
134
+
135
+ write(data: string): void {
136
+ this.pty.write(data);
137
+ }
138
+
139
+ resize(cols: number, rows: number): void {
140
+ this.pty.resize(cols, rows);
141
+ }
142
+
143
+ kill(signal?: string): void {
144
+ this.pty.kill(signal);
145
+ }
146
+ }
147
+
148
+ export function isBunRuntime(): boolean {
149
+ return typeof Bun !== "undefined" && typeof Bun.spawn === "function";
150
+ }
151
+
152
+ export function spawnUniversalPty(options: SpawnUniversalPtyOptions): UniversalPty {
153
+ if (isBunRuntime()) {
154
+ return new BunPtyAdapter(options);
155
+ }
156
+
157
+ const pty = spawnNodePty(options.file, options.args, {
158
+ name: "xterm-256color",
159
+ cols: options.cols,
160
+ rows: options.rows,
161
+ cwd: options.cwd,
162
+ env: options.env as Record<string, string>,
163
+ });
164
+ return new NodePtyAdapter(pty);
165
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shariq-pi-extensions",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Cross-platform extension suite for the Pi coding agent.",
5
5
  "license": "MIT",
6
6
  "author": "Shariq Riaz",
@@ -62,12 +62,12 @@
62
62
  "check": "tsc --noEmit -p tsconfig.json",
63
63
  "test": "node scripts/run-tests.mjs",
64
64
  "validate:package": "node scripts/validate-package.mjs",
65
- "validate": "npm run check && npm test && npm run validate:package",
65
+ "validate": "bun run check && node scripts/run-tests.mjs && bun run validate:package",
66
66
  "pack:inspect": "npm pack --dry-run",
67
- "prepublishOnly": "npm run validate"
67
+ "prepublishOnly": "bun run validate"
68
68
  },
69
69
  "dependencies": {
70
- "@lydell/node-pty": "1.1.0",
70
+ "@lydell/node-pty": "^1.1.0",
71
71
  "effect": "4.0.0-beta.98"
72
72
  },
73
73
  "peerDependencies": {
@@ -95,12 +95,12 @@
95
95
  }
96
96
  },
97
97
  "devDependencies": {
98
- "@earendil-works/pi-agent-core": "0.84.2",
99
- "@earendil-works/pi-ai": "0.84.2",
100
- "@earendil-works/pi-coding-agent": "0.84.2",
101
- "@earendil-works/pi-tui": "0.84.2",
102
- "@types/node": "^26.1.2",
103
- "typebox": "1.3.7",
98
+ "@earendil-works/pi-agent-core": "^0.85.1",
99
+ "@earendil-works/pi-ai": "^0.85.1",
100
+ "@earendil-works/pi-coding-agent": "^0.85.1",
101
+ "@earendil-works/pi-tui": "^0.85.1",
102
+ "@types/node": "^26.4.1",
103
+ "typebox": "^1.3.27",
104
104
  "typescript": "^7.0.2"
105
105
  },
106
106
  "allowScripts": {