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",
"version": "0.1.2",
"version": "0.1.4",
"description": "Create a point-in-time snapshot of a sprite's filesystem state for later restoration",
"type": "module",
"keywords": [

View file

@ -66,19 +66,18 @@ export const spritesCheckpointCreateTool = tool({
body.name = checkpointName;
}
response = await fetch(
`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/checkpoints`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'User-Agent': 'TPMJS/1.0',
},
body: JSON.stringify(body),
signal: controller.signal,
}
);
// API endpoint: POST /v1/sprites/{name}/checkpoint (singular, not plural)
// Note: Returns application/x-ndjson streaming progress events
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/checkpoint`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'User-Agent': 'TPMJS/1.0',
},
body: JSON.stringify(body),
signal: controller.signal,
});
clearTimeout(timeoutId);
} 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 {
data = (await response.json()) as Record<string, unknown>;
text = await response.text();
} 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 {
id: (data.id as string) || '',
name: (data.name as string) || checkpointName,
createdAt:
(data.createdAt as string) || (data.created_at as string) || new Date().toISOString(),
size: data.size as number | undefined,
id: checkpointId,
name: checkpointName || checkpointId,
createdAt: createdAt || new Date().toISOString(),
size: undefined,
};
},
});

View file

@ -1,6 +1,6 @@
{
"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",
"type": "module",
"keywords": [

View file

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

View file

@ -1,6 +1,6 @@
{
"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",
"type": "module",
"keywords": [

View file

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

View file

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

View file

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