@fleetagent/pi-coding-agent 0.0.9 → 0.0.10
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/CHANGELOG.md +23 -0
- package/README.md +9 -0
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +18 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +12 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +27 -6
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts +3 -3
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +3 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/pi-agent.d.ts +2 -0
- package/dist/core/pi-agent.d.ts.map +1 -1
- package/dist/core/pi-agent.js +2 -0
- package/dist/core/pi-agent.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/bash.d.ts +6 -29
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +23 -97
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +3 -2
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +6 -8
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +3 -16
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +7 -13
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +3 -17
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +11 -23
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +3 -14
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +93 -41
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +17 -15
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +53 -52
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +3 -20
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +9 -20
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/operations.d.ts +145 -0
- package/dist/core/tools/operations.d.ts.map +1 -0
- package/dist/core/tools/operations.js +418 -0
- package/dist/core/tools/operations.js.map +1 -0
- package/dist/core/tools/read.d.ts +3 -16
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +6 -13
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts +3 -14
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +6 -10
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +33 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +2 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +52 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +9 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +14 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +9 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +22 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/extensions.md +1 -2
- package/examples/extensions/README.md +0 -1
- package/examples/extensions/bash-spawn-hook.ts +2 -2
- package/examples/extensions/built-in-tool-renderer.ts +12 -5
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +9 -7
- package/examples/extensions/sandbox/index.ts +55 -56
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
- package/examples/extensions/ssh.ts +0 -220
|
@@ -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(
|
|
49
|
-
bash: createBashTool(
|
|
50
|
-
edit: createEditTool(
|
|
51
|
-
write: createWriteTool(
|
|
52
|
-
find: createFindTool(
|
|
53
|
-
grep: createGrepTool(
|
|
54
|
-
ls: createLsTool(
|
|
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 {
|
|
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():
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
reject(err);
|
|
170
|
-
});
|
|
149
|
+
let timedOut = false;
|
|
150
|
+
let timeoutHandle: NodeJS.Timeout | undefined;
|
|
171
151
|
|
|
172
|
-
|
|
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
|
-
|
|
165
|
+
child.stdout?.on("data", options.onData);
|
|
166
|
+
child.stderr?.on("data", options.onData);
|
|
183
167
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
168
|
+
child.on("error", (err) => {
|
|
169
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
170
|
+
reject(err);
|
|
171
|
+
});
|
|
187
172
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
|
|
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) => {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetagent/pi-coding-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
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
|
+
"version": "0.0.10",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@fleetagent/pi-agent-core": "^0.0.
|
|
13
|
-
"@fleetagent/pi-ai": "^0.0.
|
|
14
|
-
"@fleetagent/pi-tui": "^0.0.
|
|
12
|
+
"@fleetagent/pi-agent-core": "^0.0.10",
|
|
13
|
+
"@fleetagent/pi-ai": "^0.0.10",
|
|
14
|
+
"@fleetagent/pi-tui": "^0.0.10",
|
|
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.
|
|
477
|
-
"resolved": "https://registry.npmjs.org/@fleetagent/pi-agent-core/-/pi-agent-core-0.0.
|
|
476
|
+
"version": "0.0.10",
|
|
477
|
+
"resolved": "https://registry.npmjs.org/@fleetagent/pi-agent-core/-/pi-agent-core-0.0.10.tgz",
|
|
478
478
|
"license": "MIT",
|
|
479
479
|
"dependencies": {
|
|
480
|
-
"@fleetagent/pi-ai": "^0.0.
|
|
480
|
+
"@fleetagent/pi-ai": "^0.0.10",
|
|
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.
|
|
491
|
-
"resolved": "https://registry.npmjs.org/@fleetagent/pi-ai/-/pi-ai-0.0.
|
|
490
|
+
"version": "0.0.10",
|
|
491
|
+
"resolved": "https://registry.npmjs.org/@fleetagent/pi-ai/-/pi-ai-0.0.10.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.
|
|
514
|
-
"resolved": "https://registry.npmjs.org/@fleetagent/pi-tui/-/pi-tui-0.0.
|
|
513
|
+
"version": "0.0.10",
|
|
514
|
+
"resolved": "https://registry.npmjs.org/@fleetagent/pi-tui/-/pi-tui-0.0.10.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.
|
|
3
|
+
"version": "0.0.10",
|
|
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.
|
|
43
|
-
"@fleetagent/pi-ai": "^0.0.
|
|
44
|
-
"@fleetagent/pi-tui": "^0.0.
|
|
42
|
+
"@fleetagent/pi-agent-core": "^0.0.10",
|
|
43
|
+
"@fleetagent/pi-ai": "^0.0.10",
|
|
44
|
+
"@fleetagent/pi-tui": "^0.0.10",
|
|
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
|
-
}
|