fix: correct API endpoints for sprites tools v0.1.3/v0.1.4

Fixed URL endpoints based on Sprites API documentation:
- sprites-sessions: /exec/sessions -> /exec (v0.1.3)
- sprites-policy-get: /policies -> /policy/network (v0.1.3)
- sprites-policy-set: /policies -> /policy/network with rules[] body (v0.1.3)
- sprites-checkpoint-create: /checkpoints -> /checkpoint singular (v0.1.4)

Also fixed checkpoint-create to properly parse NDJSON streaming
response format with type/data/time fields.
This commit is contained in:
Ajax Davis 2026-01-15 04:30:52 +10:00
parent e6857e245e
commit e4c09a475f
8 changed files with 118 additions and 54 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "@tpmjs/tools-sprites-checkpoint-create", "name": "@tpmjs/tools-sprites-checkpoint-create",
"version": "0.1.2", "version": "0.1.4",
"description": "Create a point-in-time snapshot of a sprite's filesystem state for later restoration", "description": "Create a point-in-time snapshot of a sprite's filesystem state for later restoration",
"type": "module", "type": "module",
"keywords": [ "keywords": [

View file

@ -66,19 +66,18 @@ export const spritesCheckpointCreateTool = tool({
body.name = checkpointName; body.name = checkpointName;
} }
response = await fetch( // API endpoint: POST /v1/sprites/{name}/checkpoint (singular, not plural)
`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/checkpoints`, // Note: Returns application/x-ndjson streaming progress events
{ response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/checkpoint`, {
method: 'POST', method: 'POST',
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'User-Agent': 'TPMJS/1.0', 'User-Agent': 'TPMJS/1.0',
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
signal: controller.signal, signal: controller.signal,
} });
);
clearTimeout(timeoutId); clearTimeout(timeoutId);
} catch (error) { } catch (error) {
@ -104,19 +103,71 @@ export const spritesCheckpointCreateTool = tool({
); );
} }
let data: Record<string, unknown>; // API returns application/x-ndjson streaming progress events
// Each line is a JSON object, we need to parse all lines and find the result
let text: string;
try { try {
data = (await response.json()) as Record<string, unknown>; text = await response.text();
} catch { } catch {
throw new Error('Failed to parse response from Sprites API'); throw new Error('Failed to read response from Sprites API');
}
// Parse NDJSON - split by newlines and parse each line as JSON
// Format: {"type":"info"|"complete"|"error","data":"...","time":"..."}
// The checkpoint ID is in lines like:
// {"type":"info","data":" ID: v2",...}
// {"type":"complete","data":"Checkpoint v2 created successfully",...}
const lines = text
.trim()
.split('\n')
.filter((line) => line.trim());
let checkpointId = '';
let createdAt = '';
for (const line of lines) {
try {
const parsed = JSON.parse(line) as { type?: string; data?: string; time?: string };
// Check for error
if (parsed.type === 'error') {
throw new Error(`Checkpoint creation failed: ${parsed.data || 'Unknown error'}`);
}
// Extract checkpoint ID from " ID: v2" line
if (parsed.type === 'info' && parsed.data?.trim().startsWith('ID:')) {
checkpointId = parsed.data.trim().replace('ID:', '').trim();
}
// Extract created time from " Created: 2026-01-14 18:23:26" line
if (parsed.type === 'info' && parsed.data?.trim().startsWith('Created:')) {
createdAt = parsed.data.trim().replace('Created:', '').trim();
}
// Also try to extract from complete message like "Checkpoint v2 created successfully"
if (parsed.type === 'complete' && parsed.data && !checkpointId) {
const match = parsed.data.match(/Checkpoint\s+(\S+)\s+created/);
if (match && match[1]) {
checkpointId = match[1];
}
}
} catch (parseError) {
// Skip lines that aren't valid JSON (could be progress messages)
if (parseError instanceof SyntaxError) continue;
throw parseError;
}
}
if (!checkpointId) {
throw new Error(
`Failed to extract checkpoint ID from response. Raw response: ${text.slice(0, 300)}`
);
} }
return { return {
id: (data.id as string) || '', id: checkpointId,
name: (data.name as string) || checkpointName, name: checkpointName || checkpointId,
createdAt: createdAt: createdAt || new Date().toISOString(),
(data.createdAt as string) || (data.created_at as string) || new Date().toISOString(), size: undefined,
size: data.size as number | undefined,
}; };
}, },
}); });

View file

@ -1,6 +1,6 @@
{ {
"name": "@tpmjs/tools-sprites-policy-get", "name": "@tpmjs/tools-sprites-policy-get",
"version": "0.1.2", "version": "0.1.3",
"description": "Retrieve the current network policy for a sprite including allowed domains", "description": "Retrieve the current network policy for a sprite including allowed domains",
"type": "module", "type": "module",
"keywords": [ "keywords": [

View file

@ -55,14 +55,18 @@ export const spritesPolicyGetTool = tool({
const controller = new AbortController(); const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); const timeoutId = setTimeout(() => controller.abort(), 30000);
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/policies`, { // API endpoint: GET /v1/sprites/{name}/policy/network
method: 'GET', response = await fetch(
headers: { `${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/policy/network`,
Authorization: `Bearer ${token}`, {
'User-Agent': 'TPMJS/1.0', method: 'GET',
}, headers: {
signal: controller.signal, Authorization: `Bearer ${token}`,
}); 'User-Agent': 'TPMJS/1.0',
},
signal: controller.signal,
}
);
clearTimeout(timeoutId); clearTimeout(timeoutId);
} catch (error) { } catch (error) {

View file

@ -1,6 +1,6 @@
{ {
"name": "@tpmjs/tools-sprites-policy-set", "name": "@tpmjs/tools-sprites-policy-set",
"version": "0.1.2", "version": "0.1.3",
"description": "Update the network policy for a sprite to control outbound network access", "description": "Update the network policy for a sprite to control outbound network access",
"type": "module", "type": "module",
"keywords": [ "keywords": [

View file

@ -79,16 +79,27 @@ export const spritesPolicySetTool = tool({
const controller = new AbortController(); const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); const timeoutId = setTimeout(() => controller.abort(), 30000);
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/policies`, { // API endpoint: POST /v1/sprites/{name}/policy/network
method: 'POST', // Body format: { "rules": [{ "domain": "...", "action": "allow" | "deny" }] }
headers: { // Convert our mode/domains format to rules array
Authorization: `Bearer ${token}`, const rules = domains.map((domain) => ({
'Content-Type': 'application/json', domain,
'User-Agent': 'TPMJS/1.0', action: mode, // 'allow' or 'deny'
}, }));
body: JSON.stringify({ mode, domains }),
signal: controller.signal, response = await fetch(
}); `${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/policy/network`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'User-Agent': 'TPMJS/1.0',
},
body: JSON.stringify({ rules }),
signal: controller.signal,
}
);
clearTimeout(timeoutId); clearTimeout(timeoutId);
} catch (error) { } catch (error) {

View file

@ -1,6 +1,6 @@
{ {
"name": "@tpmjs/tools-sprites-sessions", "name": "@tpmjs/tools-sprites-sessions",
"version": "0.1.2", "version": "0.1.3",
"description": "List active execution sessions for a sprite", "description": "List active execution sessions for a sprite",
"type": "module", "type": "module",
"keywords": [ "keywords": [

View file

@ -61,17 +61,15 @@ export const spritesSessionsTool = tool({
const controller = new AbortController(); const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); const timeoutId = setTimeout(() => controller.abort(), 30000);
response = await fetch( // API endpoint: GET /v1/sprites/{name}/exec lists active exec sessions
`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/exec/sessions`, response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/exec`, {
{ method: 'GET',
method: 'GET', headers: {
headers: { Authorization: `Bearer ${token}`,
Authorization: `Bearer ${token}`, 'User-Agent': 'TPMJS/1.0',
'User-Agent': 'TPMJS/1.0', },
}, signal: controller.signal,
signal: controller.signal, });
}
);
clearTimeout(timeoutId); clearTimeout(timeoutId);
} catch (error) { } catch (error) {