rush-ai 0.4.0 → 0.6.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
@@ -2,9 +2,7 @@
2
2
 
3
3
  # rush-ai
4
4
 
5
- **Command-line interface for the Rush AI platform**
6
-
7
- *Create tasks, manage agents, stream results, and integrate IDE plugins — all from the terminal*
5
+ **Call Rush agents from your terminal — relay the context, not the prompt.**
8
6
 
9
7
  [![npm version](https://img.shields.io/npm/v/rush-ai.svg)](https://www.npmjs.com/package/rush-ai)
10
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -13,23 +11,60 @@
13
11
 
14
12
  ---
15
13
 
16
- ## Quick Start
14
+ ## Why rush-ai
15
+
16
+ ### Hand off your context, not your prompt
17
+
18
+ You're already deep in a conversation with your local agent — Cursor, Claude Code, or your own. It knows the codebase, the constraints, what you've already tried. When part of that work belongs on the Rush platform (building a site, asking a specialist agent), you don't want to summarize everything again.
19
+
20
+ `rush-ai` is the hand-off. Your local agent calls `rush-ai task create` with the context it already has. Rush picks it up, you switch to the browser to watch the preview, and you keep iterating there.
17
21
 
18
22
  ```bash
19
- npm install -g rush-ai
23
+ # From inside Cursor / Claude Code, the local agent runs:
24
+ npx rush-ai task create -a web-builder -p "做一个公司官网,风格大气专业"
25
+ ```
20
26
 
21
- rush-ai auth login
22
- rush-ai agent list
23
- rush-ai task create -a <agent> -p "Build a landing page"
24
- rush-ai task watch <task-id>
27
+ ### Rush agents as your sub-agents
28
+
29
+ Rush hosts a growing shelf of specialist agents. Any of them can become a sub-agent inside your own workflow — you don't rewrite the prompt, you call the agent.
30
+
31
+ ```bash
32
+ # Browse the shelf
33
+ npx rush-ai agent list
34
+
35
+ # Pick one and put it to work — e.g. the HR analytics specialist
36
+ npx rush-ai task create -a 人力资源分析专家 -p "分析 Q1 部门人效趋势"
25
37
  ```
26
38
 
39
+ ### Featured: `web-builder`
40
+
41
+ `web-builder` is Rush's most intensive agent. One prompt gives you back a live preview URL and a git repository — share it, clone it, or keep iterating via `task send`.
42
+
43
+ ```bash
44
+ npx rush-ai task create -a web-builder -p "Build a product landing page"
45
+ # → Task lodig8oknq0r running
46
+
47
+ npx rush-ai task status lodig8oknq0r
48
+ # Preview: https://lodig8oknq0r-preview.rush.zhenguanyu.com/
49
+ # Git: https://gitlab-ee.zhenguanyu.com/rush/online/lodig8oknq0r
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ```bash
55
+ npx rush-ai auth login
56
+ npx rush-ai agent list
57
+ npx rush-ai task create -a rush -p "Add a contact form to the homepage"
58
+ npx rush-ai task watch <task-id>
59
+ ```
60
+
61
+ `rush` is the default general-purpose agent. Swap `-a` for any agent from `agent list` to call a specialist instead.
62
+
27
63
  ## Features
28
64
 
29
- - **Task lifecycle** — Create, watch (SSE streaming), cancel, and download artifacts
30
- - **Agent management** — Browse agents, inspect skills and MCP servers
31
- - **MCP integration** — Discover MCP servers, list tools, run as MCP stdio server
32
- - **IDE plugins** — Install, update, diagnose plugins for Claude Code and Cursor
65
+ - **Task lifecycle** — Create, watch (SSE streaming), send follow-ups, cancel, and download artifacts
66
+ - **Agent shelf** — Browse agents, inspect skills and MCP servers, plug any agent into your own workflow as a sub-agent
67
+ - **MCP integration** — Discover MCP servers, list tools, run as an MCP stdio server
33
68
  - **Multi-profile** — Switch between environments (test, production, custom)
34
69
  - **Shell completion** — Bash, Zsh, and Fish auto-completion
35
70
  - **CI-friendly** — `--json` output, `--ci` mode, non-zero exit codes on failure
@@ -39,8 +74,11 @@ rush-ai task watch <task-id>
39
74
  **Requirements:** Node.js >= 18.0.0
40
75
 
41
76
  ```bash
42
- npm install -g rush-ai # Global install
43
- npx rush-ai <command> # Or use npx directly
77
+ # Recommended always runs the latest version, zero setup
78
+ npx rush-ai <command>
79
+
80
+ # Optional — pin a global binary
81
+ npm install -g rush-ai
44
82
  ```
45
83
 
46
84
  ## Commands
@@ -57,18 +95,13 @@ npx rush-ai <command> # Or use npx directly
57
95
  | `task status <id>` | - | Check task status |
58
96
  | `task result <id>` | - | Get task result and file summary |
59
97
  | `task files <id>` | - | List and download task artifacts |
98
+ | `task messages <id>` | - | View conversation messages for a task |
60
99
  | `task list` | `task ls` | List tasks |
61
100
  | `task watch <id>` | - | Stream task execution in real-time (SSE) |
62
101
  | `task cancel <id>` | - | Cancel a running task |
63
102
  | `mcp serve` | - | Start MCP stdio server |
64
103
  | `mcp list` | `mcp ls` | List available MCP servers |
65
104
  | `mcp list-tools <id>` | - | List tools provided by an MCP server |
66
- | `plugin install <name>` | - | Install an IDE plugin |
67
- | `plugin update <name>` | - | Update an installed plugin |
68
- | `plugin uninstall <name>` | - | Remove a plugin |
69
- | `plugin list` | `plugin ls` | List available and installed plugins |
70
- | `plugin status <name>` | - | Show plugin installation status |
71
- | `plugin doctor [name]` | - | Diagnose plugin issues |
72
105
  | `config show` | - | Show current configuration |
73
106
  | `config set <key> <value>` | - | Set a config value (`api`, `collectMetrics`, `currentTeam`) |
74
107
  | `config use <profile>` | - | Switch active profile |
@@ -124,6 +157,13 @@ rush-ai task send <task-id> -p "Change the button color to blue"
124
157
  # Pipe prompt from stdin
125
158
  echo "Refactor the auth module" | rush-ai task create -a rush
126
159
 
160
+ # View conversation messages
161
+ rush-ai task messages <task-id>
162
+ rush-ai task messages <task-id> -c <conversation-id>
163
+ rush-ai task messages <task-id> --last 5 --role user
164
+ rush-ai task messages <task-id> --compact
165
+ rush-ai task messages <task-id> --json
166
+
127
167
  # List recent tasks
128
168
  rush-ai task list -l 10 -s completed
129
169
  ```
@@ -140,19 +180,6 @@ rush-ai mcp list --search "database"
140
180
  rush-ai mcp list-tools <server-id>
141
181
  ```
142
182
 
143
- ### IDE Plugin Management
144
-
145
- ```bash
146
- # Install plugin for Claude Code
147
- rush-ai plugin install claude-code
148
-
149
- # Check plugin health
150
- rush-ai plugin doctor claude-code
151
-
152
- # Update plugin
153
- rush-ai plugin update claude-code --force
154
- ```
155
-
156
183
  ### Multi-Profile Configuration
157
184
 
158
185
  ```bash
@@ -195,9 +222,7 @@ Config files are stored in `~/.rush/`:
195
222
  ```
196
223
  ~/.rush/
197
224
  ├── auth.json # Credentials (token, refresh token, expiry)
198
- ├── config.json # Global settings (profiles, metrics, team)
199
- └── plugins/
200
- └── installed.json # Plugin manifest
225
+ └── config.json # Global settings (profiles, metrics, team)
201
226
  ```
202
227
 
203
228
  ## Development
package/dist/index.js CHANGED
@@ -3342,7 +3342,15 @@ function gitPushUrl(projectPath, url) {
3342
3342
  cwd: projectPath,
3343
3343
  stdio: "pipe",
3344
3344
  timeout: 3e5,
3345
- maxBuffer: 10 * 1024 * 1024
3345
+ maxBuffer: 10 * 1024 * 1024,
3346
+ env: {
3347
+ ...process.env,
3348
+ // 失败时不要弹密码 / SSH key 提示,否则在非交互环境(CI / 测试)
3349
+ // 会一直 hang 到外层 timeout(300s)才返回。
3350
+ GIT_TERMINAL_PROMPT: "0",
3351
+ GIT_ASKPASS: "true",
3352
+ SSH_ASKPASS: "true"
3353
+ }
3346
3354
  });
3347
3355
  return { success: true, stderr: "" };
3348
3356
  } catch (err) {
@@ -3508,6 +3516,54 @@ function registerDeploySubcommand(task, program) {
3508
3516
  }
3509
3517
 
3510
3518
  // src/commands/task/index.ts
3519
+ var MAX_TEXT_LENGTH = 500;
3520
+ function truncateText(text) {
3521
+ if (text.length <= MAX_TEXT_LENGTH) return text;
3522
+ return text.slice(0, MAX_TEXT_LENGTH) + "...";
3523
+ }
3524
+ function summarizeTools(parts) {
3525
+ const toolParts = parts.filter(
3526
+ (p) => p.type === "tool-invocation" || p.type.startsWith("tool-")
3527
+ );
3528
+ if (toolParts.length === 0) return "";
3529
+ return toolParts.map((p) => {
3530
+ if (p.toolName) return p.toolName;
3531
+ if (p.type.startsWith("tool-")) return p.type.slice(5);
3532
+ return "unknown";
3533
+ }).join(", ");
3534
+ }
3535
+ function formatTimestamp(ts) {
3536
+ if (!ts) return "";
3537
+ const d = new Date(ts);
3538
+ return d.toLocaleString();
3539
+ }
3540
+ function renderMessages(messages, conversationId, compact) {
3541
+ output.log(
3542
+ output.bold(`Conversation ${conversationId} \u2014 ${messages.length} messages`)
3543
+ );
3544
+ output.newline();
3545
+ for (const msg of messages) {
3546
+ const ts = formatTimestamp(msg.createdAt);
3547
+ output.log(`[${msg.role}] ${ts}`);
3548
+ const textParts = (msg.parts ?? []).filter(
3549
+ (p) => p.type === "text" && p.text
3550
+ );
3551
+ if (textParts.length > 0) {
3552
+ for (const tp of textParts) {
3553
+ output.log(` ${truncateText(tp.text)}`);
3554
+ }
3555
+ } else if (msg.content) {
3556
+ output.log(` ${truncateText(msg.content)}`);
3557
+ }
3558
+ if (!compact && msg.role === "assistant" && msg.parts) {
3559
+ const toolSummary = summarizeTools(msg.parts);
3560
+ if (toolSummary) {
3561
+ output.dim(` Tools: ${toolSummary}`);
3562
+ }
3563
+ }
3564
+ output.newline();
3565
+ }
3566
+ }
3511
3567
  var VALID_CATEGORIES = ["code", "image", "document", "other"];
3512
3568
  function sanitizeFilePath(filePath, outputDir) {
3513
3569
  let stripped = filePath.replace(/^[/\\]+/, "").replace(/^[a-zA-Z]:/, "");
@@ -3521,8 +3577,25 @@ function sanitizeFilePath(filePath, outputDir) {
3521
3577
  }
3522
3578
  return target;
3523
3579
  }
3524
- async function downloadFile(url, destPath) {
3525
- const response = await fetch(url);
3580
+ async function downloadFile(file, destPath, client) {
3581
+ let response = null;
3582
+ if (file.ossPath) {
3583
+ try {
3584
+ const proxyResponse = await client.fetchRaw(
3585
+ `/api/files/oss-preview?action=file&path=${encodeURIComponent(file.ossPath)}`
3586
+ );
3587
+ if (proxyResponse.ok) {
3588
+ response = proxyResponse;
3589
+ }
3590
+ } catch {
3591
+ }
3592
+ }
3593
+ if (!response && file.ossUrl) {
3594
+ response = await fetch(file.ossUrl);
3595
+ }
3596
+ if (!response) {
3597
+ throw new Error("No download path available");
3598
+ }
3526
3599
  if (!response.ok) {
3527
3600
  throw new Error(
3528
3601
  `Download failed: ${response.status} ${response.statusText}`
@@ -3619,8 +3692,23 @@ function registerTaskCommand(program) {
3619
3692
  output.log(output.bold(`Task ${data.id}`));
3620
3693
  output.log(` Agent: ${data.agent}`);
3621
3694
  output.log(` Status: ${data.status}`);
3695
+ if (data.template) {
3696
+ output.log(` Template: ${data.template}`);
3697
+ }
3698
+ if (data.podImageName) {
3699
+ output.log(` Image: ${data.podImageName}`);
3700
+ }
3622
3701
  output.log(` Created: ${new Date(data.createdAt).toLocaleString()}`);
3623
3702
  output.log(` Updated: ${new Date(data.updatedAt).toLocaleString()}`);
3703
+ if (data.previewUrl) {
3704
+ output.log(` Preview: ${data.previewUrl}`);
3705
+ }
3706
+ if (data.gitRepoUrl) {
3707
+ output.log(` Git: ${data.gitRepoUrl}`);
3708
+ }
3709
+ if (data.deployedProductionUrl) {
3710
+ output.log(` Production: ${data.deployedProductionUrl}`);
3711
+ }
3624
3712
  if (data.error) {
3625
3713
  output.error(` Error: ${data.error}`);
3626
3714
  }
@@ -3704,8 +3792,8 @@ function registerTaskCommand(program) {
3704
3792
  output.error(`File not found: ${options.download}`);
3705
3793
  process.exit(1);
3706
3794
  }
3707
- if (!file.ossUrl) {
3708
- output.error(`File "${file.fileName}" has no download URL.`);
3795
+ if (!file.ossPath && !file.ossUrl) {
3796
+ output.error(`File "${file.fileName}" has no download path.`);
3709
3797
  process.exit(1);
3710
3798
  }
3711
3799
  const outputDir = options.output || ".";
@@ -3714,7 +3802,7 @@ function registerTaskCommand(program) {
3714
3802
  output.error(`Unsafe file path: ${file.filePath}`);
3715
3803
  process.exit(1);
3716
3804
  }
3717
- await downloadFile(file.ossUrl, destPath);
3805
+ await downloadFile(file, destPath, client);
3718
3806
  if (format === "json") {
3719
3807
  output.log(
3720
3808
  JSON.stringify(
@@ -3734,10 +3822,10 @@ function registerTaskCommand(program) {
3734
3822
  let skipped = 0;
3735
3823
  let failed = 0;
3736
3824
  for (const file of files) {
3737
- if (!file.ossUrl) {
3825
+ if (!file.ossPath && !file.ossUrl) {
3738
3826
  skipped++;
3739
3827
  if (format !== "json") {
3740
- output.dim(` Skipped (no URL): ${file.filePath}`);
3828
+ output.dim(` Skipped (no download path): ${file.filePath}`);
3741
3829
  }
3742
3830
  continue;
3743
3831
  }
@@ -3750,7 +3838,7 @@ function registerTaskCommand(program) {
3750
3838
  continue;
3751
3839
  }
3752
3840
  try {
3753
- await downloadFile(file.ossUrl, destPath);
3841
+ await downloadFile(file, destPath, client);
3754
3842
  downloaded++;
3755
3843
  if (format !== "json") {
3756
3844
  output.dim(` Downloaded: ${file.filePath}`);
@@ -3807,7 +3895,65 @@ function registerTaskCommand(program) {
3807
3895
  output.log(formatOutput(rows, format));
3808
3896
  }
3809
3897
  );
3810
- 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) => {
3898
+ 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(
3899
+ async (id, options) => {
3900
+ requireAuth();
3901
+ const format = resolveFormat(program.opts());
3902
+ const client = createClient();
3903
+ let conversationId = options.conversation;
3904
+ if (!conversationId) {
3905
+ const { data: convData } = await client.get(
3906
+ `/api/chat/${encodeURIComponent(id)}/conversations`
3907
+ );
3908
+ if (!convData.conversations || convData.conversations.length === 0) {
3909
+ if (format === "json") {
3910
+ output.log(
3911
+ JSON.stringify({ messages: [], conversationId: null }, null, 2)
3912
+ );
3913
+ } else {
3914
+ output.info("No conversations found for this task.");
3915
+ }
3916
+ return;
3917
+ }
3918
+ conversationId = convData.conversations[0].id;
3919
+ }
3920
+ const { data } = await client.get(
3921
+ `/api/chat/${encodeURIComponent(id)}/conversations/${encodeURIComponent(conversationId)}`
3922
+ );
3923
+ let messages = data.messages ?? [];
3924
+ if (messages.length === 0) {
3925
+ if (format === "json") {
3926
+ output.log(
3927
+ JSON.stringify({ messages: [], conversationId }, null, 2)
3928
+ );
3929
+ } else {
3930
+ output.info("No messages in this conversation.");
3931
+ }
3932
+ return;
3933
+ }
3934
+ if (options.role) {
3935
+ messages = messages.filter((m) => m.role === options.role);
3936
+ }
3937
+ if (options.last) {
3938
+ const n = parseInt(options.last, 10);
3939
+ if (!isNaN(n) && n > 0) {
3940
+ messages = messages.slice(-n);
3941
+ }
3942
+ }
3943
+ if (format === "json") {
3944
+ output.log(
3945
+ JSON.stringify(
3946
+ { messages, conversationId, metadata: data.metadata },
3947
+ null,
3948
+ 2
3949
+ )
3950
+ );
3951
+ } else {
3952
+ renderMessages(messages, conversationId, !!options.compact);
3953
+ }
3954
+ }
3955
+ );
3956
+ task.command("list").alias("ls").description("List tasks").option("-l, --limit <limit>", "Maximum number of tasks", "50").option("-s, --status <status>", "Filter by status").action(async (options) => {
3811
3957
  requireAuth();
3812
3958
  const format = resolveFormat(program.opts());
3813
3959
  const client = createClient();