@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.
Files changed (3) hide show
  1. package/README.md +88 -0
  2. package/dist/index.js +105 -21
  3. 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
- const body = { cmd };
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
- "Content-Type": "application/json",
62
- "User-Agent": "TPMJS/1.0"
150
+ "User-Agent": "TPMJS/1.0",
151
+ ...stdin ? { "Content-Type": "application/octet-stream" } : {}
63
152
  },
64
- body: JSON.stringify(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
- let data;
93
- try {
94
- data = await response.json();
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: data.exitCode ?? data.exit_code ?? 0,
100
- stdout: data.stdout || "",
101
- stderr: data.stderr || "",
102
- duration: data.duration || duration
183
+ exitCode,
184
+ stdout,
185
+ stderr,
186
+ duration
103
187
  };
104
188
  }
105
189
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpmjs/tools-sprites-exec",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Execute a command inside a sprite and return the output with exit code",
5
5
  "type": "module",
6
6
  "keywords": [