@fleetagent/pi-coding-agent 0.0.9 → 0.0.11

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 (136) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +9 -0
  3. package/dist/cli/args.d.ts +3 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +18 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +13 -3
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +42 -8
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/bash-executor.d.ts +5 -3
  12. package/dist/core/bash-executor.d.ts.map +1 -1
  13. package/dist/core/bash-executor.js +4 -2
  14. package/dist/core/bash-executor.js.map +1 -1
  15. package/dist/core/extensions/index.d.ts +1 -1
  16. package/dist/core/extensions/index.d.ts.map +1 -1
  17. package/dist/core/extensions/index.js.map +1 -1
  18. package/dist/core/extensions/loader.d.ts.map +1 -1
  19. package/dist/core/extensions/loader.js +86 -0
  20. package/dist/core/extensions/loader.js.map +1 -1
  21. package/dist/core/extensions/runner.d.ts +3 -0
  22. package/dist/core/extensions/runner.d.ts.map +1 -1
  23. package/dist/core/extensions/runner.js +27 -0
  24. package/dist/core/extensions/runner.js.map +1 -1
  25. package/dist/core/extensions/types.d.ts +56 -3
  26. package/dist/core/extensions/types.d.ts.map +1 -1
  27. package/dist/core/extensions/types.js.map +1 -1
  28. package/dist/core/pi-agent.d.ts +2 -0
  29. package/dist/core/pi-agent.d.ts.map +1 -1
  30. package/dist/core/pi-agent.js +3 -0
  31. package/dist/core/pi-agent.js.map +1 -1
  32. package/dist/core/prompt-templates.d.ts +5 -0
  33. package/dist/core/prompt-templates.d.ts.map +1 -1
  34. package/dist/core/prompt-templates.js +115 -0
  35. package/dist/core/prompt-templates.js.map +1 -1
  36. package/dist/core/resource-loader.d.ts +15 -0
  37. package/dist/core/resource-loader.d.ts.map +1 -1
  38. package/dist/core/resource-loader.js +332 -40
  39. package/dist/core/resource-loader.js.map +1 -1
  40. package/dist/core/rules.d.ts +6 -0
  41. package/dist/core/rules.d.ts.map +1 -1
  42. package/dist/core/rules.js +216 -0
  43. package/dist/core/rules.js.map +1 -1
  44. package/dist/core/skills.d.ts +6 -0
  45. package/dist/core/skills.d.ts.map +1 -1
  46. package/dist/core/skills.js +216 -0
  47. package/dist/core/skills.js.map +1 -1
  48. package/dist/core/slash-commands.d.ts.map +1 -1
  49. package/dist/core/slash-commands.js +1 -0
  50. package/dist/core/slash-commands.js.map +1 -1
  51. package/dist/core/source-info.d.ts +2 -0
  52. package/dist/core/source-info.d.ts.map +1 -1
  53. package/dist/core/source-info.js +6 -0
  54. package/dist/core/source-info.js.map +1 -1
  55. package/dist/core/tools/bash.d.ts +6 -29
  56. package/dist/core/tools/bash.d.ts.map +1 -1
  57. package/dist/core/tools/bash.js +27 -101
  58. package/dist/core/tools/bash.js.map +1 -1
  59. package/dist/core/tools/edit-diff.d.ts +3 -2
  60. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  61. package/dist/core/tools/edit-diff.js +6 -8
  62. package/dist/core/tools/edit-diff.js.map +1 -1
  63. package/dist/core/tools/edit.d.ts +3 -16
  64. package/dist/core/tools/edit.d.ts.map +1 -1
  65. package/dist/core/tools/edit.js +12 -18
  66. package/dist/core/tools/edit.js.map +1 -1
  67. package/dist/core/tools/find.d.ts +3 -17
  68. package/dist/core/tools/find.d.ts.map +1 -1
  69. package/dist/core/tools/find.js +13 -25
  70. package/dist/core/tools/find.js.map +1 -1
  71. package/dist/core/tools/grep.d.ts +3 -14
  72. package/dist/core/tools/grep.d.ts.map +1 -1
  73. package/dist/core/tools/grep.js +95 -43
  74. package/dist/core/tools/grep.js.map +1 -1
  75. package/dist/core/tools/index.d.ts +17 -15
  76. package/dist/core/tools/index.d.ts.map +1 -1
  77. package/dist/core/tools/index.js +53 -52
  78. package/dist/core/tools/index.js.map +1 -1
  79. package/dist/core/tools/ls.d.ts +3 -20
  80. package/dist/core/tools/ls.d.ts.map +1 -1
  81. package/dist/core/tools/ls.js +11 -22
  82. package/dist/core/tools/ls.js.map +1 -1
  83. package/dist/core/tools/operations.d.ts +145 -0
  84. package/dist/core/tools/operations.d.ts.map +1 -0
  85. package/dist/core/tools/operations.js +418 -0
  86. package/dist/core/tools/operations.js.map +1 -0
  87. package/dist/core/tools/read.d.ts +3 -16
  88. package/dist/core/tools/read.d.ts.map +1 -1
  89. package/dist/core/tools/read.js +9 -15
  90. package/dist/core/tools/read.js.map +1 -1
  91. package/dist/core/tools/render-utils.d.ts +9 -0
  92. package/dist/core/tools/render-utils.d.ts.map +1 -1
  93. package/dist/core/tools/render-utils.js +14 -0
  94. package/dist/core/tools/render-utils.js.map +1 -1
  95. package/dist/core/tools/write.d.ts +3 -14
  96. package/dist/core/tools/write.d.ts.map +1 -1
  97. package/dist/core/tools/write.js +9 -12
  98. package/dist/core/tools/write.js.map +1 -1
  99. package/dist/index.d.ts +2 -2
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +1 -1
  102. package/dist/index.js.map +1 -1
  103. package/dist/main.d.ts.map +1 -1
  104. package/dist/main.js +40 -5
  105. package/dist/main.js.map +1 -1
  106. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  107. package/dist/modes/interactive/components/tool-execution.js +2 -2
  108. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  109. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  110. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  111. package/dist/modes/interactive/interactive-mode.js +73 -9
  112. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  113. package/dist/modes/rpc/rpc-client.d.ts +9 -0
  114. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  115. package/dist/modes/rpc/rpc-client.js +14 -0
  116. package/dist/modes/rpc/rpc-client.js.map +1 -1
  117. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  118. package/dist/modes/rpc/rpc-mode.js +9 -0
  119. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  120. package/dist/modes/rpc/rpc-types.d.ts +22 -0
  121. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  122. package/dist/modes/rpc/rpc-types.js.map +1 -1
  123. package/docs/extensions.md +83 -5
  124. package/docs/usage.md +2 -0
  125. package/examples/extensions/README.md +0 -1
  126. package/examples/extensions/bash-spawn-hook.ts +2 -2
  127. package/examples/extensions/built-in-tool-renderer.ts +12 -5
  128. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  129. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  130. package/examples/extensions/minimal-mode.ts +9 -7
  131. package/examples/extensions/sandbox/index.ts +55 -56
  132. package/examples/extensions/sandbox/package.json +1 -1
  133. package/examples/extensions/with-deps/package.json +1 -1
  134. package/npm-shrinkwrap.json +12 -12
  135. package/package.json +4 -4
  136. package/examples/extensions/ssh.ts +0 -220
package/docs/usage.md CHANGED
@@ -211,6 +211,8 @@ Combine `--no-*` with explicit flags to load exactly what you need, ignoring set
211
211
  pi --no-extensions -e ./my-extension.ts
212
212
  ```
213
213
 
214
+ With `--ssh`, project instruction resources are loaded from the tool backend cwd: `.pi/skills`, `.pi/rules`, `.pi/prompts`, ancestor `.agents/skills` and `.agents/rules`, and `AGENTS.md`/`CLAUDE.md`. Extension loading, themes, user resources, and package resources remain local.
215
+
214
216
  ### Other Options
215
217
 
216
218
  | Option | Description |
@@ -38,7 +38,6 @@ cp permission-gate.ts ~/.pi/agent/extensions/
38
38
  | `built-in-tool-renderer.ts` | Custom compact rendering for built-in tools (read, bash, edit, write) while keeping original behavior |
39
39
  | `minimal-mode.ts` | Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode) |
40
40
  | `truncated-tool.ts` | Wraps ripgrep with proper output truncation (50KB/2000 lines) |
41
- | `ssh.ts` | Delegate all tools to a remote machine via SSH using pluggable operations |
42
41
  | `subagent/` | Delegate tasks to specialized subagents with isolated context windows |
43
42
 
44
43
  ### Commands & UI
@@ -8,12 +8,12 @@
8
8
  */
9
9
 
10
10
  import type { ExtensionAPI } from "@fleetagent/pi-coding-agent";
11
- import { createBashTool } from "@fleetagent/pi-coding-agent";
11
+ import { createBashTool, LocalToolOperations } from "@fleetagent/pi-coding-agent";
12
12
 
13
13
  export default function (pi: ExtensionAPI) {
14
14
  const cwd = process.cwd();
15
15
 
16
- const bashTool = createBashTool(cwd, {
16
+ const bashTool = createBashTool(new LocalToolOperations(cwd), {
17
17
  spawnHook: ({ command, cwd, env }) => ({
18
18
  command: `source ~/.profile\n${command}`,
19
19
  cwd,
@@ -26,14 +26,21 @@
26
26
  */
27
27
 
28
28
  import type { BashToolDetails, EditToolDetails, ExtensionAPI, ReadToolDetails } from "@fleetagent/pi-coding-agent";
29
- import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@fleetagent/pi-coding-agent";
29
+ import {
30
+ createBashTool,
31
+ createEditTool,
32
+ createReadTool,
33
+ createWriteTool,
34
+ LocalToolOperations,
35
+ } from "@fleetagent/pi-coding-agent";
30
36
  import { Text } from "@fleetagent/pi-tui";
31
37
 
32
38
  export default function (pi: ExtensionAPI) {
33
39
  const cwd = process.cwd();
40
+ const operations = new LocalToolOperations(cwd);
34
41
 
35
42
  // --- Read tool: show path and line count ---
36
- const originalRead = createReadTool(cwd);
43
+ const originalRead = createReadTool(operations);
37
44
  pi.registerTool({
38
45
  name: "read",
39
46
  label: "read",
@@ -92,7 +99,7 @@ export default function (pi: ExtensionAPI) {
92
99
  });
93
100
 
94
101
  // --- Bash tool: show command and exit code ---
95
- const originalBash = createBashTool(cwd);
102
+ const originalBash = createBashTool(operations);
96
103
  pi.registerTool({
97
104
  name: "bash",
98
105
  label: "bash",
@@ -151,7 +158,7 @@ export default function (pi: ExtensionAPI) {
151
158
  });
152
159
 
153
160
  // --- Edit tool: show path and diff stats ---
154
- const originalEdit = createEditTool(cwd);
161
+ const originalEdit = createEditTool(operations);
155
162
  pi.registerTool({
156
163
  name: "edit",
157
164
  label: "edit",
@@ -216,7 +223,7 @@ export default function (pi: ExtensionAPI) {
216
223
  });
217
224
 
218
225
  // --- Write tool: show path and size ---
219
- const originalWrite = createWriteTool(cwd);
226
+ const originalWrite = createWriteTool(operations);
220
227
  pi.registerTool({
221
228
  name: "write",
222
229
  label: "write",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.0.9",
4
+ "version": "0.0.11",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.0.9",
4
+ "version": "0.0.11",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -25,6 +25,7 @@ import {
25
25
  createLsTool,
26
26
  createReadTool,
27
27
  createWriteTool,
28
+ LocalToolOperations,
28
29
  } from "@fleetagent/pi-coding-agent";
29
30
  import { Text } from "@fleetagent/pi-tui";
30
31
  import { homedir } from "os";
@@ -44,14 +45,15 @@ function shortenPath(path: string): string {
44
45
  const toolCache = new Map<string, ReturnType<typeof createBuiltInTools>>();
45
46
 
46
47
  function createBuiltInTools(cwd: string) {
48
+ const operations = new LocalToolOperations(cwd);
47
49
  return {
48
- read: createReadTool(cwd),
49
- bash: createBashTool(cwd),
50
- edit: createEditTool(cwd),
51
- write: createWriteTool(cwd),
52
- find: createFindTool(cwd),
53
- grep: createGrepTool(cwd),
54
- ls: createLsTool(cwd),
50
+ read: createReadTool(operations),
51
+ bash: createBashTool(operations),
52
+ edit: createEditTool(operations),
53
+ write: createWriteTool(operations),
54
+ find: createFindTool(operations),
55
+ grep: createGrepTool(operations),
56
+ ls: createLsTool(operations),
55
57
  };
56
58
  }
57
59
 
@@ -46,7 +46,7 @@ import { existsSync, readFileSync } from "node:fs";
46
46
  import { join } from "node:path";
47
47
  import { SandboxManager, type SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
48
48
  import type { ExtensionAPI } from "@fleetagent/pi-coding-agent";
49
- import { type BashOperations, createBashTool, getAgentDir } from "@fleetagent/pi-coding-agent";
49
+ import { createBashTool, getAgentDir, LocalToolOperations } from "@fleetagent/pi-coding-agent";
50
50
 
51
51
  interface SandboxConfig extends SandboxRuntimeConfig {
52
52
  enabled?: boolean;
@@ -129,47 +129,29 @@ function deepMerge(base: SandboxConfig, overrides: Partial<SandboxConfig>): Sand
129
129
  return result;
130
130
  }
131
131
 
132
- function createSandboxedBashOps(): BashOperations {
133
- return {
134
- async exec(command, cwd, { onData, signal, timeout }) {
135
- if (!existsSync(cwd)) {
136
- throw new Error(`Working directory does not exist: ${cwd}`);
137
- }
132
+ function createSandboxedBashOps(cwd: string): LocalToolOperations {
133
+ const operations = new LocalToolOperations(cwd);
134
+ operations.exec = async (command, options) => {
135
+ const workingDirectory = options.cwd ?? cwd;
136
+ if (!existsSync(workingDirectory)) {
137
+ throw new Error(`Working directory does not exist: ${workingDirectory}`);
138
+ }
138
139
 
139
- const wrappedCommand = await SandboxManager.wrapWithSandbox(command);
140
-
141
- return new Promise((resolve, reject) => {
142
- const child = spawn("bash", ["-c", wrappedCommand], {
143
- cwd,
144
- detached: true,
145
- stdio: ["ignore", "pipe", "pipe"],
146
- });
147
-
148
- let timedOut = false;
149
- let timeoutHandle: NodeJS.Timeout | undefined;
150
-
151
- if (timeout !== undefined && timeout > 0) {
152
- timeoutHandle = setTimeout(() => {
153
- timedOut = true;
154
- if (child.pid) {
155
- try {
156
- process.kill(-child.pid, "SIGKILL");
157
- } catch {
158
- child.kill("SIGKILL");
159
- }
160
- }
161
- }, timeout * 1000);
162
- }
140
+ const wrappedCommand = await SandboxManager.wrapWithSandbox(command);
163
141
 
164
- child.stdout?.on("data", onData);
165
- child.stderr?.on("data", onData);
142
+ return new Promise((resolve, reject) => {
143
+ const child = spawn("bash", ["-c", wrappedCommand], {
144
+ cwd: workingDirectory,
145
+ detached: true,
146
+ stdio: ["ignore", "pipe", "pipe"],
147
+ });
166
148
 
167
- child.on("error", (err) => {
168
- if (timeoutHandle) clearTimeout(timeoutHandle);
169
- reject(err);
170
- });
149
+ let timedOut = false;
150
+ let timeoutHandle: NodeJS.Timeout | undefined;
171
151
 
172
- const onAbort = () => {
152
+ if (options.timeout !== undefined && options.timeout > 0) {
153
+ timeoutHandle = setTimeout(() => {
154
+ timedOut = true;
173
155
  if (child.pid) {
174
156
  try {
175
157
  process.kill(-child.pid, "SIGKILL");
@@ -177,25 +159,44 @@ function createSandboxedBashOps(): BashOperations {
177
159
  child.kill("SIGKILL");
178
160
  }
179
161
  }
180
- };
162
+ }, options.timeout * 1000);
163
+ }
181
164
 
182
- signal?.addEventListener("abort", onAbort, { once: true });
165
+ child.stdout?.on("data", options.onData);
166
+ child.stderr?.on("data", options.onData);
183
167
 
184
- child.on("close", (code) => {
185
- if (timeoutHandle) clearTimeout(timeoutHandle);
186
- signal?.removeEventListener("abort", onAbort);
168
+ child.on("error", (err) => {
169
+ if (timeoutHandle) clearTimeout(timeoutHandle);
170
+ reject(err);
171
+ });
187
172
 
188
- if (signal?.aborted) {
189
- reject(new Error("aborted"));
190
- } else if (timedOut) {
191
- reject(new Error(`timeout:${timeout}`));
192
- } else {
193
- resolve({ exitCode: code });
173
+ const onAbort = () => {
174
+ if (child.pid) {
175
+ try {
176
+ process.kill(-child.pid, "SIGKILL");
177
+ } catch {
178
+ child.kill("SIGKILL");
194
179
  }
195
- });
180
+ }
181
+ };
182
+
183
+ options.signal?.addEventListener("abort", onAbort, { once: true });
184
+
185
+ child.on("close", (code) => {
186
+ if (timeoutHandle) clearTimeout(timeoutHandle);
187
+ options.signal?.removeEventListener("abort", onAbort);
188
+
189
+ if (options.signal?.aborted) {
190
+ reject(new Error("aborted"));
191
+ } else if (timedOut) {
192
+ reject(new Error(`timeout:${options.timeout}`));
193
+ } else {
194
+ resolve({ exitCode: code });
195
+ }
196
196
  });
197
- },
197
+ });
198
198
  };
199
+ return operations;
199
200
  }
200
201
 
201
202
  export default function (pi: ExtensionAPI) {
@@ -206,7 +207,7 @@ export default function (pi: ExtensionAPI) {
206
207
  });
207
208
 
208
209
  const localCwd = process.cwd();
209
- const localBash = createBashTool(localCwd);
210
+ const localBash = createBashTool(new LocalToolOperations(localCwd));
210
211
 
211
212
  let sandboxEnabled = false;
212
213
  let sandboxInitialized = false;
@@ -219,16 +220,14 @@ export default function (pi: ExtensionAPI) {
219
220
  return localBash.execute(id, params, signal, onUpdate);
220
221
  }
221
222
 
222
- const sandboxedBash = createBashTool(localCwd, {
223
- operations: createSandboxedBashOps(),
224
- });
223
+ const sandboxedBash = createBashTool(createSandboxedBashOps(localCwd));
225
224
  return sandboxedBash.execute(id, params, signal, onUpdate);
226
225
  },
227
226
  });
228
227
 
229
228
  pi.on("user_bash", () => {
230
229
  if (!sandboxEnabled || !sandboxInitialized) return;
231
- return { operations: createSandboxedBashOps() };
230
+ return { operations: createSandboxedBashOps(localCwd) };
232
231
  });
233
232
 
234
233
  pi.on("session_start", async (_event, ctx) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
3
  "private": true,
4
- "version": "0.0.9",
4
+ "version": "0.0.11",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.0.9",
4
+ "version": "0.0.11",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@fleetagent/pi-coding-agent",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@fleetagent/pi-coding-agent",
9
- "version": "0.0.9",
9
+ "version": "0.0.11",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@fleetagent/pi-agent-core": "^0.0.9",
13
- "@fleetagent/pi-ai": "^0.0.9",
14
- "@fleetagent/pi-tui": "^0.0.9",
12
+ "@fleetagent/pi-agent-core": "^0.0.11",
13
+ "@fleetagent/pi-ai": "^0.0.11",
14
+ "@fleetagent/pi-tui": "^0.0.11",
15
15
  "@silvia-odwyer/photon-node": "0.3.4",
16
16
  "chalk": "5.6.2",
17
17
  "cross-spawn": "7.0.6",
@@ -473,11 +473,11 @@
473
473
  }
474
474
  },
475
475
  "node_modules/@fleetagent/pi-agent-core": {
476
- "version": "0.0.9",
477
- "resolved": "https://registry.npmjs.org/@fleetagent/pi-agent-core/-/pi-agent-core-0.0.9.tgz",
476
+ "version": "0.0.11",
477
+ "resolved": "https://registry.npmjs.org/@fleetagent/pi-agent-core/-/pi-agent-core-0.0.11.tgz",
478
478
  "license": "MIT",
479
479
  "dependencies": {
480
- "@fleetagent/pi-ai": "^0.0.9",
480
+ "@fleetagent/pi-ai": "^0.0.11",
481
481
  "ignore": "7.0.5",
482
482
  "typebox": "1.1.38",
483
483
  "yaml": "2.9.0"
@@ -487,8 +487,8 @@
487
487
  }
488
488
  },
489
489
  "node_modules/@fleetagent/pi-ai": {
490
- "version": "0.0.9",
491
- "resolved": "https://registry.npmjs.org/@fleetagent/pi-ai/-/pi-ai-0.0.9.tgz",
490
+ "version": "0.0.11",
491
+ "resolved": "https://registry.npmjs.org/@fleetagent/pi-ai/-/pi-ai-0.0.11.tgz",
492
492
  "license": "MIT",
493
493
  "dependencies": {
494
494
  "@anthropic-ai/sdk": "0.91.1",
@@ -510,8 +510,8 @@
510
510
  }
511
511
  },
512
512
  "node_modules/@fleetagent/pi-tui": {
513
- "version": "0.0.9",
514
- "resolved": "https://registry.npmjs.org/@fleetagent/pi-tui/-/pi-tui-0.0.9.tgz",
513
+ "version": "0.0.11",
514
+ "resolved": "https://registry.npmjs.org/@fleetagent/pi-tui/-/pi-tui-0.0.11.tgz",
515
515
  "license": "MIT",
516
516
  "dependencies": {
517
517
  "get-east-asian-width": "1.6.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetagent/pi-coding-agent",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -39,9 +39,9 @@
39
39
  "prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
40
40
  },
41
41
  "dependencies": {
42
- "@fleetagent/pi-agent-core": "^0.0.9",
43
- "@fleetagent/pi-ai": "^0.0.9",
44
- "@fleetagent/pi-tui": "^0.0.9",
42
+ "@fleetagent/pi-agent-core": "^0.0.11",
43
+ "@fleetagent/pi-ai": "^0.0.11",
44
+ "@fleetagent/pi-tui": "^0.0.11",
45
45
  "@silvia-odwyer/photon-node": "0.3.4",
46
46
  "chalk": "5.6.2",
47
47
  "cross-spawn": "7.0.6",
@@ -1,220 +0,0 @@
1
- /**
2
- * SSH Remote Execution Example
3
- *
4
- * Demonstrates delegating tool operations to a remote machine via SSH.
5
- * When --ssh is provided, read/write/edit/bash run on the remote.
6
- *
7
- * Usage:
8
- * pi -e ./ssh.ts --ssh user@host
9
- * pi -e ./ssh.ts --ssh user@host:/remote/path
10
- *
11
- * Requirements:
12
- * - SSH key-based auth (no password prompts)
13
- * - bash on remote
14
- */
15
-
16
- import { spawn } from "node:child_process";
17
- import type { ExtensionAPI } from "@fleetagent/pi-coding-agent";
18
- import {
19
- type BashOperations,
20
- createBashTool,
21
- createEditTool,
22
- createReadTool,
23
- createWriteTool,
24
- type EditOperations,
25
- type ReadOperations,
26
- type WriteOperations,
27
- } from "@fleetagent/pi-coding-agent";
28
-
29
- function sshExec(remote: string, command: string): Promise<Buffer> {
30
- return new Promise((resolve, reject) => {
31
- const child = spawn("ssh", [remote, command], { stdio: ["ignore", "pipe", "pipe"] });
32
- const chunks: Buffer[] = [];
33
- const errChunks: Buffer[] = [];
34
- child.stdout.on("data", (data) => chunks.push(data));
35
- child.stderr.on("data", (data) => errChunks.push(data));
36
- child.on("error", reject);
37
- child.on("close", (code) => {
38
- if (code !== 0) {
39
- reject(new Error(`SSH failed (${code}): ${Buffer.concat(errChunks).toString()}`));
40
- } else {
41
- resolve(Buffer.concat(chunks));
42
- }
43
- });
44
- });
45
- }
46
-
47
- function createRemoteReadOps(remote: string, remoteCwd: string, localCwd: string): ReadOperations {
48
- const toRemote = (p: string) => p.replace(localCwd, remoteCwd);
49
- return {
50
- readFile: (p) => sshExec(remote, `cat ${JSON.stringify(toRemote(p))}`),
51
- access: (p) => sshExec(remote, `test -r ${JSON.stringify(toRemote(p))}`).then(() => {}),
52
- detectImageMimeType: async (p) => {
53
- try {
54
- const r = await sshExec(remote, `file --mime-type -b ${JSON.stringify(toRemote(p))}`);
55
- const m = r.toString().trim();
56
- return ["image/jpeg", "image/png", "image/gif", "image/webp"].includes(m) ? m : null;
57
- } catch {
58
- return null;
59
- }
60
- },
61
- };
62
- }
63
-
64
- function createRemoteWriteOps(remote: string, remoteCwd: string, localCwd: string): WriteOperations {
65
- const toRemote = (p: string) => p.replace(localCwd, remoteCwd);
66
- return {
67
- writeFile: async (p, content) => {
68
- const b64 = Buffer.from(content).toString("base64");
69
- await sshExec(remote, `echo ${JSON.stringify(b64)} | base64 -d > ${JSON.stringify(toRemote(p))}`);
70
- },
71
- mkdir: (dir) => sshExec(remote, `mkdir -p ${JSON.stringify(toRemote(dir))}`).then(() => {}),
72
- };
73
- }
74
-
75
- function createRemoteEditOps(remote: string, remoteCwd: string, localCwd: string): EditOperations {
76
- const r = createRemoteReadOps(remote, remoteCwd, localCwd);
77
- const w = createRemoteWriteOps(remote, remoteCwd, localCwd);
78
- return { readFile: r.readFile, access: r.access, writeFile: w.writeFile };
79
- }
80
-
81
- function createRemoteBashOps(remote: string, remoteCwd: string, localCwd: string): BashOperations {
82
- const toRemote = (p: string) => p.replace(localCwd, remoteCwd);
83
- return {
84
- exec: (command, cwd, { onData, signal, timeout }) =>
85
- new Promise((resolve, reject) => {
86
- const cmd = `cd ${JSON.stringify(toRemote(cwd))} && ${command}`;
87
- const child = spawn("ssh", [remote, cmd], { stdio: ["ignore", "pipe", "pipe"] });
88
- let timedOut = false;
89
- const timer = timeout
90
- ? setTimeout(() => {
91
- timedOut = true;
92
- child.kill();
93
- }, timeout * 1000)
94
- : undefined;
95
- child.stdout.on("data", onData);
96
- child.stderr.on("data", onData);
97
- child.on("error", (e) => {
98
- if (timer) clearTimeout(timer);
99
- reject(e);
100
- });
101
- const onAbort = () => child.kill();
102
- signal?.addEventListener("abort", onAbort, { once: true });
103
- child.on("close", (code) => {
104
- if (timer) clearTimeout(timer);
105
- signal?.removeEventListener("abort", onAbort);
106
- if (signal?.aborted) reject(new Error("aborted"));
107
- else if (timedOut) reject(new Error(`timeout:${timeout}`));
108
- else resolve({ exitCode: code });
109
- });
110
- }),
111
- };
112
- }
113
-
114
- export default function (pi: ExtensionAPI) {
115
- pi.registerFlag("ssh", { description: "SSH remote: user@host or user@host:/path", type: "string" });
116
-
117
- const localCwd = process.cwd();
118
- const localRead = createReadTool(localCwd);
119
- const localWrite = createWriteTool(localCwd);
120
- const localEdit = createEditTool(localCwd);
121
- const localBash = createBashTool(localCwd);
122
-
123
- // Resolved lazily on session_start (CLI flags not available during factory)
124
- let resolvedSsh: { remote: string; remoteCwd: string } | null = null;
125
-
126
- const getSsh = () => resolvedSsh;
127
-
128
- pi.registerTool({
129
- ...localRead,
130
- async execute(id, params, signal, onUpdate, _ctx) {
131
- const ssh = getSsh();
132
- if (ssh) {
133
- const tool = createReadTool(localCwd, {
134
- operations: createRemoteReadOps(ssh.remote, ssh.remoteCwd, localCwd),
135
- });
136
- return tool.execute(id, params, signal, onUpdate);
137
- }
138
- return localRead.execute(id, params, signal, onUpdate);
139
- },
140
- });
141
-
142
- pi.registerTool({
143
- ...localWrite,
144
- async execute(id, params, signal, onUpdate, _ctx) {
145
- const ssh = getSsh();
146
- if (ssh) {
147
- const tool = createWriteTool(localCwd, {
148
- operations: createRemoteWriteOps(ssh.remote, ssh.remoteCwd, localCwd),
149
- });
150
- return tool.execute(id, params, signal, onUpdate);
151
- }
152
- return localWrite.execute(id, params, signal, onUpdate);
153
- },
154
- });
155
-
156
- pi.registerTool({
157
- ...localEdit,
158
- async execute(id, params, signal, onUpdate, _ctx) {
159
- const ssh = getSsh();
160
- if (ssh) {
161
- const tool = createEditTool(localCwd, {
162
- operations: createRemoteEditOps(ssh.remote, ssh.remoteCwd, localCwd),
163
- });
164
- return tool.execute(id, params, signal, onUpdate);
165
- }
166
- return localEdit.execute(id, params, signal, onUpdate);
167
- },
168
- });
169
-
170
- pi.registerTool({
171
- ...localBash,
172
- async execute(id, params, signal, onUpdate, _ctx) {
173
- const ssh = getSsh();
174
- if (ssh) {
175
- const tool = createBashTool(localCwd, {
176
- operations: createRemoteBashOps(ssh.remote, ssh.remoteCwd, localCwd),
177
- });
178
- return tool.execute(id, params, signal, onUpdate);
179
- }
180
- return localBash.execute(id, params, signal, onUpdate);
181
- },
182
- });
183
-
184
- pi.on("session_start", async (_event, ctx) => {
185
- // Resolve SSH config now that CLI flags are available
186
- const arg = pi.getFlag("ssh") as string | undefined;
187
- if (arg) {
188
- if (arg.includes(":")) {
189
- const [remote, path] = arg.split(":");
190
- resolvedSsh = { remote, remoteCwd: path };
191
- } else {
192
- // No path given, evaluate pwd on remote
193
- const remote = arg;
194
- const pwd = (await sshExec(remote, "pwd")).toString().trim();
195
- resolvedSsh = { remote, remoteCwd: pwd };
196
- }
197
- ctx.ui.setStatus("ssh", ctx.ui.theme.fg("accent", `SSH: ${resolvedSsh.remote}:${resolvedSsh.remoteCwd}`));
198
- ctx.ui.notify(`SSH mode: ${resolvedSsh.remote}:${resolvedSsh.remoteCwd}`, "info");
199
- }
200
- });
201
-
202
- // Handle user ! commands via SSH
203
- pi.on("user_bash", (_event) => {
204
- const ssh = getSsh();
205
- if (!ssh) return; // No SSH, use local execution
206
- return { operations: createRemoteBashOps(ssh.remote, ssh.remoteCwd, localCwd) };
207
- });
208
-
209
- // Replace local cwd with remote cwd in system prompt
210
- pi.on("before_agent_start", async (event) => {
211
- const ssh = getSsh();
212
- if (ssh) {
213
- const modified = event.systemPrompt.replace(
214
- `Current working directory: ${localCwd}`,
215
- `Current working directory: ${ssh.remoteCwd} (via SSH: ${ssh.remote})`,
216
- );
217
- return { systemPrompt: modified };
218
- }
219
- });
220
- }