@astroanywhere/cli 0.1.1 → 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/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-7H7WD7QX.js";
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,9 +2338,46 @@ 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
- program.name("astro-cli").description("CLI for managing Astro projects, plans, tasks, and environments").version("0.1.1").option("--json", "Machine-readable JSON output").option("--quiet", "Suppress spinners and decorative output").option("--server-url <url>", "Override server URL");
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");
2080
2381
  registerProjectCommands(program);
2081
2382
  registerPlanCommands(program);
2082
2383
  registerTaskCommands(program);
@@ -2087,4 +2388,9 @@ registerEnvCommands(program);
2087
2388
  registerConfigCommands(program);
2088
2389
  registerAuthCommands(program);
2089
2390
  registerCompletionCommands(program);
2391
+ registerPlaygroundCommands(program);
2392
+ program.command("tui").description("Launch interactive terminal UI").action(async () => {
2393
+ const { launchTui } = await import("./tui.js");
2394
+ await launchTui(program.opts().serverUrl);
2395
+ });
2090
2396
  program.parse();