@astroanywhere/cli 0.2.0 → 0.2.1
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/dist/{chunk-7H7WD7QX.js → chunk-SYY2HHOY.js} +217 -29
- package/dist/client.js +3 -1
- package/dist/index.js +308 -6
- package/dist/tui.js +1734 -1058
- package/package.json +10 -7
|
@@ -168,6 +168,10 @@ var AstroClient = class {
|
|
|
168
168
|
async listFileChanges(executionId) {
|
|
169
169
|
return this.get("/api/data/file-changes", { executionId });
|
|
170
170
|
}
|
|
171
|
+
// ── Usage / Cost ────────────────────────────────────────────────────
|
|
172
|
+
async getUsageHistory(weeks = 1) {
|
|
173
|
+
return this.get("/api/data/usage/history", { weeks: String(weeks) });
|
|
174
|
+
}
|
|
171
175
|
// ── Activity ────────────────────────────────────────────────────────
|
|
172
176
|
async listActivities(params) {
|
|
173
177
|
return this.get("/api/data/activities", params);
|
|
@@ -286,12 +290,66 @@ var AstroClient = class {
|
|
|
286
290
|
}
|
|
287
291
|
return res;
|
|
288
292
|
}
|
|
293
|
+
// ── Chat (SSE streaming) ──────────────────────────────────────────
|
|
294
|
+
async projectChat(payload) {
|
|
295
|
+
const url = new URL("/api/agent/project-chat", this.baseUrl);
|
|
296
|
+
const res = await fetch(url.toString(), {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: this.headers,
|
|
299
|
+
body: JSON.stringify(payload)
|
|
300
|
+
});
|
|
301
|
+
if (!res.ok) {
|
|
302
|
+
const text = await res.text();
|
|
303
|
+
throw new Error(`Project chat failed (${res.status}): ${text}`);
|
|
304
|
+
}
|
|
305
|
+
return res;
|
|
306
|
+
}
|
|
307
|
+
async taskChat(payload) {
|
|
308
|
+
const url = new URL("/api/agent/task-chat", this.baseUrl);
|
|
309
|
+
const res = await fetch(url.toString(), {
|
|
310
|
+
method: "POST",
|
|
311
|
+
headers: this.headers,
|
|
312
|
+
body: JSON.stringify(payload)
|
|
313
|
+
});
|
|
314
|
+
if (!res.ok) {
|
|
315
|
+
const text = await res.text();
|
|
316
|
+
throw new Error(`Task chat failed (${res.status}): ${text}`);
|
|
317
|
+
}
|
|
318
|
+
return res;
|
|
319
|
+
}
|
|
320
|
+
// ── Approval ───────────────────────────────────────────────────────
|
|
321
|
+
async sendApproval(payload) {
|
|
322
|
+
return this.post("/api/dispatch/approval", payload);
|
|
323
|
+
}
|
|
324
|
+
// ── Slurm Dispatch ────────────────────────────────────────────────
|
|
325
|
+
async dispatchSlurmTask(payload) {
|
|
326
|
+
const url = new URL("/api/relay/slurm/dispatch", this.baseUrl);
|
|
327
|
+
const res = await fetch(url.toString(), {
|
|
328
|
+
method: "POST",
|
|
329
|
+
headers: this.headers,
|
|
330
|
+
body: JSON.stringify(payload)
|
|
331
|
+
});
|
|
332
|
+
if (!res.ok) {
|
|
333
|
+
const text = await res.text();
|
|
334
|
+
throw new Error(`Slurm dispatch failed (${res.status}): ${text}`);
|
|
335
|
+
}
|
|
336
|
+
return res;
|
|
337
|
+
}
|
|
289
338
|
};
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
339
|
+
function parseSSELines(buffer, lines) {
|
|
340
|
+
const events = [];
|
|
341
|
+
for (const line of lines) {
|
|
342
|
+
if (line.startsWith("data: ")) {
|
|
343
|
+
try {
|
|
344
|
+
events.push(JSON.parse(line.slice(6)));
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
}
|
|
294
348
|
}
|
|
349
|
+
return events;
|
|
350
|
+
}
|
|
351
|
+
async function readSSEStream(response, handler) {
|
|
352
|
+
if (!response.body) return;
|
|
295
353
|
const reader = response.body.getReader();
|
|
296
354
|
const decoder = new TextDecoder();
|
|
297
355
|
let buffer = "";
|
|
@@ -301,35 +359,164 @@ async function streamDispatchToStdout(response) {
|
|
|
301
359
|
buffer += decoder.decode(value, { stream: true });
|
|
302
360
|
const lines = buffer.split("\n");
|
|
303
361
|
buffer = lines.pop() ?? "";
|
|
304
|
-
for (const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
362
|
+
for (const event of parseSSELines(buffer, lines)) {
|
|
363
|
+
await handler(event);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function streamDispatchToStdout(response, opts) {
|
|
368
|
+
const result = {};
|
|
369
|
+
if (!response.body) {
|
|
370
|
+
console.log("Task dispatched (no stream)");
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
await readSSEStream(response, async (event) => {
|
|
374
|
+
const type = event.type;
|
|
375
|
+
if (opts?.json) {
|
|
376
|
+
console.log(JSON.stringify(event));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
switch (type) {
|
|
380
|
+
case "text":
|
|
381
|
+
process.stdout.write(event.content ?? "");
|
|
382
|
+
break;
|
|
383
|
+
case "tool_use":
|
|
384
|
+
process.stderr.write(`
|
|
385
|
+
[tool] ${event.name}
|
|
386
|
+
`);
|
|
387
|
+
break;
|
|
388
|
+
case "tool_result":
|
|
389
|
+
break;
|
|
390
|
+
case "session_init":
|
|
391
|
+
result.sessionId = event.sessionId;
|
|
392
|
+
break;
|
|
393
|
+
case "result":
|
|
394
|
+
console.log(`
|
|
318
395
|
--- Result: ${event.status} ---`);
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
396
|
+
if (event.summary) console.log(event.summary);
|
|
397
|
+
if (event.durationMs || event.inputTokens || event.outputTokens || event.totalCost) {
|
|
398
|
+
result.metrics = {
|
|
399
|
+
durationMs: event.durationMs,
|
|
400
|
+
inputTokens: event.inputTokens,
|
|
401
|
+
outputTokens: event.outputTokens,
|
|
402
|
+
totalCost: event.totalCost,
|
|
403
|
+
model: event.model
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
break;
|
|
407
|
+
case "plan_result":
|
|
408
|
+
console.log("\n--- Plan Generated ---");
|
|
409
|
+
console.log(JSON.stringify(event.plan ?? event, null, 2));
|
|
410
|
+
break;
|
|
411
|
+
case "approval_request":
|
|
412
|
+
if (opts?.onApprovalRequest) {
|
|
413
|
+
const approval = await opts.onApprovalRequest({
|
|
414
|
+
requestId: event.requestId,
|
|
415
|
+
question: event.question,
|
|
416
|
+
options: event.options,
|
|
417
|
+
machineId: event.machineId,
|
|
418
|
+
taskId: event.taskId
|
|
419
|
+
});
|
|
420
|
+
void approval;
|
|
421
|
+
} else {
|
|
422
|
+
process.stderr.write(`
|
|
423
|
+
[approval] ${event.question}
|
|
424
|
+
`);
|
|
425
|
+
process.stderr.write(` Options: ${event.options.join(", ")}
|
|
426
|
+
`);
|
|
427
|
+
}
|
|
428
|
+
break;
|
|
429
|
+
case "error":
|
|
430
|
+
console.error(`
|
|
323
431
|
Error: ${event.message}`);
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
432
|
+
break;
|
|
433
|
+
case "done":
|
|
434
|
+
case "heartbeat":
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
return result;
|
|
439
|
+
}
|
|
440
|
+
async function streamChatToStdout(response, opts) {
|
|
441
|
+
const result = {};
|
|
442
|
+
let assistantText = "";
|
|
443
|
+
if (!response.body) {
|
|
444
|
+
console.log("No stream received");
|
|
445
|
+
return result;
|
|
446
|
+
}
|
|
447
|
+
await readSSEStream(response, async (event) => {
|
|
448
|
+
const type = event.type;
|
|
449
|
+
if (opts?.json) {
|
|
450
|
+
console.log(JSON.stringify(event));
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
switch (type) {
|
|
454
|
+
case "text":
|
|
455
|
+
const content = event.content ?? "";
|
|
456
|
+
process.stdout.write(content);
|
|
457
|
+
assistantText += content;
|
|
458
|
+
break;
|
|
459
|
+
case "tool_use":
|
|
460
|
+
process.stderr.write(`
|
|
461
|
+
[tool] ${event.name}
|
|
462
|
+
`);
|
|
463
|
+
break;
|
|
464
|
+
case "tool_result":
|
|
465
|
+
break;
|
|
466
|
+
case "session_init":
|
|
467
|
+
result.sessionId = event.sessionId;
|
|
468
|
+
break;
|
|
469
|
+
case "file_change":
|
|
470
|
+
process.stderr.write(`[file] ${event.action} ${event.path}
|
|
471
|
+
`);
|
|
472
|
+
break;
|
|
473
|
+
case "compaction":
|
|
474
|
+
process.stderr.write(`[compaction] History compacted: ${event.originalCount} \u2192 ${event.compactedCount} messages
|
|
475
|
+
`);
|
|
476
|
+
break;
|
|
477
|
+
case "plan_result":
|
|
478
|
+
console.log("\n--- Plan Generated ---");
|
|
479
|
+
console.log(JSON.stringify(event.plan ?? event, null, 2));
|
|
480
|
+
break;
|
|
481
|
+
case "approval_request":
|
|
482
|
+
if (opts?.onApprovalRequest) {
|
|
483
|
+
const approval = await opts.onApprovalRequest({
|
|
484
|
+
requestId: event.requestId,
|
|
485
|
+
question: event.question,
|
|
486
|
+
options: event.options,
|
|
487
|
+
machineId: event.machineId,
|
|
488
|
+
taskId: event.taskId
|
|
489
|
+
});
|
|
490
|
+
void approval;
|
|
491
|
+
} else {
|
|
492
|
+
process.stderr.write(`
|
|
493
|
+
[approval] ${event.question}
|
|
494
|
+
`);
|
|
495
|
+
process.stderr.write(` Options: ${event.options.join(", ")}
|
|
496
|
+
`);
|
|
329
497
|
}
|
|
330
|
-
|
|
498
|
+
break;
|
|
499
|
+
case "done":
|
|
500
|
+
if (event.durationMs || event.inputTokens || event.outputTokens || event.totalCost) {
|
|
501
|
+
result.metrics = {
|
|
502
|
+
durationMs: event.durationMs,
|
|
503
|
+
inputTokens: event.inputTokens,
|
|
504
|
+
outputTokens: event.outputTokens,
|
|
505
|
+
totalCost: event.totalCost,
|
|
506
|
+
model: event.model
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
break;
|
|
510
|
+
case "error":
|
|
511
|
+
console.error(`
|
|
512
|
+
Error: ${event.message}`);
|
|
513
|
+
break;
|
|
514
|
+
case "heartbeat":
|
|
515
|
+
break;
|
|
331
516
|
}
|
|
332
|
-
}
|
|
517
|
+
});
|
|
518
|
+
result.assistantText = assistantText;
|
|
519
|
+
return result;
|
|
333
520
|
}
|
|
334
521
|
var _client = null;
|
|
335
522
|
var _clientUrl;
|
|
@@ -350,5 +537,6 @@ export {
|
|
|
350
537
|
getServerUrl,
|
|
351
538
|
AstroClient,
|
|
352
539
|
streamDispatchToStdout,
|
|
540
|
+
streamChatToStdout,
|
|
353
541
|
getClient
|
|
354
542
|
};
|
package/dist/client.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -6,8 +6,9 @@ import {
|
|
|
6
6
|
loadConfig,
|
|
7
7
|
resetConfig,
|
|
8
8
|
saveConfig,
|
|
9
|
+
streamChatToStdout,
|
|
9
10
|
streamDispatchToStdout
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-SYY2HHOY.js";
|
|
11
12
|
|
|
12
13
|
// src/index.ts
|
|
13
14
|
import { Command } from "commander";
|
|
@@ -128,6 +129,74 @@ function formatStatus(status) {
|
|
|
128
129
|
return colorFn(status);
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
// src/chat-utils.ts
|
|
133
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
134
|
+
import { createInterface } from "readline";
|
|
135
|
+
var MAX_HISTORY_MESSAGES = 100;
|
|
136
|
+
function loadHistory(path) {
|
|
137
|
+
try {
|
|
138
|
+
const raw = readFileSync(path, "utf-8");
|
|
139
|
+
const messages = JSON.parse(raw);
|
|
140
|
+
if (!Array.isArray(messages)) return [];
|
|
141
|
+
return messages.slice(-MAX_HISTORY_MESSAGES);
|
|
142
|
+
} catch {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function saveHistory(path, messages) {
|
|
147
|
+
writeFileSync(path, JSON.stringify(messages.slice(-MAX_HISTORY_MESSAGES), null, 2));
|
|
148
|
+
}
|
|
149
|
+
async function promptApproval(question, options) {
|
|
150
|
+
process.stderr.write(`
|
|
151
|
+
${question}
|
|
152
|
+
`);
|
|
153
|
+
for (let i = 0; i < options.length; i++) {
|
|
154
|
+
process.stderr.write(` [${i + 1}] ${options[i]}
|
|
155
|
+
`);
|
|
156
|
+
}
|
|
157
|
+
process.stderr.write("Select option (number): ");
|
|
158
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
159
|
+
return new Promise((resolve) => {
|
|
160
|
+
rl.question("", (input) => {
|
|
161
|
+
rl.close();
|
|
162
|
+
const idx = parseInt(input.trim(), 10) - 1;
|
|
163
|
+
if (idx >= 0 && idx < options.length) {
|
|
164
|
+
resolve({ answered: true, answer: options[idx] });
|
|
165
|
+
} else {
|
|
166
|
+
resolve({ answered: false });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function createApprovalHandler(client, yolo) {
|
|
172
|
+
return async (data) => {
|
|
173
|
+
let result;
|
|
174
|
+
if (yolo) {
|
|
175
|
+
result = { answered: true, answer: data.options[0] };
|
|
176
|
+
process.stderr.write(`
|
|
177
|
+
[yolo] Auto-approved: ${data.question} \u2192 ${data.options[0]}
|
|
178
|
+
`);
|
|
179
|
+
} else {
|
|
180
|
+
result = await promptApproval(data.question, data.options);
|
|
181
|
+
}
|
|
182
|
+
if (data.taskId && data.machineId && data.requestId) {
|
|
183
|
+
try {
|
|
184
|
+
await client.sendApproval({
|
|
185
|
+
taskId: data.taskId,
|
|
186
|
+
machineId: data.machineId,
|
|
187
|
+
requestId: data.requestId,
|
|
188
|
+
answered: result.answered,
|
|
189
|
+
answer: result.answer
|
|
190
|
+
});
|
|
191
|
+
} catch (err) {
|
|
192
|
+
process.stderr.write(`[approval] Failed to send: ${err instanceof Error ? err.message : String(err)}
|
|
193
|
+
`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
131
200
|
// src/commands/project.ts
|
|
132
201
|
import chalk2 from "chalk";
|
|
133
202
|
var projectColumns = [
|
|
@@ -165,7 +234,6 @@ function registerProjectCommands(program2) {
|
|
|
165
234
|
["Status", formatStatus(p.status)],
|
|
166
235
|
["Description", p.description || chalk2.dim("\u2014")],
|
|
167
236
|
["Working Directory", p.workingDirectory || chalk2.dim("\u2014")],
|
|
168
|
-
["Source Directory", p.sourceDirectory || chalk2.dim("\u2014")],
|
|
169
237
|
["Repository", p.repository || chalk2.dim("\u2014")],
|
|
170
238
|
["Delivery Mode", p.deliveryMode || chalk2.dim("\u2014")],
|
|
171
239
|
["Health", p.health || chalk2.dim("\u2014")],
|
|
@@ -311,6 +379,62 @@ function registerProjectCommands(program2) {
|
|
|
311
379
|
process.exitCode = 1;
|
|
312
380
|
}
|
|
313
381
|
});
|
|
382
|
+
project.command("chat <id>").description("Chat with AI about a project").requiredOption("--message <msg>", "Message to send").option("--session-id <sid>", "Resume existing session").option("--model <model>", "AI model to use").option("--provider <provider>", "Provider ID").option("--history-file <path>", "Path to conversation history JSON file").option("--yolo", "Auto-approve all approval requests").action(async (id, cmdOpts) => {
|
|
383
|
+
const opts = program2.opts();
|
|
384
|
+
const client = getClient(opts.serverUrl);
|
|
385
|
+
let p;
|
|
386
|
+
try {
|
|
387
|
+
p = await client.resolveProject(id);
|
|
388
|
+
} catch (err) {
|
|
389
|
+
console.error(chalk2.red(err.message));
|
|
390
|
+
process.exitCode = 1;
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
let planNodes = [];
|
|
394
|
+
let planEdges = [];
|
|
395
|
+
try {
|
|
396
|
+
const plan = await client.getPlan(p.id);
|
|
397
|
+
planNodes = plan.nodes.filter((n) => !n.deletedAt);
|
|
398
|
+
planEdges = plan.edges;
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
let messages = [];
|
|
402
|
+
if (cmdOpts.historyFile) {
|
|
403
|
+
messages = loadHistory(cmdOpts.historyFile);
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
const response = await client.projectChat({
|
|
407
|
+
message: cmdOpts.message,
|
|
408
|
+
projectId: p.id,
|
|
409
|
+
sessionId: cmdOpts.sessionId,
|
|
410
|
+
model: cmdOpts.model,
|
|
411
|
+
providerId: cmdOpts.provider,
|
|
412
|
+
visionDoc: p.visionDoc || void 0,
|
|
413
|
+
planNodes,
|
|
414
|
+
planEdges,
|
|
415
|
+
messages: messages.length > 0 ? messages : void 0
|
|
416
|
+
});
|
|
417
|
+
const approvalHandler = createApprovalHandler(client, !!cmdOpts.yolo);
|
|
418
|
+
const result = await streamChatToStdout(response, {
|
|
419
|
+
json: opts.json,
|
|
420
|
+
onApprovalRequest: approvalHandler
|
|
421
|
+
});
|
|
422
|
+
if (result.sessionId) {
|
|
423
|
+
process.stderr.write(`
|
|
424
|
+
${chalk2.dim(`Session: ${result.sessionId}`)}
|
|
425
|
+
`);
|
|
426
|
+
}
|
|
427
|
+
if (cmdOpts.historyFile && result.assistantText) {
|
|
428
|
+
messages.push({ role: "user", content: cmdOpts.message });
|
|
429
|
+
messages.push({ role: "assistant", content: result.assistantText });
|
|
430
|
+
saveHistory(cmdOpts.historyFile, messages);
|
|
431
|
+
}
|
|
432
|
+
console.log();
|
|
433
|
+
} catch (err) {
|
|
434
|
+
console.error(chalk2.red(`Chat failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
435
|
+
process.exitCode = 1;
|
|
436
|
+
}
|
|
437
|
+
});
|
|
314
438
|
project.command("delete <id>").description("Delete a project").action(async (id) => {
|
|
315
439
|
const opts = program2.opts();
|
|
316
440
|
const client = getClient(opts.serverUrl);
|
|
@@ -675,6 +799,33 @@ function registerPlanCommands(program2) {
|
|
|
675
799
|
process.exitCode = 1;
|
|
676
800
|
}
|
|
677
801
|
});
|
|
802
|
+
plan.command("generate").description("Generate a plan using AI").requiredOption("--project-id <id>", "Project ID").requiredOption("--description <desc>", "Description of what to plan").option("--model <model>", "AI model to use").option("--provider <provider>", "Preferred provider ID").option("--machine <id>", "Target machine ID").option("--yolo", "Auto-approve all approval requests").action(async (cmdOpts) => {
|
|
803
|
+
const opts = program2.opts();
|
|
804
|
+
const client = getClient(opts.serverUrl);
|
|
805
|
+
console.log(chalk3.dim("Generating plan..."));
|
|
806
|
+
console.log();
|
|
807
|
+
try {
|
|
808
|
+
const response = await client.dispatchTask({
|
|
809
|
+
nodeId: `plan-${cmdOpts.projectId}`,
|
|
810
|
+
projectId: cmdOpts.projectId,
|
|
811
|
+
isInteractivePlan: true,
|
|
812
|
+
description: cmdOpts.description,
|
|
813
|
+
model: cmdOpts.model,
|
|
814
|
+
preferredProvider: cmdOpts.provider,
|
|
815
|
+
targetMachineId: cmdOpts.machine
|
|
816
|
+
});
|
|
817
|
+
const approvalHandler = createApprovalHandler(client, !!cmdOpts.yolo);
|
|
818
|
+
await streamDispatchToStdout(response, {
|
|
819
|
+
json: opts.json,
|
|
820
|
+
onApprovalRequest: approvalHandler
|
|
821
|
+
});
|
|
822
|
+
console.log();
|
|
823
|
+
console.log(chalk3.green("Plan generation complete."));
|
|
824
|
+
} catch (err) {
|
|
825
|
+
console.error(chalk3.red(`Plan generation failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
826
|
+
process.exitCode = 1;
|
|
827
|
+
}
|
|
828
|
+
});
|
|
678
829
|
}
|
|
679
830
|
|
|
680
831
|
// src/commands/task.ts
|
|
@@ -860,17 +1011,54 @@ function registerTaskCommands(program2) {
|
|
|
860
1011
|
}
|
|
861
1012
|
console.log();
|
|
862
1013
|
});
|
|
863
|
-
cmd.command("dispatch <id>").description("Dispatch a task for execution").requiredOption("--project-id <id>", "Project ID").option("--force", "Force re-dispatch even if already running").action(async (nodeId, opts) => {
|
|
1014
|
+
cmd.command("dispatch <id>").description("Dispatch a task for execution").requiredOption("--project-id <id>", "Project ID").option("--force", "Force re-dispatch even if already running").option("--machine <id>", "Target machine ID").option("--model <model>", "AI model to use").option("--provider <provider>", "Preferred provider ID").option("--yolo", "Auto-approve all approval requests").option("--slurm", "Dispatch to Slurm cluster").option("--slurm-partition <p>", "Slurm partition").option("--slurm-gpus <n>", "Number of GPUs", parseInt).option("--slurm-gpu-type <t>", "GPU type (e.g. a100, v100)").option("--slurm-mem <m>", "Memory (e.g. 16G, 64G)").option("--slurm-time <t>", "Time limit (e.g. 1:00:00)").option("--slurm-nodes <n>", "Number of nodes", parseInt).option("--slurm-cpus <n>", "CPUs per task", parseInt).option("--slurm-qos <q>", "Quality of service").option("--slurm-account <a>", "Slurm account").option("--slurm-modules <m>", "Comma-separated modules to load").option("--cluster <id>", "Target Slurm cluster ID").action(async (nodeId, opts) => {
|
|
864
1015
|
const client = getClient(program2.opts().serverUrl);
|
|
1016
|
+
const isJson = program2.opts().json;
|
|
865
1017
|
console.log(chalk4.dim(`Dispatching task ${chalk4.bold(nodeId)} to server...`));
|
|
866
1018
|
console.log();
|
|
867
1019
|
try {
|
|
1020
|
+
if (opts.slurm) {
|
|
1021
|
+
const slurmConfig = {};
|
|
1022
|
+
if (opts.slurmPartition) slurmConfig.partition = opts.slurmPartition;
|
|
1023
|
+
if (opts.slurmNodes) slurmConfig.nodes = opts.slurmNodes;
|
|
1024
|
+
if (opts.slurmCpus) slurmConfig.cpusPerTask = opts.slurmCpus;
|
|
1025
|
+
if (opts.slurmMem) slurmConfig.mem = opts.slurmMem;
|
|
1026
|
+
if (opts.slurmTime) slurmConfig.time = opts.slurmTime;
|
|
1027
|
+
if (opts.slurmQos) slurmConfig.qos = opts.slurmQos;
|
|
1028
|
+
if (opts.slurmAccount) slurmConfig.account = opts.slurmAccount;
|
|
1029
|
+
if (opts.slurmModules) slurmConfig.modules = opts.slurmModules.split(",").map((s) => s.trim());
|
|
1030
|
+
if (opts.slurmGpus || opts.slurmGpuType) {
|
|
1031
|
+
slurmConfig.gpu = { count: opts.slurmGpus ?? 1, type: opts.slurmGpuType };
|
|
1032
|
+
}
|
|
1033
|
+
const response2 = await client.dispatchSlurmTask({
|
|
1034
|
+
task: {
|
|
1035
|
+
taskId: `exec-${nodeId}-${Date.now()}`,
|
|
1036
|
+
projectId: opts.projectId,
|
|
1037
|
+
nodeId,
|
|
1038
|
+
title: nodeId,
|
|
1039
|
+
preferredProvider: opts.provider
|
|
1040
|
+
},
|
|
1041
|
+
targetClusterId: opts.cluster,
|
|
1042
|
+
slurmConfig: Object.keys(slurmConfig).length > 0 ? slurmConfig : void 0
|
|
1043
|
+
});
|
|
1044
|
+
await streamDispatchToStdout(response2, { json: isJson });
|
|
1045
|
+
console.log();
|
|
1046
|
+
console.log(chalk4.green("Slurm dispatch complete."));
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
const approvalHandler = createApprovalHandler(client, !!opts.yolo);
|
|
868
1050
|
const response = await client.dispatchTask({
|
|
869
1051
|
nodeId,
|
|
870
1052
|
projectId: opts.projectId,
|
|
871
|
-
force: opts.force
|
|
1053
|
+
force: opts.force,
|
|
1054
|
+
targetMachineId: opts.machine,
|
|
1055
|
+
model: opts.model,
|
|
1056
|
+
preferredProvider: opts.provider
|
|
1057
|
+
});
|
|
1058
|
+
await streamDispatchToStdout(response, {
|
|
1059
|
+
json: isJson,
|
|
1060
|
+
onApprovalRequest: approvalHandler
|
|
872
1061
|
});
|
|
873
|
-
await streamDispatchToStdout(response);
|
|
874
1062
|
console.log();
|
|
875
1063
|
console.log(chalk4.green("Task dispatch complete."));
|
|
876
1064
|
} catch (err) {
|
|
@@ -878,6 +1066,70 @@ function registerTaskCommands(program2) {
|
|
|
878
1066
|
process.exitCode = 1;
|
|
879
1067
|
}
|
|
880
1068
|
});
|
|
1069
|
+
cmd.command("chat <nodeId>").description("Chat with AI about a specific task").requiredOption("--project-id <id>", "Project ID").requiredOption("--message <msg>", "Message to send").option("--session-id <sid>", "Resume existing session").option("--model <model>", "AI model to use").option("--provider <provider>", "Provider ID").option("--history-file <path>", "Path to conversation history JSON file").option("--yolo", "Auto-approve all approval requests").action(async (nodeId, cmdOpts) => {
|
|
1070
|
+
const client = getClient(program2.opts().serverUrl);
|
|
1071
|
+
const isJson = program2.opts().json;
|
|
1072
|
+
let node;
|
|
1073
|
+
try {
|
|
1074
|
+
const { nodes } = await client.getPlan(cmdOpts.projectId);
|
|
1075
|
+
node = nodes.find((n) => n.id === nodeId && !n.deletedAt);
|
|
1076
|
+
} catch (err) {
|
|
1077
|
+
console.error(chalk4.red(err.message));
|
|
1078
|
+
process.exitCode = 1;
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
if (!node) {
|
|
1082
|
+
console.error(chalk4.red(`Task not found: ${nodeId}`));
|
|
1083
|
+
process.exitCode = 1;
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
let visionDoc;
|
|
1087
|
+
try {
|
|
1088
|
+
const project = await client.getProject(cmdOpts.projectId);
|
|
1089
|
+
visionDoc = project.visionDoc || void 0;
|
|
1090
|
+
} catch {
|
|
1091
|
+
}
|
|
1092
|
+
let messages = [];
|
|
1093
|
+
if (cmdOpts.historyFile) {
|
|
1094
|
+
messages = loadHistory(cmdOpts.historyFile);
|
|
1095
|
+
}
|
|
1096
|
+
try {
|
|
1097
|
+
const response = await client.taskChat({
|
|
1098
|
+
message: cmdOpts.message,
|
|
1099
|
+
nodeId,
|
|
1100
|
+
projectId: cmdOpts.projectId,
|
|
1101
|
+
taskTitle: node.title,
|
|
1102
|
+
taskDescription: node.description || void 0,
|
|
1103
|
+
taskOutput: node.executionOutput || void 0,
|
|
1104
|
+
visionDoc,
|
|
1105
|
+
sessionId: cmdOpts.sessionId,
|
|
1106
|
+
model: cmdOpts.model,
|
|
1107
|
+
providerId: cmdOpts.provider,
|
|
1108
|
+
branchName: node.branchName || void 0,
|
|
1109
|
+
prUrl: node.prUrl || void 0,
|
|
1110
|
+
messages: messages.length > 0 ? messages : void 0
|
|
1111
|
+
});
|
|
1112
|
+
const approvalHandler = createApprovalHandler(client, !!cmdOpts.yolo);
|
|
1113
|
+
const result = await streamChatToStdout(response, {
|
|
1114
|
+
json: isJson,
|
|
1115
|
+
onApprovalRequest: approvalHandler
|
|
1116
|
+
});
|
|
1117
|
+
if (result.sessionId) {
|
|
1118
|
+
process.stderr.write(`
|
|
1119
|
+
${chalk4.dim(`Session: ${result.sessionId}`)}
|
|
1120
|
+
`);
|
|
1121
|
+
}
|
|
1122
|
+
if (cmdOpts.historyFile && result.assistantText) {
|
|
1123
|
+
messages.push({ role: "user", content: cmdOpts.message });
|
|
1124
|
+
messages.push({ role: "assistant", content: result.assistantText });
|
|
1125
|
+
saveHistory(cmdOpts.historyFile, messages);
|
|
1126
|
+
}
|
|
1127
|
+
console.log();
|
|
1128
|
+
} catch (err) {
|
|
1129
|
+
console.error(chalk4.red(`Chat failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1130
|
+
process.exitCode = 1;
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
881
1133
|
cmd.command("cancel <executionId>").description("Cancel a running task execution").option("--machine <id>", "Target machine ID").option("--node-id <id>", "Node ID").action(async (executionId, opts) => {
|
|
882
1134
|
const client = getClient(program2.opts().serverUrl);
|
|
883
1135
|
const isJson = program2.opts().json;
|
|
@@ -947,9 +1199,10 @@ function registerTaskCommands(program2) {
|
|
|
947
1199
|
process.exitCode = 1;
|
|
948
1200
|
}
|
|
949
1201
|
});
|
|
950
|
-
cmd.command("watch <executionId>").description("Watch real-time output from a running task via SSE").action(async (executionId) => {
|
|
1202
|
+
cmd.command("watch <executionId>").description("Watch real-time output from a running task via SSE").option("--yolo", "Auto-approve all approval requests").action(async (executionId, cmdOpts) => {
|
|
951
1203
|
const client = getClient(program2.opts().serverUrl);
|
|
952
1204
|
const isJson = program2.opts().json;
|
|
1205
|
+
const approvalHandler = createApprovalHandler(client, !!cmdOpts.yolo);
|
|
953
1206
|
try {
|
|
954
1207
|
const response = await client.streamEvents();
|
|
955
1208
|
if (!response.body) {
|
|
@@ -994,6 +1247,17 @@ ${chalk4.bold("--- Result:")} ${formatStatus(event.status ?? "unknown")} ${event
|
|
|
994
1247
|
case "task:stdout":
|
|
995
1248
|
process.stdout.write(event.data ?? "");
|
|
996
1249
|
break;
|
|
1250
|
+
case "task:approval_request": {
|
|
1251
|
+
const result = await approvalHandler({
|
|
1252
|
+
requestId: event.requestId,
|
|
1253
|
+
question: event.question,
|
|
1254
|
+
options: event.options,
|
|
1255
|
+
machineId: event.machineId,
|
|
1256
|
+
taskId: event.taskId
|
|
1257
|
+
});
|
|
1258
|
+
void result;
|
|
1259
|
+
break;
|
|
1260
|
+
}
|
|
997
1261
|
}
|
|
998
1262
|
} catch {
|
|
999
1263
|
}
|
|
@@ -2074,6 +2338,43 @@ function registerCompletionCommands(program2) {
|
|
|
2074
2338
|
});
|
|
2075
2339
|
}
|
|
2076
2340
|
|
|
2341
|
+
// src/commands/playground.ts
|
|
2342
|
+
import chalk11 from "chalk";
|
|
2343
|
+
function registerPlaygroundCommands(program2) {
|
|
2344
|
+
const playground = program2.command("playground").description("Run ephemeral AI executions");
|
|
2345
|
+
playground.command("start").description("Start a playground execution").requiredOption("--project-id <id>", "Project ID").requiredOption("--description <desc>", "What to execute").option("--dir <path>", "Working directory override").option("--model <model>", "AI model to use").option("--provider <provider>", "Preferred provider ID").option("--machine <id>", "Target machine ID").option("--yolo", "Auto-approve all approval requests").action(async (cmdOpts) => {
|
|
2346
|
+
const opts = program2.opts();
|
|
2347
|
+
const client = getClient(opts.serverUrl);
|
|
2348
|
+
const nodeId = `playground-${cmdOpts.projectId}-${Date.now()}`;
|
|
2349
|
+
console.log(chalk11.dim(`Starting playground execution ${chalk11.bold(nodeId)}...`));
|
|
2350
|
+
console.log();
|
|
2351
|
+
try {
|
|
2352
|
+
const response = await client.dispatchTask({
|
|
2353
|
+
nodeId,
|
|
2354
|
+
projectId: cmdOpts.projectId,
|
|
2355
|
+
skipSafetyCheck: true,
|
|
2356
|
+
description: cmdOpts.description,
|
|
2357
|
+
title: "Playground execution",
|
|
2358
|
+
model: cmdOpts.model,
|
|
2359
|
+
preferredProvider: cmdOpts.provider,
|
|
2360
|
+
targetMachineId: cmdOpts.machine,
|
|
2361
|
+
...cmdOpts.dir ? { workingDirectory: cmdOpts.dir } : {}
|
|
2362
|
+
});
|
|
2363
|
+
const approvalHandler = createApprovalHandler(client, !!cmdOpts.yolo);
|
|
2364
|
+
await streamDispatchToStdout(response, {
|
|
2365
|
+
json: opts.json,
|
|
2366
|
+
onApprovalRequest: approvalHandler
|
|
2367
|
+
});
|
|
2368
|
+
console.log();
|
|
2369
|
+
console.log(chalk11.green("Playground execution complete."));
|
|
2370
|
+
console.log(chalk11.dim(`For follow-up: astro-cli task chat ${nodeId} --project-id ${cmdOpts.projectId} --message "..."`));
|
|
2371
|
+
} catch (err) {
|
|
2372
|
+
console.error(chalk11.red(`Playground failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
2373
|
+
process.exitCode = 1;
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2077
2378
|
// src/index.ts
|
|
2078
2379
|
var program = new Command();
|
|
2079
2380
|
program.name("astro-cli").description("CLI for managing Astro projects, plans, tasks, and environments").version("0.2.0").option("--json", "Machine-readable JSON output").option("--quiet", "Suppress spinners and decorative output").option("--server-url <url>", "Override server URL");
|
|
@@ -2087,6 +2388,7 @@ registerEnvCommands(program);
|
|
|
2087
2388
|
registerConfigCommands(program);
|
|
2088
2389
|
registerAuthCommands(program);
|
|
2089
2390
|
registerCompletionCommands(program);
|
|
2391
|
+
registerPlaygroundCommands(program);
|
|
2090
2392
|
program.command("tui").description("Launch interactive terminal UI").action(async () => {
|
|
2091
2393
|
const { launchTui } = await import("./tui.js");
|
|
2092
2394
|
await launchTui(program.opts().serverUrl);
|