@tpmjs/tools-sprites-exec 0.1.2 → 0.1.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 +17 -0
- package/dist/index.js +104 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,23 @@ const pythonResult = await spritesExecTool.execute({
|
|
|
37
37
|
cmd: 'python3',
|
|
38
38
|
stdin: 'print("Hello from stdin!")'
|
|
39
39
|
});
|
|
40
|
+
|
|
41
|
+
// For shell features (pipes, redirects, etc), use bash -c
|
|
42
|
+
const shellResult = await spritesExecTool.execute({
|
|
43
|
+
name: 'my-sandbox',
|
|
44
|
+
cmd: 'bash -c "echo hello > /tmp/test.txt && cat /tmp/test.txt"'
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Shell Commands
|
|
49
|
+
|
|
50
|
+
Commands are executed directly (like `exec.Command` in Go), not through a shell. This means shell operators like `|`, `>`, `>>`, `&&` won't work directly.
|
|
51
|
+
|
|
52
|
+
For shell features, wrap your command with `bash -c`:
|
|
53
|
+
```typescript
|
|
54
|
+
// Won't work: cmd: 'echo hello > file.txt'
|
|
55
|
+
// Use instead:
|
|
56
|
+
cmd: 'bash -c "echo hello > file.txt"'
|
|
40
57
|
```
|
|
41
58
|
|
|
42
59
|
## Input Parameters
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,10 @@ import { tool, jsonSchema } from 'ai';
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
var SPRITES_API_BASE = "https://api.sprites.dev/v1";
|
|
5
|
+
var STREAM_STDIN = 0;
|
|
6
|
+
var STREAM_STDOUT = 1;
|
|
7
|
+
var STREAM_STDERR = 2;
|
|
8
|
+
var STREAM_EXIT = 3;
|
|
5
9
|
function getSpritesToken() {
|
|
6
10
|
const token = process.env.SPRITES_TOKEN;
|
|
7
11
|
if (!token) {
|
|
@@ -11,6 +15,84 @@ function getSpritesToken() {
|
|
|
11
15
|
}
|
|
12
16
|
return token;
|
|
13
17
|
}
|
|
18
|
+
function parseCommand(cmdString) {
|
|
19
|
+
const args = [];
|
|
20
|
+
let current = "";
|
|
21
|
+
let inSingleQuote = false;
|
|
22
|
+
let inDoubleQuote = false;
|
|
23
|
+
let escaped = false;
|
|
24
|
+
for (let i = 0; i < cmdString.length; i++) {
|
|
25
|
+
const char = cmdString[i];
|
|
26
|
+
if (escaped) {
|
|
27
|
+
current += char;
|
|
28
|
+
escaped = false;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (char === "\\" && !inSingleQuote) {
|
|
32
|
+
escaped = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (char === "'" && !inDoubleQuote) {
|
|
36
|
+
inSingleQuote = !inSingleQuote;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (char === '"' && !inSingleQuote) {
|
|
40
|
+
inDoubleQuote = !inDoubleQuote;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (char === " " && !inSingleQuote && !inDoubleQuote) {
|
|
44
|
+
if (current.length > 0) {
|
|
45
|
+
args.push(current);
|
|
46
|
+
current = "";
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
current += char;
|
|
51
|
+
}
|
|
52
|
+
if (current.length > 0) {
|
|
53
|
+
args.push(current);
|
|
54
|
+
}
|
|
55
|
+
return args;
|
|
56
|
+
}
|
|
57
|
+
function parseBinaryResponse(buffer) {
|
|
58
|
+
let stdout = "";
|
|
59
|
+
let stderr = "";
|
|
60
|
+
let exitCode = 0;
|
|
61
|
+
const decoder = new TextDecoder();
|
|
62
|
+
let i = 0;
|
|
63
|
+
while (i < buffer.length) {
|
|
64
|
+
const streamId = buffer[i];
|
|
65
|
+
i++;
|
|
66
|
+
if (streamId === STREAM_EXIT) {
|
|
67
|
+
if (i < buffer.length) {
|
|
68
|
+
exitCode = buffer[i];
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
let end = i;
|
|
74
|
+
while (end < buffer.length) {
|
|
75
|
+
const nextByte = buffer[end];
|
|
76
|
+
if (nextByte <= STREAM_EXIT) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
end++;
|
|
80
|
+
}
|
|
81
|
+
const payload = decoder.decode(buffer.slice(i, end));
|
|
82
|
+
switch (streamId) {
|
|
83
|
+
case STREAM_STDIN:
|
|
84
|
+
break;
|
|
85
|
+
case STREAM_STDOUT:
|
|
86
|
+
stdout += payload;
|
|
87
|
+
break;
|
|
88
|
+
case STREAM_STDERR:
|
|
89
|
+
stderr += payload;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
i = end;
|
|
93
|
+
}
|
|
94
|
+
return { stdout, stderr, exitCode };
|
|
95
|
+
}
|
|
14
96
|
var spritesExecTool = tool({
|
|
15
97
|
description: "Execute a command inside a sprite and return the output. Supports stdin input for interactive commands. Returns exit code, stdout, stderr, and execution duration.",
|
|
16
98
|
inputSchema: jsonSchema({
|
|
@@ -46,22 +128,29 @@ var spritesExecTool = tool({
|
|
|
46
128
|
const token = getSpritesToken();
|
|
47
129
|
const timeout = timeoutMs || 6e4;
|
|
48
130
|
const startTime = Date.now();
|
|
131
|
+
const cmdParts = parseCommand(cmd);
|
|
132
|
+
if (cmdParts.length === 0) {
|
|
133
|
+
throw new Error("Command cannot be empty");
|
|
134
|
+
}
|
|
135
|
+
const url = new URL(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/exec`);
|
|
136
|
+
for (const part of cmdParts) {
|
|
137
|
+
url.searchParams.append("cmd", part);
|
|
138
|
+
}
|
|
139
|
+
if (stdin) {
|
|
140
|
+
url.searchParams.set("stdin", "true");
|
|
141
|
+
}
|
|
49
142
|
let response;
|
|
50
143
|
try {
|
|
51
144
|
const controller = new AbortController();
|
|
52
145
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
53
|
-
|
|
54
|
-
if (stdin) {
|
|
55
|
-
body.stdin = stdin;
|
|
56
|
-
}
|
|
57
|
-
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/exec`, {
|
|
146
|
+
response = await fetch(url.toString(), {
|
|
58
147
|
method: "POST",
|
|
59
148
|
headers: {
|
|
60
149
|
Authorization: `Bearer ${token}`,
|
|
61
|
-
"
|
|
62
|
-
"
|
|
150
|
+
"User-Agent": "TPMJS/1.0",
|
|
151
|
+
...stdin ? { "Content-Type": "application/octet-stream" } : {}
|
|
63
152
|
},
|
|
64
|
-
body:
|
|
153
|
+
body: stdin || void 0,
|
|
65
154
|
signal: controller.signal
|
|
66
155
|
});
|
|
67
156
|
clearTimeout(timeoutId);
|
|
@@ -87,17 +176,14 @@ var spritesExecTool = tool({
|
|
|
87
176
|
`Failed to execute command in sprite "${name}": HTTP ${response.status} - ${errorText}`
|
|
88
177
|
);
|
|
89
178
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
} catch {
|
|
94
|
-
throw new Error("Failed to parse response from Sprites API");
|
|
95
|
-
}
|
|
179
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
180
|
+
const buffer = new Uint8Array(arrayBuffer);
|
|
181
|
+
const { stdout, stderr, exitCode } = parseBinaryResponse(buffer);
|
|
96
182
|
return {
|
|
97
|
-
exitCode
|
|
98
|
-
stdout
|
|
99
|
-
stderr
|
|
100
|
-
duration
|
|
183
|
+
exitCode,
|
|
184
|
+
stdout,
|
|
185
|
+
stderr,
|
|
186
|
+
duration
|
|
101
187
|
};
|
|
102
188
|
}
|
|
103
189
|
});
|