rush-ai 0.6.0 → 0.7.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/AGENTS.md ADDED
@@ -0,0 +1,37 @@
1
+ # AGENTS.md — for AI agents using rush-ai
2
+
3
+ If you are an AI agent (Cursor, Claude Code, or another IDE-resident assistant) and you landed in this package, read this file first.
4
+
5
+ ## What rush-ai is for
6
+
7
+ `rush-ai` is how a local agent hands off work to the Rush platform — without asking the user to re-state anything. Two situations you'll use it in:
8
+
9
+ 1. **Hand-off** — the conversation you're in produces work that belongs on Rush (a live website, a task for a specialist). Relay the context you already have. → read [`skills/hand-off.md`](./skills/hand-off.md).
10
+ 2. **Sub-agent in your workflow** — you're composing a multi-step plan and a step needs a specialist from Rush's agent shelf. → read [`skills/agent-shelf.md`](./skills/agent-shelf.md).
11
+
12
+ ## Getting usage on demand
13
+
14
+ From inside any shell (the one you already have a Bash tool for), these three commands give you everything you need:
15
+
16
+ ```bash
17
+ npx rush-ai skill # prints the index — start here
18
+ npx rush-ai skill hand-off # full hand-off playbook
19
+ npx rush-ai skill agent-shelf # full agent-shelf playbook
20
+ npx rush-ai agent list # see the live list of available agents
21
+ ```
22
+
23
+ Every command also supports `--help` with a "When to use" section and example flows.
24
+
25
+ ## Defaults that save you a round-trip
26
+
27
+ - Recommended invocation: **`npx rush-ai`** (always latest, zero install).
28
+ - Default agent: **`rush`** — `-a` defaults to `rush` when omitted. Pass `-a web-builder` for site-building tasks.
29
+ - Always pass **`--json`** when the result will be parsed.
30
+ - Iterate with **`task send <id>`**, not a new `task create`.
31
+
32
+ ## What NOT to do
33
+
34
+ - Don't ask the user to summarize requirements they already told you — relay them.
35
+ - Don't invent agent names; always confirm via `agent list`.
36
+ - Don't run `npx rush-ai auth login` for the user (it's interactive / opens a browser). Ask them to.
37
+ - Don't silently retry a failed task. Surface the error and ask what to do.
package/dist/index.js CHANGED
@@ -161,37 +161,122 @@ function requireAuth() {
161
161
  }
162
162
 
163
163
  // src/commands/agent/index.ts
164
+ var AGENT_HELP_AFTER = `
165
+ When to use:
166
+ Rush hosts a shelf of specialist agents \u2014 call them as sub-agents from
167
+ your own workflows instead of writing the prompts yourself. Always
168
+ discover first, then invoke.
169
+
170
+ Typical flows:
171
+ $ rush-ai agent list --default --json # just the built-in defaults
172
+ $ rush-ai agent list --search "\u4EBA\u529B" --json # fuzzy-filter by name / desc
173
+ $ rush-ai agent list --all --json # everything \u2014 may be long
174
+ $ rush-ai agent info <name> --json # inspect one agent's skills / MCP
175
+
176
+ $ rush-ai task create -a <name> -p "..." # call the agent as a sub-agent
177
+ $ rush-ai task status <id> --json # grab its output
178
+
179
+ Built-in default agents (see --default):
180
+ rush general-purpose agent
181
+ web-builder site / landing-page builder (returns previewUrl + gitRepoUrl)
182
+ skill-publisher publishes reusable skills back onto the platform
183
+
184
+ For agents: run \`rush-ai skill agent-shelf\` for the full playbook.
185
+ `;
164
186
  function registerAgentCommand(program) {
165
- const agent = program.command("agent").description("Manage and inspect agents");
166
- agent.command("list").alias("ls").description("List available agents").action(async () => {
167
- requireAuth();
168
- const format = resolveFormat(program.opts());
169
- const client = createClient();
170
- const { data } = await client.get("/api/agents");
171
- if (format === "json") {
172
- output.log(JSON.stringify(data, null, 2));
173
- return;
174
- }
175
- if (data.agents.length === 0) {
176
- output.info("No agents found.");
177
- return;
187
+ const agent = program.command("agent").description("Manage and inspect agents").addHelpText("after", AGENT_HELP_AFTER);
188
+ agent.command("list").alias("ls").description("List available agents").option(
189
+ "-l, --limit <n>",
190
+ "Max agents per page (1\u2013500, default 50)",
191
+ (v) => Number.parseInt(v, 10),
192
+ 50
193
+ ).option(
194
+ "-p, --page <n>",
195
+ "Page number (1-indexed, default 1)",
196
+ (v) => Number.parseInt(v, 10),
197
+ 1
198
+ ).option("-q, --search <text>", "Filter agents by name / description").option(
199
+ "--default",
200
+ "Only show built-in default agents (web-builder, rush, skill-publisher, ...)"
201
+ ).option(
202
+ "--all",
203
+ "Fetch every page and return the full list (may issue several requests)"
204
+ ).action(
205
+ async (opts) => {
206
+ requireAuth();
207
+ const format = resolveFormat(program.opts());
208
+ const client = createClient();
209
+ const baseParams = new URLSearchParams();
210
+ const rawLimit = Number.isFinite(opts.limit) ? opts.limit : 50;
211
+ const apiLimit = Math.min(Math.max(rawLimit, 1), 500);
212
+ const rawPage = Number.isFinite(opts.page) ? opts.page : 1;
213
+ const requestedPage = Math.max(rawPage, 1);
214
+ if (opts.search) baseParams.set("q", opts.search);
215
+ if (opts.default) baseParams.set("is_builtin", "true");
216
+ async function fetchPage(page2, limit) {
217
+ const params = new URLSearchParams(baseParams);
218
+ params.set("page", String(page2));
219
+ params.set("limit", String(limit));
220
+ const { data: data2 } = await client.get(
221
+ `/api/agents?${params.toString()}`
222
+ );
223
+ return data2;
224
+ }
225
+ let data;
226
+ if (opts.all) {
227
+ const first = await fetchPage(1, 500);
228
+ const totalPages2 = first.pagination?.totalPages ?? 1;
229
+ const all = [...first.agents];
230
+ for (let p = 2; p <= totalPages2; p++) {
231
+ const next = await fetchPage(p, 500);
232
+ all.push(...next.agents);
233
+ }
234
+ data = {
235
+ agents: all,
236
+ pagination: first.pagination ? { ...first.pagination, page: 1, limit: all.length } : {
237
+ total: all.length,
238
+ page: 1,
239
+ limit: all.length,
240
+ totalPages: 1
241
+ }
242
+ };
243
+ } else {
244
+ data = await fetchPage(requestedPage, apiLimit);
245
+ }
246
+ if (format === "json") {
247
+ output.log(JSON.stringify(data, null, 2));
248
+ return;
249
+ }
250
+ if (data.agents.length === 0) {
251
+ output.info("No agents found.");
252
+ return;
253
+ }
254
+ const total = data.pagination?.total ?? data.agents.length;
255
+ const shown = data.agents.length;
256
+ const page = data.pagination?.page ?? 1;
257
+ const totalPages = data.pagination?.totalPages ?? 1;
258
+ output.log(output.bold(`Agents (showing ${shown} of ${total}):`));
259
+ output.newline();
260
+ const rows = data.agents.map((a) => ({
261
+ Name: a.name,
262
+ Description: truncate2(a.description ?? "", 50),
263
+ Status: a.status,
264
+ Skills: String(a.skills?.length ?? 0),
265
+ MCP: String(a.mcp_servers?.length ?? 0)
266
+ }));
267
+ output.log(
268
+ formatOutput(rows, format, {
269
+ columns: { Description: { maxWidth: 50 } }
270
+ })
271
+ );
272
+ if (!opts.all && shown < total) {
273
+ output.newline();
274
+ output.dim(
275
+ `Page ${page}/${totalPages}. Use --page <n>, --limit <n>, --search <text>, or --all to see more.`
276
+ );
277
+ }
178
278
  }
179
- const total = data.pagination?.total ?? data.agents.length;
180
- output.log(output.bold(`Agents (${total} total):`));
181
- output.newline();
182
- const rows = data.agents.map((a) => ({
183
- Name: a.name,
184
- Description: truncate2(a.description ?? "", 50),
185
- Status: a.status,
186
- Skills: String(a.skills?.length ?? 0),
187
- MCP: String(a.mcp_servers?.length ?? 0)
188
- }));
189
- output.log(
190
- formatOutput(rows, format, {
191
- columns: { Description: { maxWidth: 50 } }
192
- })
193
- );
194
- });
279
+ );
195
280
  agent.command("info").description("Show agent details").argument("<name>", "Agent name or ID").action(async (nameOrId) => {
196
281
  requireAuth();
197
282
  const format = resolveFormat(program.opts());
@@ -286,7 +371,7 @@ function getApiBaseUrl() {
286
371
  async function loginViaBrowser(jsonMode) {
287
372
  const baseUrl = getApiBaseUrl();
288
373
  const state = randomBytes(16).toString("hex");
289
- return new Promise((resolve9, reject) => {
374
+ return new Promise((resolve10, reject) => {
290
375
  const server = createServer(async (req, res) => {
291
376
  try {
292
377
  const url = new URL(req.url ?? "/", "http://localhost");
@@ -382,7 +467,7 @@ async function loginViaBrowser(jsonMode) {
382
467
  );
383
468
  }
384
469
  }
385
- resolve9();
470
+ resolve10();
386
471
  } catch (err) {
387
472
  server.close();
388
473
  reject(
@@ -3244,6 +3329,66 @@ function printVerifyResults(results) {
3244
3329
  }
3245
3330
  }
3246
3331
 
3332
+ // src/commands/skill/index.ts
3333
+ import { existsSync as existsSync17, readdirSync as readdirSync2, readFileSync as readFileSync10 } from "fs";
3334
+ import { basename as basename2, resolve as resolve8 } from "path";
3335
+ var SKILL_DESCRIPTION = "Print agent usage skills (hand-off, agent-shelf, ...) as raw markdown.";
3336
+ var SKILL_HELP_AFTER = `
3337
+ Examples:
3338
+ $ npx rush-ai skill Print the skills index
3339
+ $ npx rush-ai skill hand-off Print the hand-off playbook
3340
+ $ npx rush-ai skill agent-shelf Print the agent-shelf playbook
3341
+ $ npx rush-ai skill --list List available skills
3342
+
3343
+ Designed to be consumed by AI agents inside IDEs (Cursor, Claude Code,
3344
+ etc). Output is raw markdown on stdout \u2014 pipe it, grep it, feed it to
3345
+ your own context.
3346
+ `;
3347
+ function resolveSkillsDir() {
3348
+ const baseDir = import.meta.dirname ?? __dirname;
3349
+ if (!baseDir) return null;
3350
+ const candidates = [
3351
+ resolve8(baseDir, "skills"),
3352
+ resolve8(baseDir, "..", "skills"),
3353
+ resolve8(baseDir, "..", "..", "skills"),
3354
+ resolve8(baseDir, "..", "..", "..", "skills"),
3355
+ resolve8(baseDir, "..", "..", "..", "..", "skills")
3356
+ ];
3357
+ for (const p of candidates) {
3358
+ if (existsSync17(p)) return p;
3359
+ }
3360
+ return null;
3361
+ }
3362
+ function listSkills(dir) {
3363
+ return readdirSync2(dir).filter((f) => f.endsWith(".md")).map((f) => basename2(f, ".md")).sort();
3364
+ }
3365
+ function registerSkillCommand(program) {
3366
+ program.command("skill [name]").description(SKILL_DESCRIPTION).addHelpText("after", SKILL_HELP_AFTER).option("--list", "List available skill names and exit").action((name, opts) => {
3367
+ const dir = resolveSkillsDir();
3368
+ if (!dir) {
3369
+ output.error(
3370
+ "Could not locate the skills/ directory. This is a packaging bug \u2014 please file an issue."
3371
+ );
3372
+ process.exit(2);
3373
+ return;
3374
+ }
3375
+ const available = listSkills(dir);
3376
+ if (opts.list) {
3377
+ for (const s of available) output.log(s);
3378
+ return;
3379
+ }
3380
+ const target = name ?? "README";
3381
+ const file = resolve8(dir, `${target}.md`);
3382
+ if (!existsSync17(file)) {
3383
+ output.error(`Unknown skill "${target}".`);
3384
+ output.dim(`Available: ${available.join(", ") || "(none)"}`);
3385
+ process.exit(1);
3386
+ return;
3387
+ }
3388
+ process.stdout.write(readFileSync10(file, "utf-8"));
3389
+ });
3390
+ }
3391
+
3247
3392
  // src/commands/task/index.ts
3248
3393
  import { createWriteStream } from "fs";
3249
3394
  import { mkdir as mkdir2 } from "fs/promises";
@@ -3285,28 +3430,28 @@ async function readStdinIfPiped() {
3285
3430
  if (process.stdin.isTTY) {
3286
3431
  return null;
3287
3432
  }
3288
- return new Promise((resolve9, reject) => {
3433
+ return new Promise((resolve10, reject) => {
3289
3434
  const chunks = [];
3290
3435
  process.stdin.on("data", (chunk) => {
3291
3436
  chunks.push(chunk);
3292
3437
  });
3293
3438
  process.stdin.on("end", () => {
3294
- resolve9(Buffer.concat(chunks).toString("utf-8").trim());
3439
+ resolve10(Buffer.concat(chunks).toString("utf-8").trim());
3295
3440
  });
3296
3441
  process.stdin.on("error", reject);
3297
3442
  });
3298
3443
  }
3299
3444
 
3300
3445
  // src/commands/task/deploy.ts
3301
- import { resolve as resolve8 } from "path";
3446
+ import { resolve as resolve9 } from "path";
3302
3447
  import chalk7 from "chalk";
3303
3448
 
3304
3449
  // src/util/git.ts
3305
3450
  import { execFileSync as execFileSync3 } from "child_process";
3306
- import { existsSync as existsSync17 } from "fs";
3451
+ import { existsSync as existsSync18 } from "fs";
3307
3452
  import { join as join8 } from "path";
3308
3453
  function isGitRepo(projectPath) {
3309
- return existsSync17(join8(projectPath, ".git"));
3454
+ return existsSync18(join8(projectPath, ".git"));
3310
3455
  }
3311
3456
  function gitInit(projectPath) {
3312
3457
  execFileSync3("git", ["init"], { cwd: projectPath, stdio: "pipe" });
@@ -3374,7 +3519,7 @@ function registerDeploySubcommand(task, program) {
3374
3519
  requireAuth();
3375
3520
  const format = resolveFormat(program.opts());
3376
3521
  const client = createClient();
3377
- const projectPath = resolve8(opts.path);
3522
+ const projectPath = resolve9(opts.path);
3378
3523
  if (format !== "json") {
3379
3524
  output.info("Checking project readiness...");
3380
3525
  }
@@ -3618,10 +3763,31 @@ function buildCategorySummary(files) {
3618
3763
  }
3619
3764
  return Object.entries(counts).map(([cat, count]) => `${count} ${cat}`).join(", ");
3620
3765
  }
3766
+ var TASK_HELP_AFTER = `
3767
+ When to use:
3768
+ Call \`task create\` whenever the next unit of work belongs on the Rush
3769
+ platform instead of your local machine \u2014 a specialist Rush agent runs
3770
+ it, and you get a task id to track / iterate on.
3771
+
3772
+ Typical flows:
3773
+ # Hand-off from an IDE conversation \u2192 Rush agent builds, you iterate
3774
+ $ rush-ai task create -a web-builder -p "<prompt synthesized from context>" --json
3775
+ $ rush-ai task status <id> --json # includes previewUrl / gitRepoUrl
3776
+ $ rush-ai task send <id> -p "<follow-up>" # same task, keep iterating
3777
+
3778
+ # Quick one-shot with the default agent (\`rush\`)
3779
+ $ rush-ai task create -a rush -p "Summarize the latest release notes"
3780
+
3781
+ For agents: run \`rush-ai skill hand-off\` for the full playbook.
3782
+ `;
3621
3783
  function registerTaskCommand(program) {
3622
- const task = program.command("task").description("Create and manage tasks");
3784
+ const task = program.command("task").description("Create and manage tasks").addHelpText("after", TASK_HELP_AFTER);
3623
3785
  registerDeploySubcommand(task, program);
3624
- task.command("create").description("Create a task asynchronously").requiredOption("-a, --agent <name>", "Agent name to execute the task").option("-p, --prompt <text>", "Task prompt").option("--skills <skills>", "Comma-separated skills to add", commaSplit).option("--mcp <servers>", "Comma-separated MCP servers to add", commaSplit).action(
3786
+ task.command("create").description("Create a task asynchronously").option(
3787
+ "-a, --agent <name>",
3788
+ "Agent name to execute the task (defaults to `rush`)",
3789
+ "rush"
3790
+ ).option("-p, --prompt <text>", "Task prompt").option("--skills <skills>", "Comma-separated skills to add", commaSplit).option("--mcp <servers>", "Comma-separated MCP servers to add", commaSplit).action(
3625
3791
  async (options) => {
3626
3792
  requireAuth();
3627
3793
  const format = resolveFormat(program.opts());
@@ -4078,6 +4244,7 @@ function registerCommands(program) {
4078
4244
  registerAuthCommand(program);
4079
4245
  registerAgentCommand(program);
4080
4246
  registerTaskCommand(program);
4247
+ registerSkillCommand(program);
4081
4248
  registerCheckCommand(program);
4082
4249
  registerMcpCommand(program);
4083
4250
  registerPluginCommand(program);
@@ -4149,17 +4316,29 @@ async function checkForUpdate(currentVersion) {
4149
4316
  // src/index.ts
4150
4317
  var BANNER = `
4151
4318
  ${chalk8.bold.cyan("Rush CLI")} ${chalk8.dim(`v${VERSION}`)}
4152
- ${chalk8.dim("The command-line interface for the Rush AI platform")}
4319
+ ${chalk8.dim("Call Rush agents from your terminal \u2014 relay the context, not the prompt.")}
4320
+
4321
+ ${chalk8.dim("Use cases:")}
4322
+ ${chalk8.dim("\xB7")} From Cursor / Claude Code, hand off a task to a Rush agent without restating context.
4323
+ ${chalk8.dim("\xB7")} Compose workflows where a Rush specialist agent runs as your sub-agent.
4324
+
4325
+ ${chalk8.dim("For agents:")} ${chalk8.cyan("rush-ai skill")} prints ready-to-consume usage playbooks.
4153
4326
  `;
4154
4327
  function showWelcomeGuide() {
4155
4328
  const lines = [
4156
4329
  "",
4157
4330
  ` ${chalk8.bold.cyan("Rush CLI")} ${chalk8.dim(`v${VERSION}`)}`,
4331
+ ` ${chalk8.dim("Call Rush agents from your terminal \u2014 relay the context, not the prompt.")}`,
4158
4332
  "",
4159
4333
  chalk8.dim(" Quick start:"),
4160
- ` ${chalk8.cyan("rush-ai auth login")} Log in to Rush platform`,
4161
- ` ${chalk8.cyan("rush-ai agent list")} Browse available agents`,
4162
- ` ${chalk8.cyan("rush-ai task create -a <agent>")} Create a task`,
4334
+ ` ${chalk8.cyan("rush-ai auth login")} Log in to the Rush platform`,
4335
+ ` ${chalk8.cyan("rush-ai agent list")} Browse available agents`,
4336
+ ` ${chalk8.cyan("rush-ai task create -a web-builder")} Hand a task to a Rush agent`,
4337
+ "",
4338
+ chalk8.dim(" For AI agents:"),
4339
+ ` ${chalk8.cyan("rush-ai skill")} Print usage playbooks (markdown)`,
4340
+ ` ${chalk8.cyan("rush-ai skill hand-off")} IDE \u2192 Rush task hand-off`,
4341
+ ` ${chalk8.cyan("rush-ai skill agent-shelf")} Calling a Rush agent as a sub-agent`,
4163
4342
  ""
4164
4343
  ];
4165
4344
  try {