@tpmjs/tools-sprites-exec 0.1.1 → 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 +88 -0
- package/dist/index.js +105 -21
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @tpmjs/sprites-exec
|
|
2
|
+
|
|
3
|
+
Execute a command inside a sprite and return the output.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @tpmjs/sprites-exec
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- `SPRITES_TOKEN` environment variable - Get your token from https://sprites.dev
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { spritesExecTool } from '@tpmjs/sprites-exec';
|
|
19
|
+
|
|
20
|
+
// Run a simple command
|
|
21
|
+
const result = await spritesExecTool.execute({
|
|
22
|
+
name: 'my-sandbox',
|
|
23
|
+
cmd: 'ls -la /home'
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
console.log(result);
|
|
27
|
+
// {
|
|
28
|
+
// exitCode: 0,
|
|
29
|
+
// stdout: 'total 4\ndrwxr-xr-x 2 root root 4096 Jan 15 10:30 .\n...',
|
|
30
|
+
// stderr: '',
|
|
31
|
+
// duration: 45
|
|
32
|
+
// }
|
|
33
|
+
|
|
34
|
+
// Run with stdin input
|
|
35
|
+
const pythonResult = await spritesExecTool.execute({
|
|
36
|
+
name: 'my-sandbox',
|
|
37
|
+
cmd: 'python3',
|
|
38
|
+
stdin: 'print("Hello from stdin!")'
|
|
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"'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Input Parameters
|
|
60
|
+
|
|
61
|
+
| Parameter | Type | Required | Description |
|
|
62
|
+
|-----------|------|----------|-------------|
|
|
63
|
+
| `name` | `string` | Yes | Name of the sprite to execute command in |
|
|
64
|
+
| `cmd` | `string` | Yes | Command to execute (e.g., 'ls -la', 'python script.py') |
|
|
65
|
+
| `stdin` | `string` | No | Optional stdin input to pass to the command |
|
|
66
|
+
| `timeoutMs` | `number` | No | Execution timeout in milliseconds (default: 60000) |
|
|
67
|
+
|
|
68
|
+
## Output
|
|
69
|
+
|
|
70
|
+
| Field | Type | Description |
|
|
71
|
+
|-------|------|-------------|
|
|
72
|
+
| `exitCode` | `number` | Command exit code (0 = success) |
|
|
73
|
+
| `stdout` | `string` | Standard output from the command |
|
|
74
|
+
| `stderr` | `string` | Standard error from the command |
|
|
75
|
+
| `duration` | `number` | Execution duration in milliseconds |
|
|
76
|
+
|
|
77
|
+
## Error Handling
|
|
78
|
+
|
|
79
|
+
The tool throws errors in these cases:
|
|
80
|
+
- `SPRITES_TOKEN` environment variable is not set
|
|
81
|
+
- Sprite not found (HTTP 404)
|
|
82
|
+
- Command execution timeout
|
|
83
|
+
- Invalid or expired API token (HTTP 401)
|
|
84
|
+
- Network errors with descriptive messages
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
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,31 +128,36 @@ 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);
|
|
68
157
|
} catch (error) {
|
|
69
158
|
if (error instanceof Error) {
|
|
70
159
|
if (error.name === "AbortError") {
|
|
71
|
-
throw new Error(
|
|
72
|
-
`Command execution in sprite "${name}" timed out after ${timeout}ms`
|
|
73
|
-
);
|
|
160
|
+
throw new Error(`Command execution in sprite "${name}" timed out after ${timeout}ms`);
|
|
74
161
|
}
|
|
75
162
|
throw new Error(`Failed to execute command in sprite "${name}": ${error.message}`);
|
|
76
163
|
}
|
|
@@ -89,17 +176,14 @@ var spritesExecTool = tool({
|
|
|
89
176
|
`Failed to execute command in sprite "${name}": HTTP ${response.status} - ${errorText}`
|
|
90
177
|
);
|
|
91
178
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
} catch {
|
|
96
|
-
throw new Error("Failed to parse response from Sprites API");
|
|
97
|
-
}
|
|
179
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
180
|
+
const buffer = new Uint8Array(arrayBuffer);
|
|
181
|
+
const { stdout, stderr, exitCode } = parseBinaryResponse(buffer);
|
|
98
182
|
return {
|
|
99
|
-
exitCode
|
|
100
|
-
stdout
|
|
101
|
-
stderr
|
|
102
|
-
duration
|
|
183
|
+
exitCode,
|
|
184
|
+
stdout,
|
|
185
|
+
stderr,
|
|
186
|
+
duration
|
|
103
187
|
};
|
|
104
188
|
}
|
|
105
189
|
});
|