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/AGENTS.md +3 -3
- package/README.md +40 -0
- package/dist/index.js +514 -172
- package/dist/index.js.map +1 -1
- package/dist/skills/.rush-playbooks +1 -0
- package/package.json +3 -2
- package/skills/.rush-playbooks +1 -0
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
4464
|
+
return resolve10(this.home, CLAUDE_DIR);
|
|
4383
4465
|
}
|
|
4384
4466
|
/** `<home>/.claude/settings.json` */
|
|
4385
4467
|
get settingsJson() {
|
|
4386
|
-
return
|
|
4468
|
+
return resolve10(this.claudeDir, "settings.json");
|
|
4387
4469
|
}
|
|
4388
4470
|
/** `<home>/.claude/plugins/` */
|
|
4389
4471
|
get pluginsDir() {
|
|
4390
|
-
return
|
|
4472
|
+
return resolve10(this.claudeDir, PLUGINS_SUBDIR);
|
|
4391
4473
|
}
|
|
4392
4474
|
/** `<home>/.claude/plugins/known_marketplaces.json` */
|
|
4393
4475
|
get knownMarketplacesJson() {
|
|
4394
|
-
return
|
|
4476
|
+
return resolve10(this.pluginsDir, "known_marketplaces.json");
|
|
4395
4477
|
}
|
|
4396
4478
|
/** `<home>/.claude/plugins/installed_plugins.json` */
|
|
4397
4479
|
get installedPluginsJson() {
|
|
4398
|
-
return
|
|
4480
|
+
return resolve10(this.pluginsDir, "installed_plugins.json");
|
|
4399
4481
|
}
|
|
4400
4482
|
/** `<home>/.claude/plugins/cache/` */
|
|
4401
4483
|
get cacheDir() {
|
|
4402
|
-
return
|
|
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
|
|
4494
|
+
return resolve10(this.pluginsDir, "marketplaces");
|
|
4413
4495
|
}
|
|
4414
4496
|
/** `<home>/.claude/plugins/marketplaces/<mkt>/` */
|
|
4415
4497
|
marketplaceInstallDir(marketplace) {
|
|
4416
|
-
return
|
|
4498
|
+
return resolve10(this.marketplacesRootDir, marketplace);
|
|
4417
4499
|
}
|
|
4418
4500
|
/** `<home>/.claude/plugins/cache/<mkt>/` */
|
|
4419
4501
|
marketplaceCacheDir(marketplace) {
|
|
4420
|
-
return
|
|
4502
|
+
return resolve10(this.cacheDir, marketplace);
|
|
4421
4503
|
}
|
|
4422
4504
|
/** `<home>/.claude/plugins/cache/<mkt>/<plugin>/` */
|
|
4423
4505
|
pluginCacheDir(ref) {
|
|
4424
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 ?
|
|
4771
|
-
if (resolvedTarget && resolvedTarget ===
|
|
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 =
|
|
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
|
|
5555
|
+
basename as basename3,
|
|
5474
5556
|
dirname as dirname6,
|
|
5475
5557
|
extname,
|
|
5476
5558
|
relative as pathRelative,
|
|
5477
|
-
resolve as
|
|
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 =
|
|
6200
|
+
const destPath = resolve12(versionDir, srcMcp);
|
|
6119
6201
|
files.push(destPath);
|
|
6120
|
-
const srcPath =
|
|
6121
|
-
const rel = pathRelative(
|
|
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 =
|
|
6267
|
-
const rel = pathRelative(
|
|
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 =
|
|
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 =
|
|
6371
|
+
const pluginBase = basename3(input.pluginDir);
|
|
6290
6372
|
await mkdir7(pluginParent, { recursive: true });
|
|
6291
6373
|
const tmpPluginDir = await mkdtemp(
|
|
6292
|
-
|
|
6374
|
+
resolve12(pluginParent, `.${pluginBase}.tmp-`)
|
|
6293
6375
|
);
|
|
6294
6376
|
const backupPluginDir = await mkdtemp(
|
|
6295
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
6514
|
+
sourceDir: resolve12(nativeSkillsDir, name),
|
|
6433
6515
|
skillName: name
|
|
6434
6516
|
});
|
|
6435
6517
|
}
|
|
6436
6518
|
} else {
|
|
6437
|
-
const dotSkillsDir =
|
|
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,
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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 ??
|
|
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:
|
|
6651
|
-
await
|
|
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 =
|
|
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 =
|
|
6700
|
-
const manifestPath =
|
|
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
|
|
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
|
|
6942
|
+
import { resolve as resolve13 } from "path";
|
|
6861
6943
|
var CURSOR_RELATIVE_DIR = ".cursor";
|
|
6862
6944
|
function cursorDir(home) {
|
|
6863
|
-
return
|
|
6945
|
+
return resolve13(home, CURSOR_RELATIVE_DIR);
|
|
6864
6946
|
}
|
|
6865
6947
|
function cursorMcpJsonPath(home) {
|
|
6866
|
-
return
|
|
6948
|
+
return resolve13(cursorDir(home), "mcp.json");
|
|
6867
6949
|
}
|
|
6868
6950
|
function cursorRulesDir(home) {
|
|
6869
|
-
return
|
|
6951
|
+
return resolve13(cursorDir(home), "rules");
|
|
6870
6952
|
}
|
|
6871
6953
|
function cursorRuleMdcPath(home, ruleName) {
|
|
6872
|
-
return
|
|
6954
|
+
return resolve13(cursorRulesDir(home), `${ruleName}.mdc`);
|
|
6873
6955
|
}
|
|
6874
6956
|
function cursorSkillsDir(home) {
|
|
6875
|
-
return
|
|
6957
|
+
return resolve13(cursorDir(home), "skills");
|
|
6876
6958
|
}
|
|
6877
6959
|
function cursorSkillDir(home, skillName) {
|
|
6878
|
-
return
|
|
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
|
|
7071
|
+
import { resolve as resolve14 } from "path";
|
|
6990
7072
|
async function readSkillDescription(skillSourceDir) {
|
|
6991
|
-
const skillMdPath =
|
|
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 =
|
|
7436
|
+
const skillsSourceDir = resolve15(plugin.sourceDir, "skills");
|
|
7355
7437
|
const skillDirs = await listSkillDirs(skillsSourceDir);
|
|
7356
7438
|
for (const skillName of skillDirs) {
|
|
7357
|
-
const srcDir =
|
|
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 =
|
|
7471
|
+
const rulesSourceDir = resolve15(plugin.sourceDir, "rules");
|
|
7390
7472
|
const rulesFiles = await listRuleMdFiles(rulesSourceDir);
|
|
7391
7473
|
for (const ruleFile of rulesFiles) {
|
|
7392
|
-
const srcPath =
|
|
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:
|
|
7595
|
-
await
|
|
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
|
|
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
|
|
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 :
|
|
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 =
|
|
7677
|
-
const legacyAssetsDir =
|
|
7678
|
-
const skillSymlinkPath =
|
|
7679
|
-
const settingsJsonPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
7960
|
+
return dirHasFileWithExt(resolve19(sourceDir, "commands"), ".md");
|
|
7876
7961
|
}
|
|
7877
7962
|
async function hasSkills(sourceDir) {
|
|
7878
|
-
const skillsDir =
|
|
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 =
|
|
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(
|
|
7982
|
+
return dirHasFileWithExt(resolve19(sourceDir, "rules"), ".md");
|
|
7898
7983
|
}
|
|
7899
7984
|
async function hasHooks(sourceDir) {
|
|
7900
|
-
const hooksDir =
|
|
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 =
|
|
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 =
|
|
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
|
|
8035
|
-
constructor(pluginName,
|
|
8119
|
+
var PluginCloneFailedError = class extends PluginResolverError {
|
|
8120
|
+
constructor(pluginName, marketplaceName, cause) {
|
|
8036
8121
|
super(
|
|
8037
|
-
`plugin
|
|
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.
|
|
8041
|
-
this.
|
|
8042
|
-
this.name = "
|
|
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
|
|
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 =
|
|
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
|
-
|
|
8085
|
-
|
|
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 ??
|
|
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 =
|
|
8841
|
-
const manifestPath =
|
|
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}
|
|
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 {
|
|
9711
|
-
import {
|
|
9712
|
-
var SKILL_DESCRIPTION = "
|
|
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
|
-
$
|
|
9716
|
-
$
|
|
9717
|
-
$
|
|
9718
|
-
$
|
|
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
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
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
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
const
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
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
|
|
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
|
|
9740
|
-
return
|
|
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 [
|
|
9744
|
-
const
|
|
9745
|
-
if (
|
|
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
|
|
9753
|
-
if (
|
|
9754
|
-
|
|
10063
|
+
const stripped = stripRushOnlyFlags(initialArgs);
|
|
10064
|
+
if (stripped.args.length === 0) {
|
|
10065
|
+
command.help();
|
|
9755
10066
|
return;
|
|
9756
10067
|
}
|
|
9757
|
-
const
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
11951
|
-
import { homedir as
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
12033
|
-
` ${chalk7.cyan("rush-ai
|
|
12034
|
-
` ${chalk7.cyan("rush-ai
|
|
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 {
|