Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@astroanywhere/cli

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@astroanywhere/cli - npm Package Compare versions

Comparing version
0.6.2
to
0.6.3
+641
dist/chunk-C7RPJATV.js
#!/usr/bin/env node
// src/config.ts
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
import { join } from "path";
import { homedir } from "os";
var CONFIG_DIR = join(homedir(), ".astro");
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
var DEFAULT_SERVER_URL = "https://api.astroanywhere.com";
function loadConfig() {
const envToken = process.env.ASTRO_AUTH_TOKEN;
const envServerUrl = process.env.ASTRO_SERVER_URL;
try {
if (existsSync(CONFIG_FILE)) {
const raw = readFileSync(CONFIG_FILE, "utf-8");
const file = JSON.parse(raw);
return {
serverUrl: DEFAULT_SERVER_URL,
...file,
// Env vars override file — agent-runner always has a live token
...envToken ? { authToken: envToken } : {},
...envServerUrl ? { serverUrl: envServerUrl } : {}
};
}
} catch {
}
return {
serverUrl: envServerUrl ?? DEFAULT_SERVER_URL,
...envToken ? { authToken: envToken } : {}
};
}
function saveConfig(updates) {
mkdirSync(CONFIG_DIR, { recursive: true });
const current = loadConfig();
const merged = { ...current, ...updates };
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", { mode: 384 });
}
function clearAuth() {
const current = loadConfig();
delete current.authToken;
delete current.refreshToken;
mkdirSync(CONFIG_DIR, { recursive: true });
writeFileSync(CONFIG_FILE, JSON.stringify(current, null, 2) + "\n", { mode: 384 });
}
function resetConfig() {
mkdirSync(CONFIG_DIR, { recursive: true });
writeFileSync(CONFIG_FILE, JSON.stringify({ serverUrl: DEFAULT_SERVER_URL }, null, 2) + "\n", { mode: 384 });
}
function getServerUrl(cliOverride) {
if (cliOverride) return cliOverride;
if (process.env.ASTRO_SERVER_URL) return process.env.ASTRO_SERVER_URL;
const config = loadConfig();
return config.serverUrl;
}
// src/client.ts
var AstroClient = class {
baseUrl;
headers;
constructor(opts) {
this.baseUrl = getServerUrl(opts?.serverUrl);
const config = loadConfig();
this.headers = {
"Content-Type": "application/json",
...config.authToken ? { Authorization: `Bearer ${config.authToken}` } : {}
};
}
async refreshAccessToken() {
const config = loadConfig();
if (!config.refreshToken) return false;
try {
const url = new URL("/api/device/refresh", this.baseUrl);
const res = await fetch(url.toString(), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refreshToken: config.refreshToken, grantType: "refresh_token" })
});
if (!res.ok) return false;
const data = await res.json();
saveConfig({
authToken: data.accessToken,
...data.refreshToken ? { refreshToken: data.refreshToken } : {}
});
this.headers["Authorization"] = `Bearer ${data.accessToken}`;
return true;
} catch (err) {
console.error(`Token refresh failed: ${err instanceof Error ? err.message : String(err)}`);
return false;
}
}
async request(method, path, body, params) {
const url = new URL(path, this.baseUrl);
if (params) {
for (const [k, v] of Object.entries(params)) {
if (v !== void 0) url.searchParams.set(k, v);
}
}
let res = await fetch(url.toString(), {
method,
headers: this.headers,
body: body ? JSON.stringify(body) : void 0
});
if (res.status === 401) {
const refreshed = await this.refreshAccessToken();
if (refreshed) {
res = await fetch(url.toString(), {
method,
headers: this.headers,
body: body ? JSON.stringify(body) : void 0
});
}
}
if (!res.ok) {
const text = await res.text();
let message = text;
try {
const parsed = JSON.parse(text);
if (typeof parsed.error === "string") message = parsed.error;
else if (typeof parsed.message === "string") message = parsed.message;
} catch {
}
const hint = res.status === 400 && /invalid.*id/i.test(message) ? " (pass a full UUID or a unique prefix \u2014 e.g. the first 8 chars)" : res.status === 401 ? " \u2014 run `astro-cli auth login` to re-authenticate" : res.status === 403 ? " \u2014 you do not have access to this resource" : res.status === 404 ? " \u2014 resource not found" : "";
throw new Error(`${message}${hint}`);
}
return res.json();
}
get(path, params) {
return this.request("GET", path, void 0, params);
}
post(path, body) {
return this.request("POST", path, body);
}
del(path) {
return this.request("DELETE", path);
}
// ── Projects ────────────────────────────────────────────────────────
async listProjects() {
return this.get("/api/data/projects");
}
async getProject(id) {
return this.get(`/api/data/projects/${id}`);
}
async createProject(data) {
return this.post("/api/data/projects", data);
}
async deleteProject(id) {
return this.del(`/api/data/projects/${id}`);
}
/**
* Resolve a partial project ID to a full project.
* Lists all projects and finds the one matching the prefix.
* Throws if 0 or >1 matches.
*/
async resolveProject(partialId) {
const projects = await this.listProjects();
const matches = projects.filter((p) => p.id.startsWith(partialId));
if (matches.length === 0) throw new Error(`No project found matching "${partialId}"`);
if (matches.length > 1) throw new Error(`Ambiguous ID "${partialId}" matches ${matches.length} projects`);
return matches[0];
}
async getProjectNotes(projectId) {
return this.get(`/api/data/projects/${projectId}/notes`);
}
// ── Plan ────────────────────────────────────────────────────────────
async getPlan(projectId) {
const result = await this.get(`/api/data/plan/${projectId}`);
return {
nodes: result.nodes ?? [],
edges: result.edges ?? []
};
}
async getFullPlan() {
const result = await this.get("/api/data/plan");
return {
nodes: result.nodes ?? [],
edges: result.edges ?? []
};
}
// ── Executions ──────────────────────────────────────────────────────
/**
* Get executions map keyed by nodeClientId.
* Server returns Record<nodeClientId, Execution>.
*/
async getExecutions() {
return this.get("/api/data/executions");
}
// ── Tool Traces & File Changes ─────────────────────────────────────
async listToolTraces(executionId) {
return this.get("/api/data/tool-traces", { executionId });
}
async listFileChanges(executionId) {
return this.get("/api/data/file-changes", { executionId });
}
// ── Usage / Cost ────────────────────────────────────────────────────
async getUsageHistory(weeks = 1) {
return this.get("/api/data/usage/history", { weeks: String(weeks) });
}
// ── Activity ────────────────────────────────────────────────────────
async listActivities(params) {
return this.get("/api/data/activities", params);
}
// ── Machines ────────────────────────────────────────────────────────
async listMachines() {
const result = await this.get("/api/device/machines");
return result.machines ?? [];
}
async getMachine(id) {
return this.get(`/api/device/machines/${id}`);
}
async revokeMachine(id) {
return this.del(`/api/device/machines/${id}`);
}
/**
* Resolve a partial machine ID to a full machine.
* Lists all machines and finds the one matching the prefix.
*/
async resolveMachine(partialId) {
const machines = await this.listMachines();
const matches = machines.filter((m) => !m.isRevoked && m.id.startsWith(partialId));
if (matches.length === 0) throw new Error(`No active machine found matching "${partialId}"`);
if (matches.length > 1) throw new Error(`Ambiguous ID "${partialId}" matches ${matches.length} machines`);
return matches[0];
}
// ── Observations ────────────────────────────────────────────────────
async listObservations(executionId) {
return this.get("/api/observations", { executionId });
}
async getObservationStats(executionId) {
return this.get("/api/observations/stats", { executionId });
}
// ── Search ──────────────────────────────────────────────────────────
async search(query) {
return this.get("/api/data/search", { q: query });
}
// ── References ──────────────────────────────────────────────────────
async searchReferences(query, projectId) {
const result = await this.get("/api/references/search", {
q: query,
...projectId ? { projectId } : {}
});
return result.references ?? [];
}
async listReferences(projectId) {
const result = await this.get("/api/references", projectId ? { projectId } : {});
return result.references ?? [];
}
async exportBibTeX(projectId) {
const url = new URL("/api/references/export", this.baseUrl);
url.searchParams.set("format", "bibtex");
if (projectId) url.searchParams.set("projectId", projectId);
const res = await fetch(url.toString(), { headers: this.headers });
if (!res.ok) {
const text = await res.text();
throw new Error(`API error ${res.status}: ${text}`);
}
return res.text();
}
async importReferencesFromBibTeX(content, options) {
const result = await this.request(
"POST",
"/api/references/import-bibtex",
{
bibtex: content,
addedBy: options?.accept ? "human" : "ai",
...options?.projectId ? { projectId: options.projectId } : {}
}
);
return result;
}
// ── Plan CRUD ─────────────────────────────────────────────────────
async setPlan(projectId, nodes, edges, projectName) {
const executionId = process.env.ASTRO_EXECUTION_ID || void 0;
return this.request("PUT", `/api/data/plan/${projectId}`, { nodes, edges, executionId, projectName });
}
async createPlanNode(data) {
return this.post("/api/data/plan/nodes", {
type: "task",
status: "planned",
...data
});
}
async updatePlanNode(nodeId, patch) {
return this.request("PATCH", `/api/data/plan/nodes/${nodeId}`, patch);
}
async deletePlanNode(nodeId) {
return this.del(`/api/data/plan/nodes/${nodeId}`);
}
async createPlanEdge(data) {
return this.post("/api/data/plan/edges", { type: "dependency", ...data });
}
async deletePlanEdge(edgeId) {
return this.del(`/api/data/plan/edges/${edgeId}`);
}
// ── Project Update ────────────────────────────────────────────────
async updateProject(id, patch) {
return this.request("PATCH", `/api/data/projects/${id}`, patch);
}
// ── Cancel / Steer ────────────────────────────────────────────────
async cancelTask(params) {
return this.post("/api/dispatch/cancel", params);
}
async steerTask(params) {
return this.post("/api/dispatch/steer", params);
}
// ── Relay / Environment ───────────────────────────────────────────
async getRelayStatus() {
return this.get("/api/relay/status");
}
async getProviders() {
return this.get("/api/health/providers");
}
async getSlurmClusters() {
return this.get("/api/relay/slurm/clusters");
}
// ── Observations (extended) ───────────────────────────────────────
async getTraceSummary(executionId) {
const url = new URL(`/api/observations/${executionId}/summary`, this.baseUrl);
const res = await fetch(url.toString(), { headers: this.headers });
if (!res.ok) {
const text = await res.text();
throw new Error(`API error ${res.status}: ${text}`);
}
return res.text();
}
async listObservationsFiltered(params) {
return this.get("/api/observations", params);
}
// ── SSE Events Stream ─────────────────────────────────────────────
async streamEvents(params) {
const url = new URL("/api/events/stream", this.baseUrl);
if (params?.projectId) url.searchParams.set("projectId", params.projectId);
const res = await fetch(url.toString(), {
headers: {
...this.headers,
Accept: "text/event-stream"
}
});
if (!res.ok) {
const text = await res.text();
throw new Error(`SSE connect failed (${res.status}): ${text}`);
}
return res;
}
// ── Dispatch (SSE streaming) ────────────────────────────────────────
/**
* Dispatch a task for execution. Returns the raw Response for SSE streaming.
*/
async dispatchTask(payload) {
const url = new URL("/api/dispatch/task", this.baseUrl);
const res = await fetch(url.toString(), {
method: "POST",
headers: this.headers,
body: JSON.stringify(payload)
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Dispatch failed (${res.status}): ${text}`);
}
return res;
}
// ── Chat (SSE streaming) ──────────────────────────────────────────
async projectChat(payload) {
const url = new URL("/api/agent/project-chat", this.baseUrl);
const res = await fetch(url.toString(), {
method: "POST",
headers: this.headers,
body: JSON.stringify(payload)
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Project chat failed (${res.status}): ${text}`);
}
return res;
}
async taskChat(payload) {
const url = new URL("/api/agent/task-chat", this.baseUrl);
const res = await fetch(url.toString(), {
method: "POST",
headers: this.headers,
body: JSON.stringify(payload)
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Task chat failed (${res.status}): ${text}`);
}
return res;
}
async getNode(nodeId) {
try {
return await this.get(`/api/data/plan/nodes/${nodeId}`);
} catch {
return null;
}
}
// ── Approval ───────────────────────────────────────────────────────
async sendApproval(payload) {
return this.post("/api/dispatch/approval", payload);
}
// ── Summarize ──────────────────────────────────────────────────────
async summarize(payload) {
return this.post("/api/agent/summarize", payload);
}
// ── Slurm Dispatch ────────────────────────────────────────────────
async dispatchSlurmTask(payload) {
const url = new URL("/api/relay/slurm/dispatch", this.baseUrl);
const res = await fetch(url.toString(), {
method: "POST",
headers: this.headers,
body: JSON.stringify(payload)
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Slurm dispatch failed (${res.status}): ${text}`);
}
return res;
}
};
async function readSSEStream(response, handler) {
if (!response.body) return;
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let currentEvent = "";
let dataLines = [];
function flushEvent() {
if (!currentEvent) return null;
const data = dataLines.join("\n");
const event = currentEvent;
currentEvent = "";
dataLines = [];
if (event === "text") {
return { type: "text", content: data, text: data };
}
try {
const parsed = JSON.parse(data);
return { type: event, ...parsed };
} catch {
return { type: event, data };
}
}
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
if (line.startsWith("event:")) {
const pending2 = flushEvent();
if (pending2) await handler(pending2);
currentEvent = line.slice(6).trim();
} else if (line.startsWith("data:")) {
const value2 = line.slice(5);
dataLines.push(value2.startsWith(" ") ? value2.slice(1) : value2);
} else if (line === "") {
const pending2 = flushEvent();
if (pending2) await handler(pending2);
}
}
}
const pending = flushEvent();
if (pending) await handler(pending);
}
async function streamDispatchToStdout(response, opts) {
const result = {};
if (!response.body) {
console.log("Task dispatched (no stream)");
return result;
}
await readSSEStream(response, async (event) => {
const type = event.type;
if (opts?.json) {
console.log(JSON.stringify(event));
return;
}
switch (type) {
case "text":
process.stdout.write(event.content ?? "");
break;
case "tool_use":
process.stderr.write(`
[tool] ${event.toolName ?? event.name}
`);
break;
case "tool_result":
break;
case "session_init":
result.sessionId = event.sessionId;
break;
case "result":
console.log(`
--- Result: ${event.status} ---`);
if (event.summary) console.log(event.summary);
if (event.durationMs || event.inputTokens || event.outputTokens || event.totalCost) {
result.metrics = {
durationMs: event.durationMs,
inputTokens: event.inputTokens,
outputTokens: event.outputTokens,
totalCost: event.totalCost,
model: event.model
};
}
break;
case "plan_result":
console.log("\n--- Plan Generated ---");
console.log(JSON.stringify(event.plan ?? event, null, 2));
break;
case "approval_request":
if (opts?.onApprovalRequest) {
const approval = await opts.onApprovalRequest({
requestId: event.requestId,
question: event.question,
options: event.options,
machineId: event.machineId,
taskId: event.taskId
});
void approval;
} else {
process.stderr.write(`
[approval] ${event.question}
`);
process.stderr.write(` Options: ${event.options.join(", ")}
`);
}
break;
case "error":
console.error(`
Error: ${event.error ?? event.message}`);
break;
case "done":
case "heartbeat":
case "aborted":
break;
}
});
return result;
}
async function streamChatToStdout(response, opts) {
const result = {};
let assistantText = "";
if (!response.body) {
console.log("No stream received");
return result;
}
await readSSEStream(response, async (event) => {
const type = event.type;
if (opts?.json) {
console.log(JSON.stringify(event));
return;
}
switch (type) {
case "text":
const content = event.content ?? "";
process.stdout.write(content);
assistantText += content;
break;
case "tool_use":
process.stderr.write(`
[tool] ${event.toolName ?? event.name}
`);
break;
case "tool_result":
break;
case "session_init":
result.sessionId = event.sessionId;
break;
case "file_change":
process.stderr.write(`[file] ${event.action} ${event.path}
`);
break;
case "compaction":
process.stderr.write(`[compaction] History compacted: ${event.originalCount} \u2192 ${event.compactedCount} messages
`);
break;
case "plan_result":
console.log("\n--- Plan Generated ---");
console.log(JSON.stringify(event.plan ?? event, null, 2));
break;
case "approval_request":
if (opts?.onApprovalRequest) {
const approval = await opts.onApprovalRequest({
requestId: event.requestId,
question: event.question,
options: event.options,
machineId: event.machineId,
taskId: event.taskId
});
void approval;
} else {
process.stderr.write(`
[approval] ${event.question}
`);
process.stderr.write(` Options: ${event.options.join(", ")}
`);
}
break;
case "done":
if (event.durationMs || event.inputTokens || event.outputTokens || event.totalCost) {
result.metrics = {
durationMs: event.durationMs,
inputTokens: event.inputTokens,
outputTokens: event.outputTokens,
totalCost: event.totalCost,
model: event.model
};
}
break;
case "error":
console.error(`
Error: ${event.message}`);
break;
case "heartbeat":
break;
}
});
result.assistantText = assistantText;
return result;
}
var _client = null;
var _clientUrl;
function getClient(serverUrl) {
const resolvedUrl = getServerUrl(serverUrl);
if (!_client || resolvedUrl !== _clientUrl) {
_client = new AstroClient({ serverUrl });
_clientUrl = resolvedUrl;
}
return _client;
}
export {
loadConfig,
saveConfig,
clearAuth,
resetConfig,
getServerUrl,
AstroClient,
readSSEStream,
streamDispatchToStdout,
streamChatToStdout,
getClient
};
+1
-1

@@ -8,3 +8,3 @@ #!/usr/bin/env node

streamDispatchToStdout
} from "./chunk-3FXNZICS.js";
} from "./chunk-C7RPJATV.js";
export {

@@ -11,0 +11,0 @@ AstroClient,

{
"name": "@astroanywhere/cli",
"version": "0.6.2",
"version": "0.6.3",
"description": "CLI for managing Astro projects, plans, tasks, and environments",

@@ -5,0 +5,0 @@ "type": "module",

#!/usr/bin/env node
// src/config.ts
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
import { join } from "path";
import { homedir } from "os";
var CONFIG_DIR = join(homedir(), ".astro");
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
var DEFAULT_SERVER_URL = "https://api.astroanywhere.com";
function loadConfig() {
const envToken = process.env.ASTRO_AUTH_TOKEN;
const envServerUrl = process.env.ASTRO_SERVER_URL;
try {
if (existsSync(CONFIG_FILE)) {
const raw = readFileSync(CONFIG_FILE, "utf-8");
const file = JSON.parse(raw);
return {
serverUrl: DEFAULT_SERVER_URL,
...file,
// Env vars override file — agent-runner always has a live token
...envToken ? { authToken: envToken } : {},
...envServerUrl ? { serverUrl: envServerUrl } : {}
};
}
} catch {
}
return {
serverUrl: envServerUrl ?? DEFAULT_SERVER_URL,
...envToken ? { authToken: envToken } : {}
};
}
function saveConfig(updates) {
mkdirSync(CONFIG_DIR, { recursive: true });
const current = loadConfig();
const merged = { ...current, ...updates };
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", { mode: 384 });
}
function clearAuth() {
const current = loadConfig();
delete current.authToken;
delete current.refreshToken;
mkdirSync(CONFIG_DIR, { recursive: true });
writeFileSync(CONFIG_FILE, JSON.stringify(current, null, 2) + "\n", { mode: 384 });
}
function resetConfig() {
mkdirSync(CONFIG_DIR, { recursive: true });
writeFileSync(CONFIG_FILE, JSON.stringify({ serverUrl: DEFAULT_SERVER_URL }, null, 2) + "\n", { mode: 384 });
}
function getServerUrl(cliOverride) {
if (cliOverride) return cliOverride;
if (process.env.ASTRO_SERVER_URL) return process.env.ASTRO_SERVER_URL;
const config = loadConfig();
return config.serverUrl;
}
// src/client.ts
var AstroClient = class {
baseUrl;
headers;
constructor(opts) {
this.baseUrl = getServerUrl(opts?.serverUrl);
const config = loadConfig();
this.headers = {
"Content-Type": "application/json",
...config.authToken ? { Authorization: `Bearer ${config.authToken}` } : {}
};
}
async refreshAccessToken() {
const config = loadConfig();
if (!config.refreshToken) return false;
try {
const url = new URL("/api/device/refresh", this.baseUrl);
const res = await fetch(url.toString(), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refreshToken: config.refreshToken, grantType: "refresh_token" })
});
if (!res.ok) return false;
const data = await res.json();
saveConfig({
authToken: data.accessToken,
...data.refreshToken ? { refreshToken: data.refreshToken } : {}
});
this.headers["Authorization"] = `Bearer ${data.accessToken}`;
return true;
} catch (err) {
console.error(`Token refresh failed: ${err instanceof Error ? err.message : String(err)}`);
return false;
}
}
async request(method, path, body, params) {
const url = new URL(path, this.baseUrl);
if (params) {
for (const [k, v] of Object.entries(params)) {
if (v !== void 0) url.searchParams.set(k, v);
}
}
let res = await fetch(url.toString(), {
method,
headers: this.headers,
body: body ? JSON.stringify(body) : void 0
});
if (res.status === 401) {
const refreshed = await this.refreshAccessToken();
if (refreshed) {
res = await fetch(url.toString(), {
method,
headers: this.headers,
body: body ? JSON.stringify(body) : void 0
});
}
}
if (!res.ok) {
const text = await res.text();
throw new Error(`API error ${res.status}: ${text}`);
}
return res.json();
}
get(path, params) {
return this.request("GET", path, void 0, params);
}
post(path, body) {
return this.request("POST", path, body);
}
del(path) {
return this.request("DELETE", path);
}
// ── Projects ────────────────────────────────────────────────────────
async listProjects() {
return this.get("/api/data/projects");
}
async getProject(id) {
return this.get(`/api/data/projects/${id}`);
}
async createProject(data) {
return this.post("/api/data/projects", data);
}
async deleteProject(id) {
return this.del(`/api/data/projects/${id}`);
}
/**
* Resolve a partial project ID to a full project.
* Lists all projects and finds the one matching the prefix.
* Throws if 0 or >1 matches.
*/
async resolveProject(partialId) {
const projects = await this.listProjects();
const matches = projects.filter((p) => p.id.startsWith(partialId));
if (matches.length === 0) throw new Error(`No project found matching "${partialId}"`);
if (matches.length > 1) throw new Error(`Ambiguous ID "${partialId}" matches ${matches.length} projects`);
return matches[0];
}
async getProjectNotes(projectId) {
return this.get(`/api/data/projects/${projectId}/notes`);
}
// ── Plan ────────────────────────────────────────────────────────────
async getPlan(projectId) {
const result = await this.get(`/api/data/plan/${projectId}`);
return {
nodes: result.nodes ?? [],
edges: result.edges ?? []
};
}
async getFullPlan() {
const result = await this.get("/api/data/plan");
return {
nodes: result.nodes ?? [],
edges: result.edges ?? []
};
}
// ── Executions ──────────────────────────────────────────────────────
/**
* Get executions map keyed by nodeClientId.
* Server returns Record<nodeClientId, Execution>.
*/
async getExecutions() {
return this.get("/api/data/executions");
}
// ── Tool Traces & File Changes ─────────────────────────────────────
async listToolTraces(executionId) {
return this.get("/api/data/tool-traces", { executionId });
}
async listFileChanges(executionId) {
return this.get("/api/data/file-changes", { executionId });
}
// ── Usage / Cost ────────────────────────────────────────────────────
async getUsageHistory(weeks = 1) {
return this.get("/api/data/usage/history", { weeks: String(weeks) });
}
// ── Activity ────────────────────────────────────────────────────────
async listActivities(params) {
return this.get("/api/data/activities", params);
}
// ── Machines ────────────────────────────────────────────────────────
async listMachines() {
const result = await this.get("/api/device/machines");
return result.machines ?? [];
}
async getMachine(id) {
return this.get(`/api/device/machines/${id}`);
}
async revokeMachine(id) {
return this.del(`/api/device/machines/${id}`);
}
/**
* Resolve a partial machine ID to a full machine.
* Lists all machines and finds the one matching the prefix.
*/
async resolveMachine(partialId) {
const machines = await this.listMachines();
const matches = machines.filter((m) => !m.isRevoked && m.id.startsWith(partialId));
if (matches.length === 0) throw new Error(`No active machine found matching "${partialId}"`);
if (matches.length > 1) throw new Error(`Ambiguous ID "${partialId}" matches ${matches.length} machines`);
return matches[0];
}
// ── Observations ────────────────────────────────────────────────────
async listObservations(executionId) {
return this.get("/api/observations", { executionId });
}
async getObservationStats(executionId) {
return this.get("/api/observations/stats", { executionId });
}
// ── Search ──────────────────────────────────────────────────────────
async search(query) {
return this.get("/api/data/search", { q: query });
}
// ── References ──────────────────────────────────────────────────────
async searchReferences(query, projectId) {
const result = await this.get("/api/references/search", {
q: query,
...projectId ? { projectId } : {}
});
return result.references ?? [];
}
async listReferences(projectId) {
const result = await this.get("/api/references", projectId ? { projectId } : {});
return result.references ?? [];
}
async exportBibTeX(projectId) {
const url = new URL("/api/references/export", this.baseUrl);
url.searchParams.set("format", "bibtex");
if (projectId) url.searchParams.set("projectId", projectId);
const res = await fetch(url.toString(), { headers: this.headers });
if (!res.ok) {
const text = await res.text();
throw new Error(`API error ${res.status}: ${text}`);
}
return res.text();
}
async importReferencesFromBibTeX(content, options) {
const result = await this.request(
"POST",
"/api/references/import-bibtex",
{
bibtex: content,
addedBy: options?.accept ? "human" : "ai",
...options?.projectId ? { projectId: options.projectId } : {}
}
);
return result;
}
// ── Plan CRUD ─────────────────────────────────────────────────────
async setPlan(projectId, nodes, edges, projectName) {
const executionId = process.env.ASTRO_EXECUTION_ID || void 0;
return this.request("PUT", `/api/data/plan/${projectId}`, { nodes, edges, executionId, projectName });
}
async createPlanNode(data) {
return this.post("/api/data/plan/nodes", {
type: "task",
status: "planned",
...data
});
}
async updatePlanNode(nodeId, patch) {
return this.request("PATCH", `/api/data/plan/nodes/${nodeId}`, patch);
}
async deletePlanNode(nodeId) {
return this.del(`/api/data/plan/nodes/${nodeId}`);
}
async createPlanEdge(data) {
return this.post("/api/data/plan/edges", { type: "dependency", ...data });
}
async deletePlanEdge(edgeId) {
return this.del(`/api/data/plan/edges/${edgeId}`);
}
// ── Project Update ────────────────────────────────────────────────
async updateProject(id, patch) {
return this.request("PATCH", `/api/data/projects/${id}`, patch);
}
// ── Cancel / Steer ────────────────────────────────────────────────
async cancelTask(params) {
return this.post("/api/dispatch/cancel", params);
}
async steerTask(params) {
return this.post("/api/dispatch/steer", params);
}
// ── Relay / Environment ───────────────────────────────────────────
async getRelayStatus() {
return this.get("/api/relay/status");
}
async getProviders() {
return this.get("/api/health/providers");
}
async getSlurmClusters() {
return this.get("/api/relay/slurm/clusters");
}
// ── Observations (extended) ───────────────────────────────────────
async getTraceSummary(executionId) {
const url = new URL(`/api/observations/${executionId}/summary`, this.baseUrl);
const res = await fetch(url.toString(), { headers: this.headers });
if (!res.ok) {
const text = await res.text();
throw new Error(`API error ${res.status}: ${text}`);
}
return res.text();
}
async listObservationsFiltered(params) {
return this.get("/api/observations", params);
}
// ── SSE Events Stream ─────────────────────────────────────────────
async streamEvents(params) {
const url = new URL("/api/events/stream", this.baseUrl);
if (params?.projectId) url.searchParams.set("projectId", params.projectId);
const res = await fetch(url.toString(), {
headers: {
...this.headers,
Accept: "text/event-stream"
}
});
if (!res.ok) {
const text = await res.text();
throw new Error(`SSE connect failed (${res.status}): ${text}`);
}
return res;
}
// ── Dispatch (SSE streaming) ────────────────────────────────────────
/**
* Dispatch a task for execution. Returns the raw Response for SSE streaming.
*/
async dispatchTask(payload) {
const url = new URL("/api/dispatch/task", this.baseUrl);
const res = await fetch(url.toString(), {
method: "POST",
headers: this.headers,
body: JSON.stringify(payload)
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Dispatch failed (${res.status}): ${text}`);
}
return res;
}
// ── Chat (SSE streaming) ──────────────────────────────────────────
async projectChat(payload) {
const url = new URL("/api/agent/project-chat", this.baseUrl);
const res = await fetch(url.toString(), {
method: "POST",
headers: this.headers,
body: JSON.stringify(payload)
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Project chat failed (${res.status}): ${text}`);
}
return res;
}
async taskChat(payload) {
const url = new URL("/api/agent/task-chat", this.baseUrl);
const res = await fetch(url.toString(), {
method: "POST",
headers: this.headers,
body: JSON.stringify(payload)
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Task chat failed (${res.status}): ${text}`);
}
return res;
}
async getNode(nodeId) {
try {
return await this.get(`/api/data/plan/nodes/${nodeId}`);
} catch {
return null;
}
}
// ── Approval ───────────────────────────────────────────────────────
async sendApproval(payload) {
return this.post("/api/dispatch/approval", payload);
}
// ── Summarize ──────────────────────────────────────────────────────
async summarize(payload) {
return this.post("/api/agent/summarize", payload);
}
// ── Slurm Dispatch ────────────────────────────────────────────────
async dispatchSlurmTask(payload) {
const url = new URL("/api/relay/slurm/dispatch", this.baseUrl);
const res = await fetch(url.toString(), {
method: "POST",
headers: this.headers,
body: JSON.stringify(payload)
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Slurm dispatch failed (${res.status}): ${text}`);
}
return res;
}
};
async function readSSEStream(response, handler) {
if (!response.body) return;
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let currentEvent = "";
let dataLines = [];
function flushEvent() {
if (!currentEvent) return null;
const data = dataLines.join("\n");
const event = currentEvent;
currentEvent = "";
dataLines = [];
if (event === "text") {
return { type: "text", content: data, text: data };
}
try {
const parsed = JSON.parse(data);
return { type: event, ...parsed };
} catch {
return { type: event, data };
}
}
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
if (line.startsWith("event:")) {
const pending2 = flushEvent();
if (pending2) await handler(pending2);
currentEvent = line.slice(6).trim();
} else if (line.startsWith("data:")) {
const value2 = line.slice(5);
dataLines.push(value2.startsWith(" ") ? value2.slice(1) : value2);
} else if (line === "") {
const pending2 = flushEvent();
if (pending2) await handler(pending2);
}
}
}
const pending = flushEvent();
if (pending) await handler(pending);
}
async function streamDispatchToStdout(response, opts) {
const result = {};
if (!response.body) {
console.log("Task dispatched (no stream)");
return result;
}
await readSSEStream(response, async (event) => {
const type = event.type;
if (opts?.json) {
console.log(JSON.stringify(event));
return;
}
switch (type) {
case "text":
process.stdout.write(event.content ?? "");
break;
case "tool_use":
process.stderr.write(`
[tool] ${event.toolName ?? event.name}
`);
break;
case "tool_result":
break;
case "session_init":
result.sessionId = event.sessionId;
break;
case "result":
console.log(`
--- Result: ${event.status} ---`);
if (event.summary) console.log(event.summary);
if (event.durationMs || event.inputTokens || event.outputTokens || event.totalCost) {
result.metrics = {
durationMs: event.durationMs,
inputTokens: event.inputTokens,
outputTokens: event.outputTokens,
totalCost: event.totalCost,
model: event.model
};
}
break;
case "plan_result":
console.log("\n--- Plan Generated ---");
console.log(JSON.stringify(event.plan ?? event, null, 2));
break;
case "approval_request":
if (opts?.onApprovalRequest) {
const approval = await opts.onApprovalRequest({
requestId: event.requestId,
question: event.question,
options: event.options,
machineId: event.machineId,
taskId: event.taskId
});
void approval;
} else {
process.stderr.write(`
[approval] ${event.question}
`);
process.stderr.write(` Options: ${event.options.join(", ")}
`);
}
break;
case "error":
console.error(`
Error: ${event.error ?? event.message}`);
break;
case "done":
case "heartbeat":
case "aborted":
break;
}
});
return result;
}
async function streamChatToStdout(response, opts) {
const result = {};
let assistantText = "";
if (!response.body) {
console.log("No stream received");
return result;
}
await readSSEStream(response, async (event) => {
const type = event.type;
if (opts?.json) {
console.log(JSON.stringify(event));
return;
}
switch (type) {
case "text":
const content = event.content ?? "";
process.stdout.write(content);
assistantText += content;
break;
case "tool_use":
process.stderr.write(`
[tool] ${event.toolName ?? event.name}
`);
break;
case "tool_result":
break;
case "session_init":
result.sessionId = event.sessionId;
break;
case "file_change":
process.stderr.write(`[file] ${event.action} ${event.path}
`);
break;
case "compaction":
process.stderr.write(`[compaction] History compacted: ${event.originalCount} \u2192 ${event.compactedCount} messages
`);
break;
case "plan_result":
console.log("\n--- Plan Generated ---");
console.log(JSON.stringify(event.plan ?? event, null, 2));
break;
case "approval_request":
if (opts?.onApprovalRequest) {
const approval = await opts.onApprovalRequest({
requestId: event.requestId,
question: event.question,
options: event.options,
machineId: event.machineId,
taskId: event.taskId
});
void approval;
} else {
process.stderr.write(`
[approval] ${event.question}
`);
process.stderr.write(` Options: ${event.options.join(", ")}
`);
}
break;
case "done":
if (event.durationMs || event.inputTokens || event.outputTokens || event.totalCost) {
result.metrics = {
durationMs: event.durationMs,
inputTokens: event.inputTokens,
outputTokens: event.outputTokens,
totalCost: event.totalCost,
model: event.model
};
}
break;
case "error":
console.error(`
Error: ${event.message}`);
break;
case "heartbeat":
break;
}
});
result.assistantText = assistantText;
return result;
}
var _client = null;
var _clientUrl;
function getClient(serverUrl) {
const resolvedUrl = getServerUrl(serverUrl);
if (!_client || resolvedUrl !== _clientUrl) {
_client = new AstroClient({ serverUrl });
_clientUrl = resolvedUrl;
}
return _client;
}
export {
loadConfig,
saveConfig,
clearAuth,
resetConfig,
getServerUrl,
AstroClient,
readSSEStream,
streamDispatchToStdout,
streamChatToStdout,
getClient
};

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display