rush-ai 0.4.0 → 0.5.0

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/README.md CHANGED
@@ -57,6 +57,7 @@ npx rush-ai <command> # Or use npx directly
57
57
  | `task status <id>` | - | Check task status |
58
58
  | `task result <id>` | - | Get task result and file summary |
59
59
  | `task files <id>` | - | List and download task artifacts |
60
+ | `task messages <id>` | - | View conversation messages for a task |
60
61
  | `task list` | `task ls` | List tasks |
61
62
  | `task watch <id>` | - | Stream task execution in real-time (SSE) |
62
63
  | `task cancel <id>` | - | Cancel a running task |
@@ -124,6 +125,13 @@ rush-ai task send <task-id> -p "Change the button color to blue"
124
125
  # Pipe prompt from stdin
125
126
  echo "Refactor the auth module" | rush-ai task create -a rush
126
127
 
128
+ # View conversation messages
129
+ rush-ai task messages <task-id>
130
+ rush-ai task messages <task-id> -c <conversation-id>
131
+ rush-ai task messages <task-id> --last 5 --role user
132
+ rush-ai task messages <task-id> --compact
133
+ rush-ai task messages <task-id> --json
134
+
127
135
  # List recent tasks
128
136
  rush-ai task list -l 10 -s completed
129
137
  ```
package/dist/index.js CHANGED
@@ -3508,6 +3508,54 @@ function registerDeploySubcommand(task, program) {
3508
3508
  }
3509
3509
 
3510
3510
  // src/commands/task/index.ts
3511
+ var MAX_TEXT_LENGTH = 500;
3512
+ function truncateText(text) {
3513
+ if (text.length <= MAX_TEXT_LENGTH) return text;
3514
+ return text.slice(0, MAX_TEXT_LENGTH) + "...";
3515
+ }
3516
+ function summarizeTools(parts) {
3517
+ const toolParts = parts.filter(
3518
+ (p) => p.type === "tool-invocation" || p.type.startsWith("tool-")
3519
+ );
3520
+ if (toolParts.length === 0) return "";
3521
+ return toolParts.map((p) => {
3522
+ if (p.toolName) return p.toolName;
3523
+ if (p.type.startsWith("tool-")) return p.type.slice(5);
3524
+ return "unknown";
3525
+ }).join(", ");
3526
+ }
3527
+ function formatTimestamp(ts) {
3528
+ if (!ts) return "";
3529
+ const d = new Date(ts);
3530
+ return d.toLocaleString();
3531
+ }
3532
+ function renderMessages(messages, conversationId, compact) {
3533
+ output.log(
3534
+ output.bold(`Conversation ${conversationId} \u2014 ${messages.length} messages`)
3535
+ );
3536
+ output.newline();
3537
+ for (const msg of messages) {
3538
+ const ts = formatTimestamp(msg.createdAt);
3539
+ output.log(`[${msg.role}] ${ts}`);
3540
+ const textParts = (msg.parts ?? []).filter(
3541
+ (p) => p.type === "text" && p.text
3542
+ );
3543
+ if (textParts.length > 0) {
3544
+ for (const tp of textParts) {
3545
+ output.log(` ${truncateText(tp.text)}`);
3546
+ }
3547
+ } else if (msg.content) {
3548
+ output.log(` ${truncateText(msg.content)}`);
3549
+ }
3550
+ if (!compact && msg.role === "assistant" && msg.parts) {
3551
+ const toolSummary = summarizeTools(msg.parts);
3552
+ if (toolSummary) {
3553
+ output.dim(` Tools: ${toolSummary}`);
3554
+ }
3555
+ }
3556
+ output.newline();
3557
+ }
3558
+ }
3511
3559
  var VALID_CATEGORIES = ["code", "image", "document", "other"];
3512
3560
  function sanitizeFilePath(filePath, outputDir) {
3513
3561
  let stripped = filePath.replace(/^[/\\]+/, "").replace(/^[a-zA-Z]:/, "");
@@ -3521,8 +3569,25 @@ function sanitizeFilePath(filePath, outputDir) {
3521
3569
  }
3522
3570
  return target;
3523
3571
  }
3524
- async function downloadFile(url, destPath) {
3525
- const response = await fetch(url);
3572
+ async function downloadFile(file, destPath, client) {
3573
+ let response = null;
3574
+ if (file.ossPath) {
3575
+ try {
3576
+ const proxyResponse = await client.fetchRaw(
3577
+ `/api/files/oss-preview?action=file&path=${encodeURIComponent(file.ossPath)}`
3578
+ );
3579
+ if (proxyResponse.ok) {
3580
+ response = proxyResponse;
3581
+ }
3582
+ } catch {
3583
+ }
3584
+ }
3585
+ if (!response && file.ossUrl) {
3586
+ response = await fetch(file.ossUrl);
3587
+ }
3588
+ if (!response) {
3589
+ throw new Error("No download path available");
3590
+ }
3526
3591
  if (!response.ok) {
3527
3592
  throw new Error(
3528
3593
  `Download failed: ${response.status} ${response.statusText}`
@@ -3619,8 +3684,23 @@ function registerTaskCommand(program) {
3619
3684
  output.log(output.bold(`Task ${data.id}`));
3620
3685
  output.log(` Agent: ${data.agent}`);
3621
3686
  output.log(` Status: ${data.status}`);
3687
+ if (data.template) {
3688
+ output.log(` Template: ${data.template}`);
3689
+ }
3690
+ if (data.podImageName) {
3691
+ output.log(` Image: ${data.podImageName}`);
3692
+ }
3622
3693
  output.log(` Created: ${new Date(data.createdAt).toLocaleString()}`);
3623
3694
  output.log(` Updated: ${new Date(data.updatedAt).toLocaleString()}`);
3695
+ if (data.previewUrl) {
3696
+ output.log(` Preview: ${data.previewUrl}`);
3697
+ }
3698
+ if (data.gitRepoUrl) {
3699
+ output.log(` Git: ${data.gitRepoUrl}`);
3700
+ }
3701
+ if (data.deployedProductionUrl) {
3702
+ output.log(` Production: ${data.deployedProductionUrl}`);
3703
+ }
3624
3704
  if (data.error) {
3625
3705
  output.error(` Error: ${data.error}`);
3626
3706
  }
@@ -3704,8 +3784,8 @@ function registerTaskCommand(program) {
3704
3784
  output.error(`File not found: ${options.download}`);
3705
3785
  process.exit(1);
3706
3786
  }
3707
- if (!file.ossUrl) {
3708
- output.error(`File "${file.fileName}" has no download URL.`);
3787
+ if (!file.ossPath && !file.ossUrl) {
3788
+ output.error(`File "${file.fileName}" has no download path.`);
3709
3789
  process.exit(1);
3710
3790
  }
3711
3791
  const outputDir = options.output || ".";
@@ -3714,7 +3794,7 @@ function registerTaskCommand(program) {
3714
3794
  output.error(`Unsafe file path: ${file.filePath}`);
3715
3795
  process.exit(1);
3716
3796
  }
3717
- await downloadFile(file.ossUrl, destPath);
3797
+ await downloadFile(file, destPath, client);
3718
3798
  if (format === "json") {
3719
3799
  output.log(
3720
3800
  JSON.stringify(
@@ -3734,10 +3814,10 @@ function registerTaskCommand(program) {
3734
3814
  let skipped = 0;
3735
3815
  let failed = 0;
3736
3816
  for (const file of files) {
3737
- if (!file.ossUrl) {
3817
+ if (!file.ossPath && !file.ossUrl) {
3738
3818
  skipped++;
3739
3819
  if (format !== "json") {
3740
- output.dim(` Skipped (no URL): ${file.filePath}`);
3820
+ output.dim(` Skipped (no download path): ${file.filePath}`);
3741
3821
  }
3742
3822
  continue;
3743
3823
  }
@@ -3750,7 +3830,7 @@ function registerTaskCommand(program) {
3750
3830
  continue;
3751
3831
  }
3752
3832
  try {
3753
- await downloadFile(file.ossUrl, destPath);
3833
+ await downloadFile(file, destPath, client);
3754
3834
  downloaded++;
3755
3835
  if (format !== "json") {
3756
3836
  output.dim(` Downloaded: ${file.filePath}`);
@@ -3807,6 +3887,64 @@ function registerTaskCommand(program) {
3807
3887
  output.log(formatOutput(rows, format));
3808
3888
  }
3809
3889
  );
3890
+ task.command("messages").description("View conversation messages for a task").argument("<id>", "Task ID (project ID)").option("-c, --conversation <id>", "Specific conversation ID").option("--last <n>", "Show only last N messages").option("--role <role>", "Filter by role (user, assistant, system)").option("--compact", "Text-only output, no tool details or reasoning").action(
3891
+ async (id, options) => {
3892
+ requireAuth();
3893
+ const format = resolveFormat(program.opts());
3894
+ const client = createClient();
3895
+ let conversationId = options.conversation;
3896
+ if (!conversationId) {
3897
+ const { data: convData } = await client.get(
3898
+ `/api/chat/${encodeURIComponent(id)}/conversations`
3899
+ );
3900
+ if (!convData.conversations || convData.conversations.length === 0) {
3901
+ if (format === "json") {
3902
+ output.log(
3903
+ JSON.stringify({ messages: [], conversationId: null }, null, 2)
3904
+ );
3905
+ } else {
3906
+ output.info("No conversations found for this task.");
3907
+ }
3908
+ return;
3909
+ }
3910
+ conversationId = convData.conversations[0].id;
3911
+ }
3912
+ const { data } = await client.get(
3913
+ `/api/chat/${encodeURIComponent(id)}/conversations/${encodeURIComponent(conversationId)}`
3914
+ );
3915
+ let messages = data.messages ?? [];
3916
+ if (messages.length === 0) {
3917
+ if (format === "json") {
3918
+ output.log(
3919
+ JSON.stringify({ messages: [], conversationId }, null, 2)
3920
+ );
3921
+ } else {
3922
+ output.info("No messages in this conversation.");
3923
+ }
3924
+ return;
3925
+ }
3926
+ if (options.role) {
3927
+ messages = messages.filter((m) => m.role === options.role);
3928
+ }
3929
+ if (options.last) {
3930
+ const n = parseInt(options.last, 10);
3931
+ if (!isNaN(n) && n > 0) {
3932
+ messages = messages.slice(-n);
3933
+ }
3934
+ }
3935
+ if (format === "json") {
3936
+ output.log(
3937
+ JSON.stringify(
3938
+ { messages, conversationId, metadata: data.metadata },
3939
+ null,
3940
+ 2
3941
+ )
3942
+ );
3943
+ } else {
3944
+ renderMessages(messages, conversationId, !!options.compact);
3945
+ }
3946
+ }
3947
+ );
3810
3948
  task.command("list").alias("ls").description("List tasks").option("-l, --limit <limit>", "Maximum number of tasks", "20").option("-s, --status <status>", "Filter by status").action(async (options) => {
3811
3949
  requireAuth();
3812
3950
  const format = resolveFormat(program.opts());