rush-ai 0.14.1 → 0.16.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/dist/index.js CHANGED
@@ -214,7 +214,7 @@ Built-in default agents (see --default):
214
214
  web-builder site / landing-page builder (returns previewUrl + gitRepoUrl)
215
215
  skill-publisher publishes reusable skills back onto the platform
216
216
 
217
- For agents: run \`rush-ai skill agent-shelf\` for the full playbook.
217
+ For agents: run \`rush-ai playbook agent-shelf\` for the full playbook.
218
218
  `;
219
219
  function registerAgentCommand(program) {
220
220
  const agent = program.command("agent").description("Manage and inspect agents").addHelpText("after", AGENT_HELP_AFTER);
@@ -327,10 +327,9 @@ function registerAgentCommand(program) {
327
327
  } catch {
328
328
  }
329
329
  const candidates = parsed?.candidates;
330
- const isValid = Array.isArray(candidates) && candidates.length > 0 && candidates.every(
330
+ if (Array.isArray(candidates) && candidates.length > 0 && candidates.every(
331
331
  (c) => typeof c.id === "string" && typeof c.name === "string"
332
- );
333
- if (isValid) {
332
+ )) {
334
333
  if (format === "json") {
335
334
  output.log(JSON.stringify(parsed, null, 2));
336
335
  process.exitCode = 1;
@@ -1410,13 +1409,15 @@ ${MARKER_BEGIN}
1410
1409
  _rush_cli_completion() {
1411
1410
  local cur="\${COMP_WORDS[COMP_CWORD]}"
1412
1411
  local prev="\${COMP_WORDS[COMP_CWORD-1]}"
1413
- local cmds="auth agent task mcp plugin completion config doctor"
1412
+ local cmds="auth agent task skill playbook mcp plugin completion config doctor"
1414
1413
 
1415
1414
  case "$prev" in
1416
1415
  rush-ai) COMPREPLY=( $(compgen -W "$cmds" -- "$cur") ) ;;
1417
1416
  auth) COMPREPLY=( $(compgen -W "login logout status" -- "$cur") ) ;;
1418
1417
  agent) COMPREPLY=( $(compgen -W "list info" -- "$cur") ) ;;
1419
1418
  task) COMPREPLY=( $(compgen -W "run create status result list watch cancel" -- "$cur") ) ;;
1419
+ skill) COMPREPLY=( $(compgen -W "init find install list info update outdated uninstall publish group doctor" -- "$cur") ) ;;
1420
+ playbook) COMPREPLY=( $(compgen -W "hand-off agent-shelf" -- "$cur") ) ;;
1420
1421
  plugin) COMPREPLY=( $(compgen -W "install list status update uninstall" -- "$cur") ) ;;
1421
1422
  completion) COMPREPLY=( $(compgen -W "install bash zsh fish" -- "$cur") ) ;;
1422
1423
  config) COMPREPLY=( $(compgen -W "show set use create delete list" -- "$cur") ) ;;
@@ -1432,6 +1433,8 @@ _rush_cli_completion() {
1432
1433
  'auth:Authentication commands'
1433
1434
  'agent:Agent management'
1434
1435
  'task:Task operations'
1436
+ 'skill:Skill package management'
1437
+ 'playbook:Agent usage playbooks'
1435
1438
  'mcp:MCP server'
1436
1439
  'plugin:Plugin management'
1437
1440
  'completion:Shell completion'
@@ -1447,6 +1450,8 @@ ${MARKER_BEGIN}
1447
1450
  complete -c rush-ai -n '__fish_use_subcommand' -a auth -d 'Authentication'
1448
1451
  complete -c rush-ai -n '__fish_use_subcommand' -a agent -d 'Agent management'
1449
1452
  complete -c rush-ai -n '__fish_use_subcommand' -a task -d 'Task operations'
1453
+ complete -c rush-ai -n '__fish_use_subcommand' -a skill -d 'Skill package management'
1454
+ complete -c rush-ai -n '__fish_use_subcommand' -a playbook -d 'Agent usage playbooks'
1450
1455
  complete -c rush-ai -n '__fish_use_subcommand' -a mcp -d 'MCP server'
1451
1456
  complete -c rush-ai -n '__fish_use_subcommand' -a plugin -d 'Plugin management'
1452
1457
  complete -c rush-ai -n '__fish_use_subcommand' -a completion -d 'Shell completion'
@@ -1455,6 +1460,8 @@ complete -c rush-ai -n '__fish_use_subcommand' -a doctor -d 'System diagnostics'
1455
1460
  complete -c rush-ai -n '__fish_seen_subcommand_from auth' -a 'login logout status'
1456
1461
  complete -c rush-ai -n '__fish_seen_subcommand_from agent' -a 'list info'
1457
1462
  complete -c rush-ai -n '__fish_seen_subcommand_from task' -a 'run create status result list watch cancel'
1463
+ complete -c rush-ai -n '__fish_seen_subcommand_from skill' -a 'init find install list info update outdated uninstall publish group doctor'
1464
+ complete -c rush-ai -n '__fish_seen_subcommand_from playbook' -a 'hand-off agent-shelf'
1458
1465
  complete -c rush-ai -n '__fish_seen_subcommand_from plugin' -a 'install list status update uninstall'
1459
1466
  complete -c rush-ai -n '__fish_seen_subcommand_from completion' -a 'install bash zsh fish'
1460
1467
  complete -c rush-ai -n '__fish_seen_subcommand_from config' -a 'show set use create delete list'
@@ -1507,7 +1514,8 @@ function registerCompletionCommand(program) {
1507
1514
  if (shell === "fish") {
1508
1515
  try {
1509
1516
  await mkdir(dirname(rcFile), { recursive: true });
1510
- await writeFile(rcFile, script + "\n");
1517
+ await writeFile(rcFile, `${script}
1518
+ `);
1511
1519
  output.success(`Fish completion installed to ${rcFile}`);
1512
1520
  } catch {
1513
1521
  output.error(`Failed to write to ${rcFile}`);
@@ -1527,7 +1535,9 @@ function registerCompletionCommand(program) {
1527
1535
  } catch {
1528
1536
  }
1529
1537
  try {
1530
- await appendFile(rcFile, "\n" + script + "\n");
1538
+ await appendFile(rcFile, `
1539
+ ${script}
1540
+ `);
1531
1541
  output.success(`Completion installed to ${rcFile}`);
1532
1542
  output.dim(
1533
1543
  ` Run \`source ${rcFile}\` or restart your shell to activate.`
@@ -4290,8 +4300,80 @@ function registerMcpCommand(program) {
4290
4300
  });
4291
4301
  }
4292
4302
 
4303
+ // src/commands/playbook/index.ts
4304
+ import { existsSync as existsSync11, readdirSync, readFileSync as readFileSync7 } from "fs";
4305
+ import { basename as basename2, resolve as resolve9 } from "path";
4306
+ var PLAYBOOK_DESCRIPTION = "Print agent usage playbooks (hand-off, agent-shelf, ...) as raw markdown.";
4307
+ var PLAYBOOK_HELP_AFTER = `
4308
+ Examples:
4309
+ $ npx rush-ai playbook Print the playbook index
4310
+ $ npx rush-ai playbook hand-off Print the hand-off playbook
4311
+ $ npx rush-ai playbook agent-shelf Print the agent-shelf playbook
4312
+ $ npx rush-ai playbook --list List available playbooks
4313
+
4314
+ Designed to be consumed by AI agents inside IDEs (Cursor, Claude Code,
4315
+ Codex, etc). Output is raw markdown on stdout \u2014 pipe it, grep it, feed it
4316
+ to your own context.
4317
+ `;
4318
+ function resolvePlaybooksDir() {
4319
+ const baseDir = import.meta.dirname ?? __dirname;
4320
+ if (!baseDir) return null;
4321
+ const candidates = [
4322
+ resolve9(baseDir, "skills"),
4323
+ resolve9(baseDir, "..", "skills"),
4324
+ resolve9(baseDir, "..", "..", "skills"),
4325
+ resolve9(baseDir, "..", "..", "..", "skills"),
4326
+ resolve9(baseDir, "..", "..", "..", "..", "skills")
4327
+ ];
4328
+ for (const p of candidates) {
4329
+ if (existsSync11(resolve9(p, ".rush-playbooks"))) return p;
4330
+ }
4331
+ return null;
4332
+ }
4333
+ function listPlaybooks(dir) {
4334
+ return readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => basename2(f, ".md")).sort();
4335
+ }
4336
+ function getAvailablePlaybooks() {
4337
+ const dir = resolvePlaybooksDir();
4338
+ return dir ? listPlaybooks(dir) : [];
4339
+ }
4340
+ function isPlaybookName(name) {
4341
+ if (!name) return false;
4342
+ return getAvailablePlaybooks().includes(name);
4343
+ }
4344
+ function printPlaybook(name, opts = {}) {
4345
+ const dir = resolvePlaybooksDir();
4346
+ if (!dir) {
4347
+ output.error(
4348
+ "Could not locate the skills/ directory. This is a packaging bug \u2014 please file an issue."
4349
+ );
4350
+ process.exit(2);
4351
+ return;
4352
+ }
4353
+ const available = listPlaybooks(dir);
4354
+ if (opts.list) {
4355
+ for (const s of available) output.log(s);
4356
+ return;
4357
+ }
4358
+ const target = name ?? "README";
4359
+ const file2 = resolve9(dir, `${target}.md`);
4360
+ if (!existsSync11(file2)) {
4361
+ output.error(`Unknown playbook "${target}".`);
4362
+ output.dim(`Available: ${available.join(", ") || "(none)"}`);
4363
+ process.exit(1);
4364
+ return;
4365
+ }
4366
+ process.stdout.write(readFileSync7(file2, "utf-8"));
4367
+ }
4368
+ function registerPlaybookCommand(program) {
4369
+ program.command("playbook [name]").description(PLAYBOOK_DESCRIPTION).addHelpText("after", PLAYBOOK_HELP_AFTER).option("--list", "List available playbook names and exit").action((name, opts) => {
4370
+ printPlaybook(name, opts);
4371
+ });
4372
+ }
4373
+
4293
4374
  // src/commands/plugin/install.ts
4294
- import { resolve as resolve20 } from "path";
4375
+ import { rm as rm12 } from "fs/promises";
4376
+ import { resolve as resolve21 } from "path";
4295
4377
 
4296
4378
  // src/installers/claude-code/installer.ts
4297
4379
  import { randomUUID as randomUUID4 } from "crypto";
@@ -4309,7 +4391,7 @@ import {
4309
4391
  unlink
4310
4392
  } from "fs/promises";
4311
4393
  import { homedir as homedir5 } from "os";
4312
- import { dirname as dirname4, join as join9, resolve as resolve10 } from "path";
4394
+ import { dirname as dirname4, join as join9, resolve as resolve11 } from "path";
4313
4395
 
4314
4396
  // src/installers/claude-code/mcp.ts
4315
4397
  import { execFileSync as execFileSync5 } from "child_process";
@@ -4361,7 +4443,7 @@ function defaultRushBinaryResolver() {
4361
4443
  }
4362
4444
 
4363
4445
  // src/installers/claude-code/paths.ts
4364
- import { resolve as resolve9 } from "path";
4446
+ import { resolve as resolve10 } from "path";
4365
4447
  var CLAUDE_DIR = ".claude";
4366
4448
  var PLUGINS_SUBDIR = "plugins";
4367
4449
  var CACHE_SUBDIR = "cache";
@@ -4379,27 +4461,27 @@ var ClaudeCodePaths = class {
4379
4461
  }
4380
4462
  /** `<home>/.claude/` */
4381
4463
  get claudeDir() {
4382
- return resolve9(this.home, CLAUDE_DIR);
4464
+ return resolve10(this.home, CLAUDE_DIR);
4383
4465
  }
4384
4466
  /** `<home>/.claude/settings.json` */
4385
4467
  get settingsJson() {
4386
- return resolve9(this.claudeDir, "settings.json");
4468
+ return resolve10(this.claudeDir, "settings.json");
4387
4469
  }
4388
4470
  /** `<home>/.claude/plugins/` */
4389
4471
  get pluginsDir() {
4390
- return resolve9(this.claudeDir, PLUGINS_SUBDIR);
4472
+ return resolve10(this.claudeDir, PLUGINS_SUBDIR);
4391
4473
  }
4392
4474
  /** `<home>/.claude/plugins/known_marketplaces.json` */
4393
4475
  get knownMarketplacesJson() {
4394
- return resolve9(this.pluginsDir, "known_marketplaces.json");
4476
+ return resolve10(this.pluginsDir, "known_marketplaces.json");
4395
4477
  }
4396
4478
  /** `<home>/.claude/plugins/installed_plugins.json` */
4397
4479
  get installedPluginsJson() {
4398
- return resolve9(this.pluginsDir, "installed_plugins.json");
4480
+ return resolve10(this.pluginsDir, "installed_plugins.json");
4399
4481
  }
4400
4482
  /** `<home>/.claude/plugins/cache/` */
4401
4483
  get cacheDir() {
4402
- return resolve9(this.pluginsDir, CACHE_SUBDIR);
4484
+ return resolve10(this.pluginsDir, CACHE_SUBDIR);
4403
4485
  }
4404
4486
  /**
4405
4487
  * `<home>/.claude/plugins/marketplaces/`。
@@ -4409,34 +4491,34 @@ var ClaudeCodePaths = class {
4409
4491
  * Claude Code 会识别不到 plugin 条目(regression fix,bug #4)。
4410
4492
  */
4411
4493
  get marketplacesRootDir() {
4412
- return resolve9(this.pluginsDir, "marketplaces");
4494
+ return resolve10(this.pluginsDir, "marketplaces");
4413
4495
  }
4414
4496
  /** `<home>/.claude/plugins/marketplaces/<mkt>/` */
4415
4497
  marketplaceInstallDir(marketplace) {
4416
- return resolve9(this.marketplacesRootDir, marketplace);
4498
+ return resolve10(this.marketplacesRootDir, marketplace);
4417
4499
  }
4418
4500
  /** `<home>/.claude/plugins/cache/<mkt>/` */
4419
4501
  marketplaceCacheDir(marketplace) {
4420
- return resolve9(this.cacheDir, marketplace);
4502
+ return resolve10(this.cacheDir, marketplace);
4421
4503
  }
4422
4504
  /** `<home>/.claude/plugins/cache/<mkt>/<plugin>/` */
4423
4505
  pluginCacheDir(ref) {
4424
- return resolve9(this.marketplaceCacheDir(ref.marketplace), ref.name);
4506
+ return resolve10(this.marketplaceCacheDir(ref.marketplace), ref.name);
4425
4507
  }
4426
4508
  /** `<home>/.claude/plugins/cache/<mkt>/<plugin>/<version>/` */
4427
4509
  pluginVersionDir(ref, version) {
4428
- return resolve9(this.pluginCacheDir(ref), version);
4510
+ return resolve10(this.pluginCacheDir(ref), version);
4429
4511
  }
4430
4512
  /** `<home>/.claude/plugins/cache/<mkt>/<plugin>/<version>/.claude-plugin/plugin.json` */
4431
4513
  pluginManifestPath(ref, version) {
4432
- return resolve9(
4514
+ return resolve10(
4433
4515
  this.pluginVersionDir(ref, version),
4434
4516
  PLUGIN_MANIFEST_RELATIVE
4435
4517
  );
4436
4518
  }
4437
4519
  /** `<home>/.claude/plugins/cache/<mkt>/<plugin>/<version>/<capability>/` */
4438
4520
  capabilityDir(ref, version, capability) {
4439
- return resolve9(this.pluginVersionDir(ref, version), capability);
4521
+ return resolve10(this.pluginVersionDir(ref, version), capability);
4440
4522
  }
4441
4523
  };
4442
4524
  function pluginKey(ref) {
@@ -4518,7 +4600,7 @@ var ClaudeCodeInstaller = class {
4518
4600
  const { ref, version } = plugin;
4519
4601
  const pluginVersionDir = this.paths.pluginVersionDir(ref, version);
4520
4602
  const key = pluginKey(ref);
4521
- if (!opts.force) {
4603
+ if (!opts.force && version !== "unknown") {
4522
4604
  let already = false;
4523
4605
  try {
4524
4606
  already = await this.isAlreadyInstalledAtVersion(ref, version);
@@ -4553,7 +4635,7 @@ var ClaudeCodeInstaller = class {
4553
4635
  await mkdir5(pluginVersionDir, { recursive: true });
4554
4636
  writtenFiles.push(pluginVersionDir);
4555
4637
  for (const cap of CAPABILITY_DIRS) {
4556
- const srcDir = resolve10(plugin.sourceDir, cap);
4638
+ const srcDir = resolve11(plugin.sourceDir, cap);
4557
4639
  if (!await dirExists(srcDir)) continue;
4558
4640
  const dstDir = this.paths.capabilityDir(ref, version, cap);
4559
4641
  await mkdir5(dstDir, { recursive: true });
@@ -4767,8 +4849,8 @@ var ClaudeCodeInstaller = class {
4767
4849
  const kind = await inodeKind(mktDir);
4768
4850
  if (kind === "symlink") {
4769
4851
  const target = await readlink(mktDir).catch(() => null);
4770
- const resolvedTarget = target !== null ? resolve10(dirname4(mktDir), target) : null;
4771
- if (resolvedTarget && resolvedTarget === resolve10(src.rootDir)) {
4852
+ const resolvedTarget = target !== null ? resolve11(dirname4(mktDir), target) : null;
4853
+ if (resolvedTarget && resolvedTarget === resolve11(src.rootDir)) {
4772
4854
  return { written: false, path: mktDir };
4773
4855
  }
4774
4856
  throw new Error(
@@ -4837,7 +4919,7 @@ var ClaudeCodeInstaller = class {
4837
4919
  const pluginVersionDir = this.paths.pluginVersionDir(ref, version);
4838
4920
  const dryFiles = [pluginVersionDir];
4839
4921
  for (const cap of CAPABILITY_DIRS) {
4840
- const srcDir = resolve10(plugin.sourceDir, cap);
4922
+ const srcDir = resolve11(plugin.sourceDir, cap);
4841
4923
  if (!await dirExists(srcDir)) continue;
4842
4924
  const dstDir = this.paths.capabilityDir(ref, version, cap);
4843
4925
  dryFiles.push(dstDir);
@@ -5470,11 +5552,11 @@ import {
5470
5552
  } from "fs/promises";
5471
5553
  import { homedir as homedir6 } from "os";
5472
5554
  import {
5473
- basename as basename2,
5555
+ basename as basename3,
5474
5556
  dirname as dirname6,
5475
5557
  extname,
5476
5558
  relative as pathRelative,
5477
- resolve as resolve11
5559
+ resolve as resolve12
5478
5560
  } from "path";
5479
5561
 
5480
5562
  // src/installers/codex/mcp.ts
@@ -5837,7 +5919,7 @@ var CodexInstaller = class {
5837
5919
  };
5838
5920
  }
5839
5921
  const ref = plugin.ref;
5840
- if (!opts.force && await this.isInstalled(ref)) {
5922
+ if (!opts.force && plugin.version !== "unknown" && await this.isInstalled(ref)) {
5841
5923
  const existingVersion = await detectInstalledVersion(this.home, ref);
5842
5924
  if (existingVersion?.version === plugin.version) {
5843
5925
  return {
@@ -6115,10 +6197,10 @@ var CodexInstaller = class {
6115
6197
  const srcMcp = plugin.manifest.mcpServers;
6116
6198
  if (srcMcp !== void 0 && srcMcp !== null) {
6117
6199
  if (typeof srcMcp === "string") {
6118
- const destPath = resolve11(versionDir, srcMcp);
6200
+ const destPath = resolve12(versionDir, srcMcp);
6119
6201
  files.push(destPath);
6120
- const srcPath = resolve11(plugin.sourceDir, srcMcp);
6121
- const rel = pathRelative(resolve11(plugin.sourceDir), srcPath);
6202
+ const srcPath = resolve12(plugin.sourceDir, srcMcp);
6203
+ const rel = pathRelative(resolve12(plugin.sourceDir), srcPath);
6122
6204
  const traversal = rel === ".." || rel.startsWith("..") || rel.startsWith("/");
6123
6205
  const exists = await pathExists5(srcPath);
6124
6206
  if (traversal || !exists) {
@@ -6263,13 +6345,13 @@ function assertSafePathComponents(ref, version) {
6263
6345
  }
6264
6346
  async function copyAuthorProvidedMcp(sourceDir, versionDir, relativeRef) {
6265
6347
  if (typeof relativeRef !== "string" || relativeRef.length === 0) return null;
6266
- const srcPath = resolve11(sourceDir, relativeRef);
6267
- const rel = pathRelative(resolve11(sourceDir), srcPath);
6348
+ const srcPath = resolve12(sourceDir, relativeRef);
6349
+ const rel = pathRelative(resolve12(sourceDir), srcPath);
6268
6350
  if (rel === ".." || rel.startsWith("..") || rel.startsWith("/")) {
6269
6351
  return null;
6270
6352
  }
6271
6353
  if (!await pathExists5(srcPath)) return null;
6272
- const destPath = resolve11(versionDir, relativeRef);
6354
+ const destPath = resolve12(versionDir, relativeRef);
6273
6355
  await mkdir7(dirname6(destPath), { recursive: true });
6274
6356
  const raw = await readFile7(srcPath, "utf8");
6275
6357
  await writeFileAtomic(destPath, raw);
@@ -6286,13 +6368,13 @@ async function copyAuthorProvidedMcp(sourceDir, versionDir, relativeRef) {
6286
6368
  }
6287
6369
  async function writeCodexMarketplaceMirror(input) {
6288
6370
  const pluginParent = dirname6(input.pluginDir);
6289
- const pluginBase = basename2(input.pluginDir);
6371
+ const pluginBase = basename3(input.pluginDir);
6290
6372
  await mkdir7(pluginParent, { recursive: true });
6291
6373
  const tmpPluginDir = await mkdtemp(
6292
- resolve11(pluginParent, `.${pluginBase}.tmp-`)
6374
+ resolve12(pluginParent, `.${pluginBase}.tmp-`)
6293
6375
  );
6294
6376
  const backupPluginDir = await mkdtemp(
6295
- resolve11(pluginParent, `.${pluginBase}.bak-`)
6377
+ resolve12(pluginParent, `.${pluginBase}.bak-`)
6296
6378
  );
6297
6379
  let hadExistingPluginDir = false;
6298
6380
  let swappedPluginDir = false;
@@ -6325,7 +6407,7 @@ async function writeCodexMarketplaceMirror(input) {
6325
6407
  try {
6326
6408
  if (await pathExists5(input.pluginDir)) {
6327
6409
  displacedPluginDir = await mkdtemp(
6328
- resolve11(pluginParent, `.${pluginBase}.failed-`)
6410
+ resolve12(pluginParent, `.${pluginBase}.failed-`)
6329
6411
  );
6330
6412
  await rm7(displacedPluginDir, { recursive: true, force: true });
6331
6413
  await rename6(input.pluginDir, displacedPluginDir);
@@ -6412,7 +6494,7 @@ async function planCodexSkills(plugin) {
6412
6494
  const usedNames = /* @__PURE__ */ new Set();
6413
6495
  const coveredAliases = /* @__PURE__ */ new Set();
6414
6496
  const commandFiles = await listMarkdownFiles(
6415
- resolve11(plugin.sourceDir, "commands"),
6497
+ resolve12(plugin.sourceDir, "commands"),
6416
6498
  {
6417
6499
  recursive: false
6418
6500
  }
@@ -6422,19 +6504,19 @@ async function planCodexSkills(plugin) {
6422
6504
  commandFiles.map((file2) => markdownSkillAlias(plugin.manifest.name, file2))
6423
6505
  )
6424
6506
  );
6425
- const nativeSkillsDir = resolve11(plugin.sourceDir, "skills");
6507
+ const nativeSkillsDir = resolve12(plugin.sourceDir, "skills");
6426
6508
  if (await hasCodexSkillDir(nativeSkillsDir)) {
6427
6509
  for (const name of await listCodexSkillNames(nativeSkillsDir)) {
6428
6510
  usedNames.add(name);
6429
6511
  coveredAliases.add(skillAlias(plugin.manifest.name, name));
6430
6512
  sources.push({
6431
6513
  kind: "copy-skill",
6432
- sourceDir: resolve11(nativeSkillsDir, name),
6514
+ sourceDir: resolve12(nativeSkillsDir, name),
6433
6515
  skillName: name
6434
6516
  });
6435
6517
  }
6436
6518
  } else {
6437
- const dotSkillsDir = resolve11(plugin.sourceDir, ".skills");
6519
+ const dotSkillsDir = resolve12(plugin.sourceDir, ".skills");
6438
6520
  const claudeSkills = await listMarkdownFiles(dotSkillsDir);
6439
6521
  const claudeAliases = /* @__PURE__ */ new Map();
6440
6522
  for (const file2 of claudeSkills) {
@@ -6473,7 +6555,7 @@ async function planCodexSkills(plugin) {
6473
6555
  async function materializeCodexSkills(plan, dstSkills) {
6474
6556
  for (const source of plan.sources) {
6475
6557
  if (source.kind === "copy-skill") {
6476
- await cp(source.sourceDir, resolve11(dstSkills, source.skillName), {
6558
+ await cp(source.sourceDir, resolve12(dstSkills, source.skillName), {
6477
6559
  recursive: true,
6478
6560
  dereference: false,
6479
6561
  preserveTimestamps: true,
@@ -6484,7 +6566,7 @@ async function materializeCodexSkills(plan, dstSkills) {
6484
6566
  const raw = await readFile7(source.sourceFile, "utf8");
6485
6567
  const normalized = normalizeSkillMarkdown(raw, source.skillName);
6486
6568
  await writeFileAtomic(
6487
- resolve11(dstSkills, source.skillName, "SKILL.md"),
6569
+ resolve12(dstSkills, source.skillName, "SKILL.md"),
6488
6570
  normalized
6489
6571
  );
6490
6572
  }
@@ -6570,7 +6652,7 @@ async function listCodexSkillNames(skillsDir) {
6570
6652
  const names = [];
6571
6653
  for (const entry of entries) {
6572
6654
  if (!entry.isDirectory()) continue;
6573
- if (await pathExists5(resolve11(skillsDir, entry.name, "SKILL.md"))) {
6655
+ if (await pathExists5(resolve12(skillsDir, entry.name, "SKILL.md"))) {
6574
6656
  names.push(entry.name);
6575
6657
  }
6576
6658
  }
@@ -6588,7 +6670,7 @@ async function listMarkdownFiles(dir, opts = {}) {
6588
6670
  return;
6589
6671
  }
6590
6672
  for (const entry of entries) {
6591
- const abs = resolve11(current, entry.name);
6673
+ const abs = resolve12(current, entry.name);
6592
6674
  if (entry.isDirectory()) {
6593
6675
  if (recursive) await walk2(abs);
6594
6676
  continue;
@@ -6616,7 +6698,7 @@ async function markdownSkillAlias(pluginName, filePath) {
6616
6698
  const triggerAlias = extractTriggerAlias(await readFile7(filePath, "utf8"));
6617
6699
  return skillAlias(
6618
6700
  pluginName,
6619
- triggerAlias ?? basename2(filePath, extname(filePath))
6701
+ triggerAlias ?? basename3(filePath, extname(filePath))
6620
6702
  );
6621
6703
  }
6622
6704
  function markdownPathAlias(pluginName, baseDir, filePath) {
@@ -6647,8 +6729,8 @@ async function writeFileAtomic(filePath, content) {
6647
6729
  const tmp = `${filePath}.${Math.random().toString(36).slice(2)}.tmp`;
6648
6730
  try {
6649
6731
  await writeFile6(tmp, content, { encoding: "utf8", flag: "w" });
6650
- const { rename: rename9 } = await import("fs/promises");
6651
- await rename9(tmp, filePath);
6732
+ const { rename: rename10 } = await import("fs/promises");
6733
+ await rename10(tmp, filePath);
6652
6734
  } catch (err) {
6653
6735
  await rm7(tmp, { force: true }).catch(() => {
6654
6736
  });
@@ -6680,7 +6762,7 @@ function parsePluginKey2(key) {
6680
6762
  return { name: key.slice(0, at), marketplace: key.slice(at + 1) };
6681
6763
  }
6682
6764
  async function detectInstalledVersion(home, ref) {
6683
- const baseDir = resolve11(
6765
+ const baseDir = resolve12(
6684
6766
  codexHomeDir(home),
6685
6767
  "plugins",
6686
6768
  "cache",
@@ -6696,8 +6778,8 @@ async function detectInstalledVersion(home, ref) {
6696
6778
  }
6697
6779
  let best = null;
6698
6780
  for (const entry of entries) {
6699
- const versionDir = resolve11(baseDir, entry);
6700
- const manifestPath = resolve11(versionDir, ".codex-plugin", "plugin.json");
6781
+ const versionDir = resolve12(baseDir, entry);
6782
+ const manifestPath = resolve12(versionDir, ".codex-plugin", "plugin.json");
6701
6783
  if (!await pathExists5(manifestPath)) continue;
6702
6784
  try {
6703
6785
  const raw = await readFile7(manifestPath, "utf8");
@@ -6738,7 +6820,7 @@ import {
6738
6820
  writeFile as writeFile8
6739
6821
  } from "fs/promises";
6740
6822
  import { homedir as homedir7 } from "os";
6741
- import { dirname as dirname8, relative, resolve as resolve14 } from "path";
6823
+ import { dirname as dirname8, relative, resolve as resolve15 } from "path";
6742
6824
 
6743
6825
  // src/installers/cursor/marker.ts
6744
6826
  var RUSH_AI_MARKER = "<!-- rush-ai:auto-generated -->";
@@ -6857,25 +6939,25 @@ async function pathExists6(p) {
6857
6939
  }
6858
6940
 
6859
6941
  // src/installers/cursor/paths.ts
6860
- import { resolve as resolve12 } from "path";
6942
+ import { resolve as resolve13 } from "path";
6861
6943
  var CURSOR_RELATIVE_DIR = ".cursor";
6862
6944
  function cursorDir(home) {
6863
- return resolve12(home, CURSOR_RELATIVE_DIR);
6945
+ return resolve13(home, CURSOR_RELATIVE_DIR);
6864
6946
  }
6865
6947
  function cursorMcpJsonPath(home) {
6866
- return resolve12(cursorDir(home), "mcp.json");
6948
+ return resolve13(cursorDir(home), "mcp.json");
6867
6949
  }
6868
6950
  function cursorRulesDir(home) {
6869
- return resolve12(cursorDir(home), "rules");
6951
+ return resolve13(cursorDir(home), "rules");
6870
6952
  }
6871
6953
  function cursorRuleMdcPath(home, ruleName) {
6872
- return resolve12(cursorRulesDir(home), `${ruleName}.mdc`);
6954
+ return resolve13(cursorRulesDir(home), `${ruleName}.mdc`);
6873
6955
  }
6874
6956
  function cursorSkillsDir(home) {
6875
- return resolve12(cursorDir(home), "skills");
6957
+ return resolve13(cursorDir(home), "skills");
6876
6958
  }
6877
6959
  function cursorSkillDir(home, skillName) {
6878
- return resolve12(cursorSkillsDir(home), skillName);
6960
+ return resolve13(cursorSkillsDir(home), skillName);
6879
6961
  }
6880
6962
  function bridgeSkillFileReference(skillName) {
6881
6963
  return `.cursor/skills/${skillName}/SKILL.md`;
@@ -6986,9 +7068,9 @@ function quoteIfNeeded(value) {
6986
7068
 
6987
7069
  // src/installers/cursor/skills.ts
6988
7070
  import { readFile as readFile9 } from "fs/promises";
6989
- import { resolve as resolve13 } from "path";
7071
+ import { resolve as resolve14 } from "path";
6990
7072
  async function readSkillDescription(skillSourceDir) {
6991
- const skillMdPath = resolve13(skillSourceDir, "SKILL.md");
7073
+ const skillMdPath = resolve14(skillSourceDir, "SKILL.md");
6992
7074
  let raw;
6993
7075
  try {
6994
7076
  raw = await readFile9(skillMdPath, "utf8");
@@ -7141,7 +7223,7 @@ var CursorInstaller = class {
7141
7223
  const { included, skipped } = partitionCapabilities(plugin.capabilities);
7142
7224
  const force = opts.force === true;
7143
7225
  const dryRun = opts.dryRun === true;
7144
- if (!force && !dryRun) {
7226
+ if (!force && !dryRun && plugin.version !== "unknown") {
7145
7227
  const store = await RushRegistryStore.load({ home: this.home });
7146
7228
  const existing = store.get(plugin.ref)?.targets?.cursor;
7147
7229
  if (existing && existing.version === plugin.version) {
@@ -7351,10 +7433,10 @@ var CursorInstaller = class {
7351
7433
  const prevEntry = registryStore.get(plugin.ref);
7352
7434
  const prevFiles = new Set(prevEntry?.targets?.cursor?.files ?? []);
7353
7435
  if (plugin.capabilities.includes("skills")) {
7354
- const skillsSourceDir = resolve14(plugin.sourceDir, "skills");
7436
+ const skillsSourceDir = resolve15(plugin.sourceDir, "skills");
7355
7437
  const skillDirs = await listSkillDirs(skillsSourceDir);
7356
7438
  for (const skillName of skillDirs) {
7357
- const srcDir = resolve14(skillsSourceDir, skillName);
7439
+ const srcDir = resolve15(skillsSourceDir, skillName);
7358
7440
  const destDir = cursorSkillDir(this.home, skillName);
7359
7441
  const bridgeRulePath = cursorRuleMdcPath(this.home, skillName);
7360
7442
  if (await dirExists2(destDir)) {
@@ -7386,10 +7468,10 @@ var CursorInstaller = class {
7386
7468
  }
7387
7469
  }
7388
7470
  if (plugin.capabilities.includes("rules")) {
7389
- const rulesSourceDir = resolve14(plugin.sourceDir, "rules");
7471
+ const rulesSourceDir = resolve15(plugin.sourceDir, "rules");
7390
7472
  const rulesFiles = await listRuleMdFiles(rulesSourceDir);
7391
7473
  for (const ruleFile of rulesFiles) {
7392
- const srcPath = resolve14(rulesSourceDir, ruleFile);
7474
+ const srcPath = resolve15(rulesSourceDir, ruleFile);
7393
7475
  const ruleName = ruleFile.replace(/\.md$/i, "");
7394
7476
  const destPath = cursorRuleMdcPath(this.home, ruleName);
7395
7477
  await assertMdcWritable(destPath, args.force);
@@ -7591,8 +7673,8 @@ async function fileExists(p) {
7591
7673
  }
7592
7674
  }
7593
7675
  async function safeRename(from, to) {
7594
- const { rename: rename9 } = await import("fs/promises");
7595
- await rename9(from, to);
7676
+ const { rename: rename10 } = await import("fs/promises");
7677
+ await rename10(from, to);
7596
7678
  }
7597
7679
  async function classifyArtifactPath(p) {
7598
7680
  let s;
@@ -7612,12 +7694,12 @@ async function classifyArtifactPath(p) {
7612
7694
  // src/migration/cleanup.ts
7613
7695
  import { lstat as lstat3, rm as rm10, unlink as unlink2 } from "fs/promises";
7614
7696
  import { homedir as homedir9 } from "os";
7615
- import { resolve as resolve16 } from "path";
7697
+ import { resolve as resolve17 } from "path";
7616
7698
 
7617
7699
  // src/migration/detect.ts
7618
7700
  import { access as access9, lstat as lstat2, readFile as readFile11, readlink as readlink2, stat as stat8 } from "fs/promises";
7619
7701
  import { homedir as homedir8 } from "os";
7620
- import { isAbsolute as isAbsolute4, resolve as resolve15 } from "path";
7702
+ import { isAbsolute as isAbsolute4, resolve as resolve16 } from "path";
7621
7703
  var LEGACY_ASSET_MANIFEST_RELATIVE = ".rush/plugins/claude-code/asset-manifest.json";
7622
7704
  var LEGACY_ASSETS_DIR_RELATIVE = ".rush/plugins/claude-code/assets";
7623
7705
  var LEGACY_CLAUDE_CODE_DIR_RELATIVE = ".rush/plugins/claude-code";
@@ -7636,7 +7718,7 @@ async function isSymlinkPointingInto(linkPath, expectedTarget) {
7636
7718
  const lst = await lstat2(linkPath);
7637
7719
  if (!lst.isSymbolicLink()) return false;
7638
7720
  const rawTarget = await readlink2(linkPath);
7639
- const resolvedTarget = isAbsolute4(rawTarget) ? rawTarget : resolve15(linkPath, "..", rawTarget);
7721
+ const resolvedTarget = isAbsolute4(rawTarget) ? rawTarget : resolve16(linkPath, "..", rawTarget);
7640
7722
  return resolvedTarget === expectedTarget || resolvedTarget.startsWith(`${expectedTarget}/`);
7641
7723
  } catch {
7642
7724
  return false;
@@ -7673,10 +7755,10 @@ async function hasLegacyMcpWithoutEnabled(settingsJsonPath) {
7673
7755
  }
7674
7756
  async function detectLegacyInstall(opts = {}) {
7675
7757
  const home = opts.home ?? homedir8();
7676
- const assetManifestPath = resolve15(home, LEGACY_ASSET_MANIFEST_RELATIVE);
7677
- const legacyAssetsDir = resolve15(home, LEGACY_ASSETS_DIR_RELATIVE);
7678
- const skillSymlinkPath = resolve15(home, LEGACY_SKILL_SYMLINK_RELATIVE);
7679
- const settingsJsonPath = resolve15(home, CLAUDE_SETTINGS_JSON_RELATIVE);
7758
+ const assetManifestPath = resolve16(home, LEGACY_ASSET_MANIFEST_RELATIVE);
7759
+ const legacyAssetsDir = resolve16(home, LEGACY_ASSETS_DIR_RELATIVE);
7760
+ const skillSymlinkPath = resolve16(home, LEGACY_SKILL_SYMLINK_RELATIVE);
7761
+ const settingsJsonPath = resolve16(home, CLAUDE_SETTINGS_JSON_RELATIVE);
7680
7762
  const [hasAssetManifest, hasLegacySkillSymlink, legacyMcpWithoutEnabled] = await Promise.all([
7681
7763
  pathExists7(assetManifestPath),
7682
7764
  isSymlinkPointingInto(skillSymlinkPath, legacyAssetsDir),
@@ -7691,7 +7773,7 @@ async function detectLegacyInstall(opts = {}) {
7691
7773
  }
7692
7774
  async function detectLegacyVersion(opts = {}) {
7693
7775
  const home = opts.home ?? homedir8();
7694
- const manifestPath = resolve15(home, LEGACY_ASSET_MANIFEST_RELATIVE);
7776
+ const manifestPath = resolve16(home, LEGACY_ASSET_MANIFEST_RELATIVE);
7695
7777
  try {
7696
7778
  const lst = await stat8(manifestPath);
7697
7779
  if (!lst.isFile()) return "0.7.x";
@@ -7709,7 +7791,7 @@ async function detectLegacyVersion(opts = {}) {
7709
7791
 
7710
7792
  // src/migration/cleanup.ts
7711
7793
  async function cleanupLegacyDir(home) {
7712
- const dir = resolve16(home, LEGACY_CLAUDE_CODE_DIR_RELATIVE);
7794
+ const dir = resolve17(home, LEGACY_CLAUDE_CODE_DIR_RELATIVE);
7713
7795
  try {
7714
7796
  await rm10(dir, { recursive: true, force: true });
7715
7797
  return { ok: true };
@@ -7721,7 +7803,7 @@ async function cleanupLegacyDir(home) {
7721
7803
  }
7722
7804
  }
7723
7805
  async function cleanupLegacySymlink(home) {
7724
- const path4 = resolve16(home, LEGACY_SKILL_SYMLINK_RELATIVE);
7806
+ const path4 = resolve17(home, LEGACY_SKILL_SYMLINK_RELATIVE);
7725
7807
  try {
7726
7808
  let lst;
7727
7809
  try {
@@ -7743,7 +7825,7 @@ async function cleanupLegacySymlink(home) {
7743
7825
  }
7744
7826
  }
7745
7827
  async function cleanupLegacyMcp(home) {
7746
- const path4 = resolve16(home, CLAUDE_SETTINGS_JSON_RELATIVE);
7828
+ const path4 = resolve17(home, CLAUDE_SETTINGS_JSON_RELATIVE);
7747
7829
  try {
7748
7830
  const { data, existed } = await readJsonFile(
7749
7831
  path4,
@@ -7801,11 +7883,11 @@ function errorMessage(err) {
7801
7883
  // src/migration/log.ts
7802
7884
  import { appendFile as appendFile2, mkdir as mkdir10 } from "fs/promises";
7803
7885
  import { homedir as homedir10 } from "os";
7804
- import { dirname as dirname9, resolve as resolve17 } from "path";
7886
+ import { dirname as dirname9, resolve as resolve18 } from "path";
7805
7887
  var MIGRATION_LOG_RELATIVE_PATH = ".rush/plugins/migration-failed.log";
7806
7888
  function resolveMigrationLogPath(opts = {}) {
7807
7889
  const home = opts.home ?? homedir10();
7808
- return resolve17(home, MIGRATION_LOG_RELATIVE_PATH);
7890
+ return resolve18(home, MIGRATION_LOG_RELATIVE_PATH);
7809
7891
  }
7810
7892
  async function appendMigrationFailure(reason, opts = {}) {
7811
7893
  const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
@@ -7830,9 +7912,12 @@ async function appendMigrationFailure(reason, opts = {}) {
7830
7912
 
7831
7913
  // src/migration/migrate.ts
7832
7914
  import { access as access12, readFile as readFile13 } from "fs/promises";
7833
- import { homedir as homedir11 } from "os";
7915
+ import { homedir as homedir12 } from "os";
7834
7916
 
7835
7917
  // src/plugins/resolver.ts
7918
+ import { randomUUID as randomUUID8 } from "crypto";
7919
+ import { mkdir as mkdir11, rename as rename9, rm as rm11 } from "fs/promises";
7920
+ import { homedir as homedir11 } from "os";
7836
7921
  import {
7837
7922
  isAbsolute as isAbsolute5,
7838
7923
  relative as pathRelative2,
@@ -7843,7 +7928,7 @@ import {
7843
7928
  // src/plugins/capabilities.ts
7844
7929
  import { constants as fsConstants9 } from "fs";
7845
7930
  import { access as access10, readdir as readdir4, stat as stat9 } from "fs/promises";
7846
- import { extname as extname2, resolve as resolve18 } from "path";
7931
+ import { extname as extname2, resolve as resolve19 } from "path";
7847
7932
  var CAPABILITY_ORDER = [
7848
7933
  "commands",
7849
7934
  "skills",
@@ -7872,10 +7957,10 @@ async function scanCapabilities(sourceDir, manifest) {
7872
7957
  return CAPABILITY_ORDER.filter((cap) => found.has(cap));
7873
7958
  }
7874
7959
  async function hasCommands(sourceDir) {
7875
- return dirHasFileWithExt(resolve18(sourceDir, "commands"), ".md");
7960
+ return dirHasFileWithExt(resolve19(sourceDir, "commands"), ".md");
7876
7961
  }
7877
7962
  async function hasSkills(sourceDir) {
7878
- const skillsDir = resolve18(sourceDir, "skills");
7963
+ const skillsDir = resolve19(sourceDir, "skills");
7879
7964
  if (await dirExists3(skillsDir)) {
7880
7965
  let entries;
7881
7966
  try {
@@ -7885,7 +7970,7 @@ async function hasSkills(sourceDir) {
7885
7970
  }
7886
7971
  for (const entry of entries) {
7887
7972
  if (!entry.isDirectory()) continue;
7888
- const skillMdPath = resolve18(skillsDir, entry.name, "SKILL.md");
7973
+ const skillMdPath = resolve19(skillsDir, entry.name, "SKILL.md");
7889
7974
  if (await fileExists2(skillMdPath)) {
7890
7975
  return true;
7891
7976
  }
@@ -7894,10 +7979,10 @@ async function hasSkills(sourceDir) {
7894
7979
  return hasClaudeStyleSkills(sourceDir);
7895
7980
  }
7896
7981
  async function hasRules(sourceDir) {
7897
- return dirHasFileWithExt(resolve18(sourceDir, "rules"), ".md");
7982
+ return dirHasFileWithExt(resolve19(sourceDir, "rules"), ".md");
7898
7983
  }
7899
7984
  async function hasHooks(sourceDir) {
7900
- const hooksDir = resolve18(sourceDir, "hooks");
7985
+ const hooksDir = resolve19(sourceDir, "hooks");
7901
7986
  if (!await dirExists3(hooksDir)) {
7902
7987
  return false;
7903
7988
  }
@@ -7939,7 +8024,7 @@ async function dirHasFileWithExt(dirPath, ext) {
7939
8024
  );
7940
8025
  }
7941
8026
  async function hasClaudeStyleSkills(sourceDir) {
7942
- const dotSkillsDir = resolve18(sourceDir, ".skills");
8027
+ const dotSkillsDir = resolve19(sourceDir, ".skills");
7943
8028
  if (!await dirExists3(dotSkillsDir)) {
7944
8029
  return false;
7945
8030
  }
@@ -7951,7 +8036,7 @@ async function hasClaudeStyleSkills(sourceDir) {
7951
8036
  return false;
7952
8037
  }
7953
8038
  for (const entry of entries) {
7954
- const abs = resolve18(dirPath, entry.name);
8039
+ const abs = resolve19(dirPath, entry.name);
7955
8040
  if (entry.isDirectory()) {
7956
8041
  if (await walk2(abs)) return true;
7957
8042
  continue;
@@ -8031,25 +8116,25 @@ var PluginManifestCorruptError = class extends PluginResolverError {
8031
8116
  this.name = "PluginManifestCorruptError";
8032
8117
  }
8033
8118
  };
8034
- var InvalidPluginVersionError = class extends PluginResolverError {
8035
- constructor(pluginName, manifestPath, actualValue) {
8119
+ var PluginCloneFailedError = class extends PluginResolverError {
8120
+ constructor(pluginName, marketplaceName, cause) {
8036
8121
  super(
8037
- `plugin.json at '${manifestPath}' has invalid version field (got ${actualValue === void 0 ? "undefined" : `'${actualValue}'`}). Plugin '${pluginName}' must declare a non-empty version (e.g. "0.1.0"); 'unknown' is not allowed (see #906).`
8122
+ `Failed to clone plugin '${pluginName}' from marketplace '${marketplaceName}': ${cause instanceof Error ? cause.message : String(cause)}`
8038
8123
  );
8039
8124
  this.pluginName = pluginName;
8040
- this.manifestPath = manifestPath;
8041
- this.actualValue = actualValue;
8042
- this.name = "InvalidPluginVersionError";
8125
+ this.marketplaceName = marketplaceName;
8126
+ this.cause = cause;
8127
+ this.name = "PluginCloneFailedError";
8043
8128
  }
8044
8129
  };
8045
8130
 
8046
8131
  // src/plugins/manifest.ts
8047
8132
  import { constants as fsConstants10 } from "fs";
8048
8133
  import { access as access11, readFile as readFile12 } from "fs/promises";
8049
- import { resolve as resolve19 } from "path";
8134
+ import { resolve as resolve20 } from "path";
8050
8135
  var PLUGIN_MANIFEST_RELATIVE_PATH = ".claude-plugin/plugin.json";
8051
8136
  async function readPluginManifest(pluginName, sourceDir) {
8052
- const manifestPath = resolve19(sourceDir, PLUGIN_MANIFEST_RELATIVE_PATH);
8137
+ const manifestPath = resolve20(sourceDir, PLUGIN_MANIFEST_RELATIVE_PATH);
8053
8138
  try {
8054
8139
  await access11(manifestPath, fsConstants10.R_OK);
8055
8140
  } catch {
@@ -8081,13 +8166,8 @@ function parsePluginManifest(pluginName, raw, sourcePathForError) {
8081
8166
  "'name' must be a non-empty string"
8082
8167
  );
8083
8168
  }
8084
- const version = obj.version;
8085
- if (typeof version !== "string" || version.length === 0 || version.toLowerCase() === "unknown") {
8086
- throw new InvalidPluginVersionError(
8087
- pluginName,
8088
- sourcePathForError,
8089
- typeof version === "string" ? version : void 0
8090
- );
8169
+ if (typeof obj.version !== "string" || obj.version.length === 0) {
8170
+ obj.version = "unknown";
8091
8171
  }
8092
8172
  if (obj.mcpServers !== void 0) {
8093
8173
  const m = obj.mcpServers;
@@ -8108,6 +8188,10 @@ var RUSH_AI_MARKETPLACE_NAME3 = "rush-marketplace";
8108
8188
  var RUSH_MCP_SERVER_KEY3 = "rush";
8109
8189
  async function resolvePlugin(ref, marketplace, options = {}) {
8110
8190
  const entry = findPluginEntry(ref, marketplace);
8191
+ await ensurePluginCloned(ref, marketplace, entry, {
8192
+ runner: options.gitRunner,
8193
+ dryRun: options.dryRun
8194
+ });
8111
8195
  const sourceDir = resolvePluginSourceDir(ref, marketplace, entry);
8112
8196
  const manifest = await readPluginManifest(ref.name, sourceDir);
8113
8197
  const capabilities = await scanCapabilities(sourceDir, manifest);
@@ -8137,6 +8221,9 @@ function findPluginEntry(ref, marketplace) {
8137
8221
  return entry;
8138
8222
  }
8139
8223
  function resolvePluginSourceDir(ref, marketplace, entry) {
8224
+ if (hasUrlSource(entry)) {
8225
+ return extractUrlSourceDir(marketplace, entry);
8226
+ }
8140
8227
  const relPath = extractRelativeSourcePath(ref, marketplace, entry);
8141
8228
  if (isAbsolute5(relPath)) {
8142
8229
  throw new PluginSourceUnresolvableError(
@@ -8158,6 +8245,29 @@ function resolvePluginSourceDir(ref, marketplace, entry) {
8158
8245
  }
8159
8246
  return abs;
8160
8247
  }
8248
+ function extractUrlSourceDir(marketplace, entry) {
8249
+ const src = entry.source;
8250
+ const cacheDir = pluginCacheDir(marketplace.name, entry.name);
8251
+ const subPath = typeof src.path === "string" ? src.path.trim() : "";
8252
+ if (!subPath) return cacheDir;
8253
+ if (isAbsolute5(subPath)) {
8254
+ throw new PluginSourceUnresolvableError(
8255
+ entry.name,
8256
+ marketplace.name,
8257
+ `URL source subpath must be relative; got absolute '${subPath}'`
8258
+ );
8259
+ }
8260
+ const resolved = pathResolve2(cacheDir, subPath);
8261
+ const rel = pathRelative2(cacheDir, resolved);
8262
+ if (rel === ".." || rel.startsWith(`..${pathSep}`) || isAbsolute5(rel)) {
8263
+ throw new PluginSourceUnresolvableError(
8264
+ entry.name,
8265
+ marketplace.name,
8266
+ `URL source subpath '${subPath}' escapes plugin cache dir`
8267
+ );
8268
+ }
8269
+ return resolved;
8270
+ }
8161
8271
  function extractRelativeSourcePath(ref, marketplace, entry) {
8162
8272
  if (entry.source === void 0 || entry.source === null) {
8163
8273
  return `plugins/${entry.name}`;
@@ -8227,12 +8337,93 @@ function defaultRushAiBinaryResolver2() {
8227
8337
  }
8228
8338
  return void 0;
8229
8339
  }
8340
+ function pluginCacheDir(marketplaceName, pluginName) {
8341
+ assertSafePathComponent(marketplaceName, "marketplace name");
8342
+ assertSafePathComponent(pluginName, "plugin name");
8343
+ return pathResolve2(
8344
+ homedir11(),
8345
+ ".rush",
8346
+ "plugin-cache",
8347
+ marketplaceName,
8348
+ pluginName
8349
+ );
8350
+ }
8351
+ function assertSafePathComponent(value, label) {
8352
+ if (value.includes("/") || value.includes("\\") || value === ".." || value.startsWith("../") || value.startsWith("..\\")) {
8353
+ throw new PluginSourceUnresolvableError(
8354
+ value,
8355
+ "",
8356
+ `${label} '${value}' contains unsafe path characters`
8357
+ );
8358
+ }
8359
+ }
8360
+ function hasUrlSource(entry) {
8361
+ const src = entry.source;
8362
+ return !!src && typeof src === "object" && !Array.isArray(src) && typeof src.url === "string" && src.url.length > 0;
8363
+ }
8364
+ async function ensurePluginCloned(ref, marketplace, entry, opts) {
8365
+ const src = entry.source;
8366
+ if (!src || typeof src !== "object" || Array.isArray(src)) return;
8367
+ if (typeof src.url !== "string" || !src.url) return;
8368
+ const cacheDir = pluginCacheDir(marketplace.name, entry.name);
8369
+ if (await pathExists2(cacheDir)) return;
8370
+ if (opts?.dryRun) {
8371
+ throw new PluginSourceUnresolvableError(
8372
+ ref.name,
8373
+ marketplace.name,
8374
+ `Plugin '${ref.name}' has not been cached yet. Run without --dry-run first.`
8375
+ );
8376
+ }
8377
+ const git = opts?.runner ?? defaultGitRunner;
8378
+ const parent = pathResolve2(cacheDir, "..");
8379
+ await mkdir11(parent, { recursive: true });
8380
+ const tmpDir = `${cacheDir}.${randomUUID8()}.tmp`;
8381
+ try {
8382
+ const args = ["clone", "--depth", "1"];
8383
+ if (typeof src.ref === "string" && src.ref.length > 0) {
8384
+ args.push("--branch", src.ref);
8385
+ }
8386
+ args.push("--", src.url, tmpDir);
8387
+ const result = await git(args);
8388
+ if (result.exitCode !== 0) {
8389
+ throw new Error(
8390
+ `git clone exited with code ${result.exitCode}: ${result.stderr.trim()}`
8391
+ );
8392
+ }
8393
+ if (typeof src.sha === "string" && src.sha.length > 0) {
8394
+ try {
8395
+ const head = await git(["rev-parse", "HEAD"], { cwd: tmpDir });
8396
+ const actualSha = head.stdout.trim();
8397
+ if (!actualSha.startsWith(src.sha) && !src.sha.startsWith(actualSha)) {
8398
+ output.warn(
8399
+ `Plugin '${ref.name}' sha mismatch (expected ${src.sha.slice(0, 7)}, got ${actualSha.slice(0, 7)})`
8400
+ );
8401
+ }
8402
+ } catch {
8403
+ }
8404
+ }
8405
+ try {
8406
+ await rename9(tmpDir, cacheDir);
8407
+ } catch (err) {
8408
+ if (err.code === "ENOTEMPTY" && await pathExists2(cacheDir)) {
8409
+ await rm11(tmpDir, { recursive: true, force: true });
8410
+ return;
8411
+ }
8412
+ throw err;
8413
+ }
8414
+ } catch (err) {
8415
+ await rm11(tmpDir, { recursive: true, force: true }).catch(() => {
8416
+ });
8417
+ if (err instanceof PluginSourceUnresolvableError) throw err;
8418
+ throw new PluginCloneFailedError(ref.name, marketplace.name, err);
8419
+ }
8420
+ }
8230
8421
 
8231
8422
  // src/migration/migrate.ts
8232
8423
  var LEGACY_MIGRATION_KEY = "legacy-rush-0.7.x";
8233
8424
  var SKIP_MIGRATION_ENV = "RUSH_SKIP_MIGRATION";
8234
8425
  async function maybeRunMigration(input) {
8235
- const home = input.home ?? homedir11();
8426
+ const home = input.home ?? homedir12();
8236
8427
  const now = input.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
8237
8428
  const envGet = input.env ?? ((key) => process.env[key]);
8238
8429
  const reporter = input.reporter ?? {};
@@ -8804,6 +8995,10 @@ async function runInstall(input) {
8804
8995
  const rushSource = parseSource(`rush://${apiHost}`);
8805
8996
  await cache.add(rushSource, { as: ref.marketplace });
8806
8997
  marketplace = await cache.get(ref.marketplace);
8998
+ } else if (ref.marketplace === "claude-plugins-official") {
8999
+ const source = parseSource("github:anthropics/claude-plugins-official");
9000
+ await cache.add(source, { as: "claude-plugins-official" });
9001
+ marketplace = await cache.get("claude-plugins-official");
8807
9002
  } else {
8808
9003
  throw new RushError(
8809
9004
  `Marketplace '${ref.marketplace}' is not in the local cache. Run 'rush-ai marketplace add <source>' first.`,
@@ -8837,8 +9032,8 @@ async function runInstall(input) {
8837
9032
  }
8838
9033
  }
8839
9034
  if (marketplace.source.kind === "rush") {
8840
- const pluginDir = resolve20(marketplace.rootDir, "plugins", ref.name);
8841
- const manifestPath = resolve20(pluginDir, ".claude-plugin/plugin.json");
9035
+ const pluginDir = resolve21(marketplace.rootDir, "plugins", ref.name);
9036
+ const manifestPath = resolve21(pluginDir, ".claude-plugin/plugin.json");
8842
9037
  const alreadyMaterialized = await pathExists2(manifestPath);
8843
9038
  const hasNewSecrets = input.secrets && Object.keys(input.secrets).length > 0;
8844
9039
  const shouldMaterialize = !alreadyMaterialized || force || hasNewSecrets;
@@ -8900,15 +9095,24 @@ async function runInstall(input) {
8900
9095
  }
8901
9096
  }
8902
9097
  }
9098
+ if (force && !dryRun) {
9099
+ const entry = marketplace.manifest.plugins.find((p) => p.name === ref.name);
9100
+ if (entry && hasUrlSource(entry)) {
9101
+ await rm12(pluginCacheDir(ref.marketplace, ref.name), {
9102
+ recursive: true,
9103
+ force: true
9104
+ });
9105
+ }
9106
+ }
8903
9107
  const resolveFn = input.resolvePluginFn ?? resolvePlugin;
8904
9108
  let plugin;
8905
9109
  try {
8906
- plugin = await resolveFn(ref, marketplace);
9110
+ plugin = await resolveFn(ref, marketplace, { dryRun });
8907
9111
  } catch (err) {
8908
9112
  if (err instanceof PluginNotFoundInMarketplaceError && marketplace.source.kind === "rush") {
8909
9113
  marketplace = await cache.update(ref.marketplace);
8910
9114
  try {
8911
- plugin = await resolveFn(ref, marketplace);
9115
+ plugin = await resolveFn(ref, marketplace, { dryRun });
8912
9116
  } catch (retryErr) {
8913
9117
  if (retryErr instanceof PluginNotFoundInMarketplaceError) {
8914
9118
  throw new RushError(
@@ -9106,8 +9310,9 @@ function registerInstallCommand(group, _root) {
9106
9310
  function printInstallSummary(result) {
9107
9311
  const { plugin, results, targetExplicit, dryRun } = result;
9108
9312
  const tag = dryRun ? " (dry-run)" : "";
9313
+ const ver = plugin.version !== "unknown" ? ` v${plugin.version}` : "";
9109
9314
  output.log(
9110
- `Installing ${plugin.ref.name}@${plugin.ref.marketplace} v${plugin.version}...${tag}`
9315
+ `Installing ${plugin.ref.name}@${plugin.ref.marketplace}${ver}...${tag}`
9111
9316
  );
9112
9317
  output.newline();
9113
9318
  for (const r of results) {
@@ -9707,68 +9912,201 @@ function registerPluginCommand(program) {
9707
9912
  }
9708
9913
 
9709
9914
  // src/commands/skill/index.ts
9710
- import { existsSync as existsSync11, readdirSync, readFileSync as readFileSync7 } from "fs";
9711
- import { basename as basename3, resolve as resolve21 } from "path";
9712
- var SKILL_DESCRIPTION = "Print agent usage skills (hand-off, agent-shelf, ...) as raw markdown.";
9915
+ import { spawn as spawn2 } from "child_process";
9916
+ import { fileURLToPath } from "url";
9917
+ var SKILL_DESCRIPTION = "Manage AI agent skills through reskill using Rush auth and registry defaults.";
9713
9918
  var SKILL_HELP_AFTER = `
9714
9919
  Examples:
9715
- $ npx rush-ai skill Print the skills index
9716
- $ npx rush-ai skill hand-off Print the hand-off playbook
9717
- $ npx rush-ai skill agent-shelf Print the agent-shelf playbook
9718
- $ npx rush-ai skill --list List available skills
9920
+ $ rush-ai skill install @kanyun/rush-find-skills
9921
+ $ rush-ai skill find vue
9922
+ $ rush-ai skill list
9923
+ $ rush-ai skill publish --dry-run
9924
+ $ rush-ai skill group list
9719
9925
 
9720
- Designed to be consumed by AI agents inside IDEs (Cursor, Claude Code,
9721
- etc). Output is raw markdown on stdout \u2014 pipe it, grep it, feed it to
9722
- your own context.
9926
+ Auth:
9927
+ Use rush-ai auth login/logout/status. rush-ai skill forwards your Rush
9928
+ token to the bundled reskill package manager via RESKILL_TOKEN and
9929
+ defaults RESKILL_REGISTRY to the active Rush API URL.
9930
+
9931
+ Notes:
9932
+ rush-ai skill is a thin facade: unknown reskill flags and arguments are
9933
+ forwarded as-is after Rush auth/registry injection.
9934
+
9935
+ The old "rush-ai skill hand-off" playbook path is deprecated. Use
9936
+ "rush-ai playbook hand-off" instead.
9723
9937
  `;
9724
- function resolveSkillsDir() {
9725
- const baseDir = import.meta.dirname ?? __dirname;
9726
- if (!baseDir) return null;
9727
- const candidates = [
9728
- resolve21(baseDir, "skills"),
9729
- resolve21(baseDir, "..", "skills"),
9730
- resolve21(baseDir, "..", "..", "skills"),
9731
- resolve21(baseDir, "..", "..", "..", "skills"),
9732
- resolve21(baseDir, "..", "..", "..", "..", "skills")
9733
- ];
9734
- for (const p of candidates) {
9735
- if (existsSync11(p)) return p;
9938
+ var DISABLE_RESKILL_UPDATE_CHECK_PRELOAD = `data:text/javascript,${encodeURIComponent(`
9939
+ const originalFetch = globalThis.fetch;
9940
+ globalThis.fetch = (input, init) => {
9941
+ const url = typeof input === 'string' ? input : input?.url;
9942
+ if (url === 'https://registry.npmjs.org/reskill') {
9943
+ return Promise.resolve(new Response('', { status: 204 }));
9944
+ }
9945
+ return originalFetch(input, init);
9946
+ };
9947
+ `)}`;
9948
+ var AUTH_LIFECYCLE_COMMANDS = /* @__PURE__ */ new Set(["login", "logout", "whoami"]);
9949
+ var RESKILL_JSON_COMMANDS = /* @__PURE__ */ new Set([
9950
+ "find",
9951
+ "search",
9952
+ "list",
9953
+ "ls",
9954
+ "info",
9955
+ "outdated",
9956
+ "doctor"
9957
+ ]);
9958
+ var RESKILL_YES_COMMANDS = /* @__PURE__ */ new Set([
9959
+ "install",
9960
+ "i",
9961
+ "uninstall",
9962
+ "un",
9963
+ "remove",
9964
+ "rm",
9965
+ "publish",
9966
+ "pub"
9967
+ ]);
9968
+ function hasAnyFlag(args, flags) {
9969
+ return args.some((arg) => flags.includes(arg));
9970
+ }
9971
+ function stripRushOnlyFlags(args) {
9972
+ let ci = false;
9973
+ const forwarded = [];
9974
+ for (const arg of args) {
9975
+ if (arg === "--ci") {
9976
+ ci = true;
9977
+ continue;
9978
+ }
9979
+ if (arg.startsWith("--ci=")) {
9980
+ ci = !["0", "false", "no"].includes(
9981
+ arg.slice("--ci=".length).toLowerCase()
9982
+ );
9983
+ continue;
9984
+ }
9985
+ forwarded.push(arg);
9736
9986
  }
9737
- return null;
9987
+ return { args: forwarded, ci };
9988
+ }
9989
+ function applyGlobalOptions(args, opts) {
9990
+ const forwarded = [...args];
9991
+ const command = forwarded[0];
9992
+ if (!command) return forwarded;
9993
+ if (opts.json && RESKILL_JSON_COMMANDS.has(command) && !hasAnyFlag(forwarded, ["--json", "-j"])) {
9994
+ forwarded.push("--json");
9995
+ }
9996
+ const needsYes = RESKILL_YES_COMMANDS.has(command) || command === "group" && forwarded[1] === "delete";
9997
+ if (opts.ci && needsYes && !hasAnyFlag(forwarded, ["--yes", "-y"])) {
9998
+ forwarded.push("--yes");
9999
+ }
10000
+ return forwarded;
10001
+ }
10002
+ function createReskillEnv() {
10003
+ const env = { ...process.env };
10004
+ env.RESKILL_REGISTRY = getGlobalConfig().api;
10005
+ const token = getAuthToken();
10006
+ if (token) {
10007
+ env.RESKILL_TOKEN = token;
10008
+ }
10009
+ return env;
9738
10010
  }
9739
- function listSkills(dir) {
9740
- return readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => basename3(f, ".md")).sort();
10011
+ function getReskillInvocation() {
10012
+ return {
10013
+ command: process.execPath,
10014
+ args: [
10015
+ "--import",
10016
+ DISABLE_RESKILL_UPDATE_CHECK_PRELOAD,
10017
+ fileURLToPath(import.meta.resolve("reskill/cli"))
10018
+ ]
10019
+ };
10020
+ }
10021
+ function runReskill(args) {
10022
+ return new Promise((resolve23, reject) => {
10023
+ const reskill = getReskillInvocation();
10024
+ const child = spawn2(reskill.command, [...reskill.args, ...args], {
10025
+ env: createReskillEnv(),
10026
+ stdio: "inherit"
10027
+ });
10028
+ child.on("error", reject);
10029
+ child.on("close", (code, signal) => {
10030
+ if (signal) {
10031
+ output.error(`reskill exited from signal ${signal}`);
10032
+ resolve23(1);
10033
+ return;
10034
+ }
10035
+ resolve23(code ?? 1);
10036
+ });
10037
+ });
10038
+ }
10039
+ function maybeHandleLegacyPlaybook(args) {
10040
+ if (args.length === 1 && args[0] === "--list") {
10041
+ output.warn(
10042
+ "`rush-ai skill --list` is deprecated. Use `rush-ai playbook --list` for playbooks or `rush-ai skill list` for installed skills."
10043
+ );
10044
+ printPlaybook(void 0, { list: true });
10045
+ return true;
10046
+ }
10047
+ const [name] = args;
10048
+ if (isPlaybookName(name)) {
10049
+ output.warn(
10050
+ `\`rush-ai skill ${name}\` is deprecated. Use \`rush-ai playbook ${name}\` instead.`
10051
+ );
10052
+ printPlaybook(name);
10053
+ return true;
10054
+ }
10055
+ return false;
9741
10056
  }
9742
10057
  function registerSkillCommand(program) {
9743
- program.command("skill [name]").description(SKILL_DESCRIPTION).addHelpText("after", SKILL_HELP_AFTER).option("--list", "List available skill names and exit").action((name, opts) => {
9744
- const dir = resolveSkillsDir();
9745
- if (!dir) {
9746
- output.error(
9747
- "Could not locate the skills/ directory. This is a packaging bug \u2014 please file an issue."
9748
- );
9749
- process.exit(2);
10058
+ const command = program.command("skill [args...]").description(SKILL_DESCRIPTION).addHelpText("after", SKILL_HELP_AFTER).allowUnknownOption(true).allowExcessArguments(true).action(async (rawArgs) => {
10059
+ const initialArgs = rawArgs ?? [];
10060
+ if (maybeHandleLegacyPlaybook(initialArgs)) {
9750
10061
  return;
9751
10062
  }
9752
- const available = listSkills(dir);
9753
- if (opts.list) {
9754
- for (const s of available) output.log(s);
10063
+ const stripped = stripRushOnlyFlags(initialArgs);
10064
+ if (stripped.args.length === 0) {
10065
+ command.help();
9755
10066
  return;
9756
10067
  }
9757
- const target = name ?? "README";
9758
- const file2 = resolve21(dir, `${target}.md`);
9759
- if (!existsSync11(file2)) {
9760
- output.error(`Unknown skill "${target}".`);
9761
- output.dim(`Available: ${available.join(", ") || "(none)"}`);
10068
+ const [reskillCommand] = stripped.args;
10069
+ if (reskillCommand && AUTH_LIFECYCLE_COMMANDS.has(reskillCommand)) {
10070
+ output.error(
10071
+ `Use \`rush-ai auth ${reskillCommand === "whoami" ? "status" : reskillCommand}\` instead.`
10072
+ );
10073
+ output.dim(
10074
+ "`rush-ai skill` reuses Rush auth automatically; it does not maintain a separate reskill login state."
10075
+ );
9762
10076
  process.exit(1);
9763
10077
  return;
9764
10078
  }
9765
- process.stdout.write(readFileSync7(file2, "utf-8"));
10079
+ const globalOpts = command.optsWithGlobals();
10080
+ const forwarded = applyGlobalOptions(stripped.args, {
10081
+ json: globalOpts.json,
10082
+ ci: stripped.ci || isCIMode(globalOpts)
10083
+ });
10084
+ try {
10085
+ const code = await runReskill(forwarded);
10086
+ if (code !== 0) {
10087
+ process.exit(code);
10088
+ }
10089
+ } catch (error) {
10090
+ const err = error;
10091
+ if (err.code === "ENOENT") {
10092
+ output.error(
10093
+ "Could not start the bundled reskill CLI. Reinstall rush-ai and try again."
10094
+ );
10095
+ } else if (err.code === "ERR_PACKAGE_PATH_NOT_EXPORTED" || err.code === "ERR_MODULE_NOT_FOUND") {
10096
+ output.error(
10097
+ "Could not resolve the bundled reskill CLI entry. Reinstall rush-ai and try again."
10098
+ );
10099
+ } else {
10100
+ output.error(`Failed to run reskill: ${err.message}`);
10101
+ }
10102
+ process.exit(1);
10103
+ }
9766
10104
  });
9767
10105
  }
9768
10106
 
9769
10107
  // src/commands/task/index.ts
9770
10108
  import { createWriteStream } from "fs";
9771
- import { mkdir as mkdir11, readFile as readFile14, stat as stat10 } from "fs/promises";
10109
+ import { mkdir as mkdir12, readFile as readFile14, stat as stat10 } from "fs/promises";
9772
10110
  import path3 from "path";
9773
10111
  import { Readable } from "stream";
9774
10112
  import { pipeline } from "stream/promises";
@@ -10136,7 +10474,7 @@ function writeHandoffFile(projectPath, content) {
10136
10474
  var NO_PROJECT_HINT = [
10137
10475
  "No rush project detected for this directory. Either:",
10138
10476
  ' rush-ai task create -a web-builder -p "..." (build a new site from a prompt)',
10139
- " rush-ai task init (link an existing local project)"
10477
+ " rush-ai task link (link an existing local project)"
10140
10478
  ].join("\n");
10141
10479
  function registerPushSubcommand(task, program) {
10142
10480
  task.command("push").description(
@@ -10314,7 +10652,7 @@ function pushWithRouting(projectPath, source, envGitRemote) {
10314
10652
  if (!envRemoteUsable) {
10315
10653
  return {
10316
10654
  success: false,
10317
- stderr: "Cannot push: .rush/env.md identifies a Rush project but has no usable GIT_REMOTE. Re-run `rush-ai task init` to refresh the deploy token, or set `origin` to the Rush GitLab URL."
10655
+ stderr: "Cannot push: .rush/env.md identifies a Rush project but has no usable GIT_REMOTE. Re-run `rush-ai task link` to refresh the deploy token, or set `origin` to the Rush GitLab URL."
10318
10656
  };
10319
10657
  }
10320
10658
  return gitPushUrl(projectPath, envGitRemote);
@@ -11256,7 +11594,7 @@ function formatCreatedAt(ts) {
11256
11594
  var MAX_TEXT_LENGTH = 500;
11257
11595
  function truncateText(text) {
11258
11596
  if (text.length <= MAX_TEXT_LENGTH) return text;
11259
- return text.slice(0, MAX_TEXT_LENGTH) + "...";
11597
+ return `${text.slice(0, MAX_TEXT_LENGTH)}...`;
11260
11598
  }
11261
11599
  function summarizeTools(parts) {
11262
11600
  const toolParts = parts.filter(
@@ -11287,7 +11625,9 @@ function renderMessages(messages, conversationId, compact) {
11287
11625
  );
11288
11626
  if (textParts.length > 0) {
11289
11627
  for (const tp of textParts) {
11290
- output.log(` ${truncateText(tp.text)}`);
11628
+ if (tp.text) {
11629
+ output.log(` ${truncateText(tp.text)}`);
11630
+ }
11291
11631
  }
11292
11632
  } else if (msg.content) {
11293
11633
  output.log(` ${truncateText(msg.content)}`);
@@ -11341,7 +11681,7 @@ async function downloadFile(file2, destPath, client) {
11341
11681
  if (!response.body) {
11342
11682
  throw new Error("No response body");
11343
11683
  }
11344
- await mkdir11(path3.dirname(destPath), { recursive: true });
11684
+ await mkdir12(path3.dirname(destPath), { recursive: true });
11345
11685
  const nodeStream = Readable.fromWeb(
11346
11686
  response.body
11347
11687
  );
@@ -11370,7 +11710,7 @@ Typical flows:
11370
11710
  # Quick one-shot with the default agent (\`rush\`)
11371
11711
  $ rush-ai task create -a rush -p "Summarize the latest release notes"
11372
11712
 
11373
- For agents: run \`rush-ai skill hand-off\` for the full playbook.
11713
+ For agents: run \`rush-ai playbook hand-off\` for the full playbook.
11374
11714
  `;
11375
11715
  function registerTaskCommand(program) {
11376
11716
  const task = program.command("task").description("Create and manage tasks").addHelpText("after", TASK_HELP_AFTER);
@@ -11720,7 +12060,7 @@ function registerTaskCommand(program) {
11720
12060
  }
11721
12061
  if (options.last) {
11722
12062
  const n = parseInt(options.last, 10);
11723
- if (!isNaN(n) && n > 0) {
12063
+ if (!Number.isNaN(n) && n > 0) {
11724
12064
  messages = messages.slice(-n);
11725
12065
  }
11726
12066
  }
@@ -11937,6 +12277,7 @@ function registerCommands(program) {
11937
12277
  registerAgentCommand(program);
11938
12278
  registerTaskCommand(program);
11939
12279
  registerSkillCommand(program);
12280
+ registerPlaybookCommand(program);
11940
12281
  registerCheckCommand(program);
11941
12282
  registerMcpCommand(program);
11942
12283
  registerMarketplaceCommand(program);
@@ -11947,8 +12288,8 @@ function registerCommands(program) {
11947
12288
  }
11948
12289
 
11949
12290
  // src/util/update-check.ts
11950
- import { mkdir as mkdir12, readFile as readFile15, writeFile as writeFile9 } from "fs/promises";
11951
- import { homedir as homedir12 } from "os";
12291
+ import { mkdir as mkdir13, readFile as readFile15, writeFile as writeFile9 } from "fs/promises";
12292
+ import { homedir as homedir13 } from "os";
11952
12293
  import { dirname as dirname10, join as join10 } from "path";
11953
12294
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
11954
12295
  var FETCH_TIMEOUT_MS = 3e3;
@@ -11972,13 +12313,13 @@ function isNewerVersion(current, latest) {
11972
12313
  }
11973
12314
  async function writeLastCheck(checkFile) {
11974
12315
  try {
11975
- await mkdir12(dirname10(checkFile), { recursive: true });
12316
+ await mkdir13(dirname10(checkFile), { recursive: true });
11976
12317
  await writeFile9(checkFile, JSON.stringify({ lastCheck: Date.now() }));
11977
12318
  } catch {
11978
12319
  }
11979
12320
  }
11980
12321
  async function checkForUpdate(currentVersion) {
11981
- const rushDir = join10(homedir12(), ".rush");
12322
+ const rushDir = join10(homedir13(), ".rush");
11982
12323
  const checkFile = join10(rushDir, "update-check.json");
11983
12324
  try {
11984
12325
  if (process.env.CI) return;
@@ -12015,7 +12356,7 @@ var BANNER = `
12015
12356
  ${chalk7.dim("\xB7")} From Cursor / Claude Code, hand off a task to a Rush agent without restating context.
12016
12357
  ${chalk7.dim("\xB7")} Compose workflows where a Rush specialist agent runs as your sub-agent.
12017
12358
 
12018
- ${chalk7.dim("For agents:")} ${chalk7.cyan("rush-ai skill")} prints ready-to-consume usage playbooks.
12359
+ ${chalk7.dim("For agents:")} ${chalk7.cyan("rush-ai playbook")} prints ready-to-consume usage playbooks.
12019
12360
  `;
12020
12361
  function showWelcomeGuide() {
12021
12362
  const lines = [
@@ -12027,11 +12368,12 @@ function showWelcomeGuide() {
12027
12368
  ` ${chalk7.cyan("rush-ai auth login")} Log in to the Rush platform`,
12028
12369
  ` ${chalk7.cyan("rush-ai agent list")} Browse available agents`,
12029
12370
  ` ${chalk7.cyan("rush-ai task create -a web-builder")} Hand a task to a Rush agent`,
12371
+ ` ${chalk7.cyan("rush-ai skill install <skill>")} Install a Skill through Rush auth`,
12030
12372
  "",
12031
12373
  chalk7.dim(" For AI agents:"),
12032
- ` ${chalk7.cyan("rush-ai skill")} Print usage playbooks (markdown)`,
12033
- ` ${chalk7.cyan("rush-ai skill hand-off")} IDE \u2192 Rush task hand-off`,
12034
- ` ${chalk7.cyan("rush-ai skill agent-shelf")} Calling a Rush agent as a sub-agent`,
12374
+ ` ${chalk7.cyan("rush-ai playbook")} Print usage playbooks (markdown)`,
12375
+ ` ${chalk7.cyan("rush-ai playbook hand-off")} IDE \u2192 Rush task hand-off`,
12376
+ ` ${chalk7.cyan("rush-ai playbook agent-shelf")} Calling a Rush agent as a sub-agent`,
12035
12377
  ""
12036
12378
  ];
12037
12379
  try {