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 +62 -37
- package/dist/index.js +156 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# rush-ai
|
|
4
4
|
|
|
5
|
-
**
|
|
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
|
[](https://www.npmjs.com/package/rush-ai)
|
|
10
8
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -13,23 +11,60 @@
|
|
|
13
11
|
|
|
14
12
|
---
|
|
15
13
|
|
|
16
|
-
##
|
|
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
|
-
|
|
23
|
+
# From inside Cursor / Claude Code, the local agent runs:
|
|
24
|
+
npx rush-ai task create -a web-builder -p "做一个公司官网,风格大气专业"
|
|
25
|
+
```
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
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
|
-
|
|
43
|
-
npx rush-ai <command>
|
|
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
|
-
|
|
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(
|
|
3525
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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("
|
|
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();
|