dimcode-darwin-x64 0.0.78 → 0.0.79-beta.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/bin/dimcode
CHANGED
|
Binary file
|
|
@@ -14,7 +14,8 @@ import { stripVTControlCharacters } from "node:util";
|
|
|
14
14
|
import { homedir, platform, tmpdir } from "os";
|
|
15
15
|
import * as readline from "readline";
|
|
16
16
|
import { Writable } from "stream";
|
|
17
|
-
import {
|
|
17
|
+
import { promisify } from "util";
|
|
18
|
+
import { execFile, execSync, spawn, spawnSync } from "child_process";
|
|
18
19
|
import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
|
|
19
20
|
import { parse } from "yaml";
|
|
20
21
|
import { createHash } from "crypto";
|
|
@@ -319,6 +320,7 @@ async function searchMultiselect(options) {
|
|
|
319
320
|
const lockedTitle = `${import_picocolors.default.bold(lockedSection.title)} ${import_picocolors.default.dim("── always included")}`;
|
|
320
321
|
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${lockedTitle} ${S_BAR_H.repeat(12)}`);
|
|
321
322
|
for (const item of lockedSection.items) lines.push(`${S_BAR} ${S_BULLET} ${import_picocolors.default.bold(item.label)}`);
|
|
323
|
+
if (lockedSection.hiddenCount && lockedSection.hiddenCount > 0) lines.push(`${S_BAR} ${import_picocolors.default.dim(`...and ${lockedSection.hiddenCount} more`)}`);
|
|
322
324
|
lines.push(`${S_BAR}`);
|
|
323
325
|
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("Additional agents")} ${S_BAR_H.repeat(29)}`);
|
|
324
326
|
}
|
|
@@ -434,6 +436,7 @@ const CLONE_TIMEOUT_MS = (() => {
|
|
|
434
436
|
const parsed = Number.parseInt(raw, 10);
|
|
435
437
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CLONE_TIMEOUT_MS;
|
|
436
438
|
})();
|
|
439
|
+
const execFileAsync = promisify(execFile);
|
|
437
440
|
var GitCloneError = class extends Error {
|
|
438
441
|
url;
|
|
439
442
|
isTimeout;
|
|
@@ -446,14 +449,58 @@ var GitCloneError = class extends Error {
|
|
|
446
449
|
this.isAuthError = isAuthError;
|
|
447
450
|
}
|
|
448
451
|
};
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
+
function parseGitHubRepoUrl(url) {
|
|
453
|
+
const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
454
|
+
if (sshMatch) {
|
|
455
|
+
const owner = sshMatch[1];
|
|
456
|
+
const repo = sshMatch[2];
|
|
457
|
+
return {
|
|
458
|
+
owner,
|
|
459
|
+
repo,
|
|
460
|
+
slug: `${owner}/${repo}`,
|
|
461
|
+
sshUrl: `git@github.com:${owner}/${repo}.git`
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
const parsed = new URL(url);
|
|
466
|
+
if (parsed.hostname !== "github.com") return null;
|
|
467
|
+
const match = parsed.pathname.match(/^\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
468
|
+
if (!match) return null;
|
|
469
|
+
const owner = match[1];
|
|
470
|
+
const repo = match[2];
|
|
471
|
+
return {
|
|
472
|
+
owner,
|
|
473
|
+
repo,
|
|
474
|
+
slug: `${owner}/${repo}`,
|
|
475
|
+
sshUrl: `git@github.com:${owner}/${repo}.git`
|
|
476
|
+
};
|
|
477
|
+
} catch {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function isGitHubHttpsCloneUrl(url) {
|
|
482
|
+
try {
|
|
483
|
+
const parsed = new URL(url);
|
|
484
|
+
return parsed.protocol === "https:" && parsed.hostname === "github.com";
|
|
485
|
+
} catch {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function isGitHubSsoAuthError(message) {
|
|
490
|
+
const lower = message.toLowerCase();
|
|
491
|
+
return lower.includes("saml sso") || lower.includes("enforced sso") || lower.includes("enabled or enforced saml") || lower.includes("re-authorize the oauth application");
|
|
492
|
+
}
|
|
493
|
+
function isAuthFailure(message) {
|
|
494
|
+
return message.includes("Authentication failed") || message.includes("could not read Username") || message.includes("Permission denied") || message.includes("Repository not found") || message.includes("requested URL returned error: 403") || isGitHubSsoAuthError(message);
|
|
495
|
+
}
|
|
496
|
+
function createGitClient(extraEnv) {
|
|
497
|
+
return esm_default({
|
|
452
498
|
timeout: { block: CLONE_TIMEOUT_MS },
|
|
453
499
|
env: {
|
|
454
500
|
...process.env,
|
|
455
501
|
GIT_TERMINAL_PROMPT: "0",
|
|
456
|
-
GIT_LFS_SKIP_SMUDGE: "1"
|
|
502
|
+
GIT_LFS_SKIP_SMUDGE: "1",
|
|
503
|
+
...extraEnv
|
|
457
504
|
},
|
|
458
505
|
config: [
|
|
459
506
|
"filter.lfs.required=false",
|
|
@@ -462,25 +509,98 @@ async function cloneRepo(url, ref) {
|
|
|
462
509
|
"filter.lfs.process="
|
|
463
510
|
]
|
|
464
511
|
});
|
|
512
|
+
}
|
|
513
|
+
async function resetTempDir(dir) {
|
|
514
|
+
await rm(dir, {
|
|
515
|
+
recursive: true,
|
|
516
|
+
force: true
|
|
517
|
+
}).catch(() => {});
|
|
518
|
+
await mkdir(dir, { recursive: true });
|
|
519
|
+
}
|
|
520
|
+
async function tryGhClone(repo, tempDir, ref) {
|
|
521
|
+
let cloneTarget = repo.slug;
|
|
522
|
+
try {
|
|
523
|
+
const { stdout, stderr } = await execFileAsync("gh", [
|
|
524
|
+
"auth",
|
|
525
|
+
"status",
|
|
526
|
+
"-h",
|
|
527
|
+
"github.com"
|
|
528
|
+
], {
|
|
529
|
+
timeout: 5e3,
|
|
530
|
+
env: {
|
|
531
|
+
...process.env,
|
|
532
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
const statusOutput = `${stdout}${stderr}`;
|
|
536
|
+
if (/Git operations protocol:\s+ssh/i.test(statusOutput)) cloneTarget = repo.sshUrl;
|
|
537
|
+
} catch {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
await execFileAsync("gh", [
|
|
541
|
+
"repo",
|
|
542
|
+
"clone",
|
|
543
|
+
cloneTarget,
|
|
544
|
+
tempDir,
|
|
545
|
+
"--",
|
|
546
|
+
...ref ? [
|
|
547
|
+
"--depth=1",
|
|
548
|
+
"--branch",
|
|
549
|
+
ref
|
|
550
|
+
] : ["--depth=1"]
|
|
551
|
+
], {
|
|
552
|
+
timeout: CLONE_TIMEOUT_MS,
|
|
553
|
+
env: {
|
|
554
|
+
...process.env,
|
|
555
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
function buildGitHubAuthError(url, repo, message) {
|
|
561
|
+
if (repo && isGitHubSsoAuthError(message)) return `GitHub blocked HTTPS access to ${url} because the organization enforces SAML SSO.\n skills tried your existing git credentials and available fallbacks, but none succeeded.\n - Re-authorize your GitHub credentials/app for that org's SSO policy\n - Or rerun with SSH: npx skills add ${repo.sshUrl}\n - Verify access with: gh auth status -h github.com or ssh -T git@github.com`;
|
|
562
|
+
if (repo) return `Authentication failed for ${url}.\n - For private repos, ensure you have access\n - Retry with SSH: npx skills add ${repo.sshUrl}\n - Check access with: gh auth status -h github.com or ssh -T git@github.com`;
|
|
563
|
+
return `Authentication failed for ${url}.\n - For private repos, ensure you have access\n - For SSH: Check your keys with 'ssh -T git@github.com'\n - For HTTPS: Run 'gh auth login' or configure git credentials`;
|
|
564
|
+
}
|
|
565
|
+
async function cloneRepo(url, ref) {
|
|
566
|
+
const tempDir = await mkdtemp(join(tmpdir(), "skills-"));
|
|
465
567
|
const cloneOptions = ref ? [
|
|
466
568
|
"--depth",
|
|
467
569
|
"1",
|
|
468
570
|
"--branch",
|
|
469
571
|
ref
|
|
470
572
|
] : ["--depth", "1"];
|
|
573
|
+
const repo = parseGitHubRepoUrl(url);
|
|
471
574
|
try {
|
|
472
|
-
await
|
|
575
|
+
await createGitClient().clone(url, tempDir, cloneOptions);
|
|
473
576
|
return tempDir;
|
|
474
577
|
} catch (error) {
|
|
578
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
579
|
+
const isTimeout = errorMessage.includes("block timeout") || errorMessage.includes("timed out");
|
|
580
|
+
const isAuthError = isAuthFailure(errorMessage);
|
|
581
|
+
if (isTimeout) {
|
|
582
|
+
await rm(tempDir, {
|
|
583
|
+
recursive: true,
|
|
584
|
+
force: true
|
|
585
|
+
}).catch(() => {});
|
|
586
|
+
throw new GitCloneError(`Clone timed out after ${Math.round(CLONE_TIMEOUT_MS / 1e3)}s. Common causes:\n - Large repository: raise the timeout with SKILLS_CLONE_TIMEOUT_MS=600000 (10m)\n - Slow network: retry, or clone manually and pass the local path to 'skills add'\n - Private repo without credentials: ensure auth is configured\n - For SSH: ssh-add -l (to check loaded keys)\n - For HTTPS: gh auth status (if using GitHub CLI)`, url, true, false);
|
|
587
|
+
}
|
|
588
|
+
if (isAuthError && repo && isGitHubHttpsCloneUrl(url)) {
|
|
589
|
+
try {
|
|
590
|
+
await resetTempDir(tempDir);
|
|
591
|
+
if (await tryGhClone(repo, tempDir, ref)) return tempDir;
|
|
592
|
+
} catch {}
|
|
593
|
+
try {
|
|
594
|
+
await resetTempDir(tempDir);
|
|
595
|
+
await createGitClient({ GIT_SSH_COMMAND: process.env.GIT_SSH_COMMAND ?? "ssh -o BatchMode=yes" }).clone(repo.sshUrl, tempDir, cloneOptions);
|
|
596
|
+
return tempDir;
|
|
597
|
+
} catch {}
|
|
598
|
+
}
|
|
475
599
|
await rm(tempDir, {
|
|
476
600
|
recursive: true,
|
|
477
601
|
force: true
|
|
478
602
|
}).catch(() => {});
|
|
479
|
-
|
|
480
|
-
const isTimeout = errorMessage.includes("block timeout") || errorMessage.includes("timed out");
|
|
481
|
-
const isAuthError = errorMessage.includes("Authentication failed") || errorMessage.includes("could not read Username") || errorMessage.includes("Permission denied") || errorMessage.includes("Repository not found");
|
|
482
|
-
if (isTimeout) throw new GitCloneError(`Clone timed out after ${Math.round(CLONE_TIMEOUT_MS / 1e3)}s. Common causes:\n - Large repository: raise the timeout with SKILLS_CLONE_TIMEOUT_MS=600000 (10m)\n - Slow network: retry, or clone manually and pass the local path to 'skills add'\n - Private repo without credentials: ensure auth is configured\n - For SSH: ssh-add -l (to check loaded keys)\n - For HTTPS: gh auth status (if using GitHub CLI)`, url, true, false);
|
|
483
|
-
if (isAuthError) throw new GitCloneError(`Authentication failed for ${url}.\n - For private repos, ensure you have access\n - For SSH: Check your keys with 'ssh -T git@github.com'\n - For HTTPS: Run 'gh auth login' or configure git credentials`, url, false, true);
|
|
603
|
+
if (isAuthError) throw new GitCloneError(buildGitHubAuthError(url, repo, errorMessage), url, false, true);
|
|
484
604
|
throw new GitCloneError(`Failed to clone ${url}: ${errorMessage}`, url, false, false);
|
|
485
605
|
}
|
|
486
606
|
}
|
|
@@ -818,6 +938,8 @@ const configHome = xdgConfig ?? join(home, ".config");
|
|
|
818
938
|
const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
|
|
819
939
|
const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
|
|
820
940
|
const vibeHome = process.env.VIBE_HOME?.trim() || join(home, ".vibe");
|
|
941
|
+
const hermesHome = process.env.HERMES_HOME?.trim() || join(home, ".hermes");
|
|
942
|
+
const autohandHome = process.env.AUTOHAND_HOME?.trim() || join(home, ".autohand");
|
|
821
943
|
const zedAppDataHome = process.env.APPDATA?.trim();
|
|
822
944
|
const zedFlatpakConfigHome = process.env.FLATPAK_XDG_CONFIG_HOME?.trim();
|
|
823
945
|
function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
|
|
@@ -854,6 +976,33 @@ const agents = {
|
|
|
854
976
|
return existsSync(join(home, ".gemini/antigravity"));
|
|
855
977
|
}
|
|
856
978
|
},
|
|
979
|
+
"antigravity-cli": {
|
|
980
|
+
name: "antigravity-cli",
|
|
981
|
+
displayName: "Antigravity CLI",
|
|
982
|
+
skillsDir: ".agents/skills",
|
|
983
|
+
globalSkillsDir: join(home, ".gemini/antigravity-cli/skills"),
|
|
984
|
+
detectInstalled: async () => {
|
|
985
|
+
return existsSync(join(home, ".gemini/antigravity-cli"));
|
|
986
|
+
}
|
|
987
|
+
},
|
|
988
|
+
astrbot: {
|
|
989
|
+
name: "astrbot",
|
|
990
|
+
displayName: "AstrBot",
|
|
991
|
+
skillsDir: "data/skills",
|
|
992
|
+
globalSkillsDir: join(home, ".astrbot/data/skills"),
|
|
993
|
+
detectInstalled: async () => {
|
|
994
|
+
return existsSync(join(process.cwd(), "data/skills")) || existsSync(join(home, ".astrbot"));
|
|
995
|
+
}
|
|
996
|
+
},
|
|
997
|
+
"autohand-code": {
|
|
998
|
+
name: "autohand-code",
|
|
999
|
+
displayName: "Autohand Code CLI",
|
|
1000
|
+
skillsDir: ".autohand/skills",
|
|
1001
|
+
globalSkillsDir: join(autohandHome, "skills"),
|
|
1002
|
+
detectInstalled: async () => {
|
|
1003
|
+
return existsSync(autohandHome);
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
857
1006
|
augment: {
|
|
858
1007
|
name: "augment",
|
|
859
1008
|
displayName: "Augment",
|
|
@@ -1012,6 +1161,7 @@ const agents = {
|
|
|
1012
1161
|
displayName: "Dexto",
|
|
1013
1162
|
skillsDir: ".agents/skills",
|
|
1014
1163
|
globalSkillsDir: join(home, ".agents/skills"),
|
|
1164
|
+
showInUniversalPrompt: false,
|
|
1015
1165
|
detectInstalled: async () => {
|
|
1016
1166
|
return existsSync(join(home, ".dexto"));
|
|
1017
1167
|
}
|
|
@@ -1030,6 +1180,7 @@ const agents = {
|
|
|
1030
1180
|
displayName: "Firebender",
|
|
1031
1181
|
skillsDir: ".agents/skills",
|
|
1032
1182
|
globalSkillsDir: join(home, ".firebender/skills"),
|
|
1183
|
+
showInUniversalPrompt: false,
|
|
1033
1184
|
detectInstalled: async () => {
|
|
1034
1185
|
return existsSync(join(home, ".firebender"));
|
|
1035
1186
|
}
|
|
@@ -1074,9 +1225,27 @@ const agents = {
|
|
|
1074
1225
|
name: "hermes-agent",
|
|
1075
1226
|
displayName: "Hermes Agent",
|
|
1076
1227
|
skillsDir: ".hermes/skills",
|
|
1077
|
-
globalSkillsDir: join(
|
|
1228
|
+
globalSkillsDir: join(hermesHome, "skills"),
|
|
1078
1229
|
detectInstalled: async () => {
|
|
1079
|
-
return existsSync(
|
|
1230
|
+
return existsSync(hermesHome);
|
|
1231
|
+
}
|
|
1232
|
+
},
|
|
1233
|
+
"inference-sh": {
|
|
1234
|
+
name: "inference-sh",
|
|
1235
|
+
displayName: "inference.sh",
|
|
1236
|
+
skillsDir: ".inferencesh/skills",
|
|
1237
|
+
globalSkillsDir: join(home, ".inferencesh/skills"),
|
|
1238
|
+
detectInstalled: async () => {
|
|
1239
|
+
return existsSync(join(home, ".inferencesh"));
|
|
1240
|
+
}
|
|
1241
|
+
},
|
|
1242
|
+
jazz: {
|
|
1243
|
+
name: "jazz",
|
|
1244
|
+
displayName: "Jazz",
|
|
1245
|
+
skillsDir: ".jazz/skills",
|
|
1246
|
+
globalSkillsDir: join(home, ".jazz/skills"),
|
|
1247
|
+
detectInstalled: async () => {
|
|
1248
|
+
return existsSync(join(home, ".jazz")) || existsSync(join(process.cwd(), ".jazz"));
|
|
1080
1249
|
}
|
|
1081
1250
|
},
|
|
1082
1251
|
junie: {
|
|
@@ -1106,13 +1275,13 @@ const agents = {
|
|
|
1106
1275
|
return existsSync(join(home, ".kilocode"));
|
|
1107
1276
|
}
|
|
1108
1277
|
},
|
|
1109
|
-
"kimi-cli": {
|
|
1110
|
-
name: "kimi-cli",
|
|
1278
|
+
"kimi-code-cli": {
|
|
1279
|
+
name: "kimi-code-cli",
|
|
1111
1280
|
displayName: "Kimi Code CLI",
|
|
1112
1281
|
skillsDir: ".agents/skills",
|
|
1113
|
-
globalSkillsDir: join(home, ".
|
|
1282
|
+
globalSkillsDir: join(home, ".agents/skills"),
|
|
1114
1283
|
detectInstalled: async () => {
|
|
1115
|
-
return existsSync(join(home, ".kimi"));
|
|
1284
|
+
return existsSync(join(home, ".kimi-code")) || existsSync(join(home, ".kimi"));
|
|
1116
1285
|
}
|
|
1117
1286
|
},
|
|
1118
1287
|
"kiro-cli": {
|
|
@@ -1133,6 +1302,25 @@ const agents = {
|
|
|
1133
1302
|
return existsSync(join(home, ".kode"));
|
|
1134
1303
|
}
|
|
1135
1304
|
},
|
|
1305
|
+
lingma: {
|
|
1306
|
+
name: "lingma",
|
|
1307
|
+
displayName: "Lingma",
|
|
1308
|
+
skillsDir: ".lingma/skills",
|
|
1309
|
+
globalSkillsDir: join(home, ".lingma/skills"),
|
|
1310
|
+
detectInstalled: async () => {
|
|
1311
|
+
return existsSync(join(home, ".lingma"));
|
|
1312
|
+
}
|
|
1313
|
+
},
|
|
1314
|
+
loaf: {
|
|
1315
|
+
name: "loaf",
|
|
1316
|
+
displayName: "Loaf",
|
|
1317
|
+
skillsDir: ".agents/skills",
|
|
1318
|
+
globalSkillsDir: join(home, ".agents/skills"),
|
|
1319
|
+
showInUniversalPrompt: false,
|
|
1320
|
+
detectInstalled: async () => {
|
|
1321
|
+
return existsSync(join(home, ".loaf"));
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1136
1324
|
mcpjam: {
|
|
1137
1325
|
name: "mcpjam",
|
|
1138
1326
|
displayName: "MCPJam",
|
|
@@ -1151,6 +1339,15 @@ const agents = {
|
|
|
1151
1339
|
return existsSync(vibeHome);
|
|
1152
1340
|
}
|
|
1153
1341
|
},
|
|
1342
|
+
moxby: {
|
|
1343
|
+
name: "moxby",
|
|
1344
|
+
displayName: "Moxby",
|
|
1345
|
+
skillsDir: ".moxby/skills",
|
|
1346
|
+
globalSkillsDir: join(home, ".moxby/skills"),
|
|
1347
|
+
detectInstalled: async () => {
|
|
1348
|
+
return existsSync(join(home, ".moxby"));
|
|
1349
|
+
}
|
|
1350
|
+
},
|
|
1154
1351
|
mux: {
|
|
1155
1352
|
name: "mux",
|
|
1156
1353
|
displayName: "Mux",
|
|
@@ -1178,6 +1375,15 @@ const agents = {
|
|
|
1178
1375
|
return existsSync(join(home, ".openhands"));
|
|
1179
1376
|
}
|
|
1180
1377
|
},
|
|
1378
|
+
ona: {
|
|
1379
|
+
name: "ona",
|
|
1380
|
+
displayName: "Ona",
|
|
1381
|
+
skillsDir: ".ona/skills",
|
|
1382
|
+
globalSkillsDir: join(home, ".ona/skills"),
|
|
1383
|
+
detectInstalled: async () => {
|
|
1384
|
+
return existsSync(join(home, ".ona"));
|
|
1385
|
+
}
|
|
1386
|
+
},
|
|
1181
1387
|
pi: {
|
|
1182
1388
|
name: "pi",
|
|
1183
1389
|
displayName: "Pi",
|
|
@@ -1196,6 +1402,15 @@ const agents = {
|
|
|
1196
1402
|
return existsSync(join(home, ".qoder"));
|
|
1197
1403
|
}
|
|
1198
1404
|
},
|
|
1405
|
+
"qoder-cn": {
|
|
1406
|
+
name: "qoder-cn",
|
|
1407
|
+
displayName: "Qoder CN",
|
|
1408
|
+
skillsDir: ".qoder/skills",
|
|
1409
|
+
globalSkillsDir: join(home, ".qoder-cn/skills"),
|
|
1410
|
+
detectInstalled: async () => {
|
|
1411
|
+
return existsSync(join(home, ".qoder-cn"));
|
|
1412
|
+
}
|
|
1413
|
+
},
|
|
1199
1414
|
"qwen-code": {
|
|
1200
1415
|
name: "qwen-code",
|
|
1201
1416
|
displayName: "Qwen Code",
|
|
@@ -1215,6 +1430,15 @@ const agents = {
|
|
|
1215
1430
|
return existsSync(join(process.cwd(), ".replit"));
|
|
1216
1431
|
}
|
|
1217
1432
|
},
|
|
1433
|
+
reasonix: {
|
|
1434
|
+
name: "reasonix",
|
|
1435
|
+
displayName: "Reasonix",
|
|
1436
|
+
skillsDir: ".reasonix/skills",
|
|
1437
|
+
globalSkillsDir: join(home, ".reasonix/skills"),
|
|
1438
|
+
detectInstalled: async () => {
|
|
1439
|
+
return existsSync(join(home, ".reasonix"));
|
|
1440
|
+
}
|
|
1441
|
+
},
|
|
1218
1442
|
rovodev: {
|
|
1219
1443
|
name: "rovodev",
|
|
1220
1444
|
displayName: "Rovo Dev",
|
|
@@ -1242,6 +1466,24 @@ const agents = {
|
|
|
1242
1466
|
return existsSync(join(home, ".tabnine"));
|
|
1243
1467
|
}
|
|
1244
1468
|
},
|
|
1469
|
+
terramind: {
|
|
1470
|
+
name: "terramind",
|
|
1471
|
+
displayName: "Terramind",
|
|
1472
|
+
skillsDir: ".terramind/skills",
|
|
1473
|
+
globalSkillsDir: join(home, ".terramind/skills"),
|
|
1474
|
+
detectInstalled: async () => {
|
|
1475
|
+
return existsSync(join(home, ".terramind"));
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
tinycloud: {
|
|
1479
|
+
name: "tinycloud",
|
|
1480
|
+
displayName: "Tinycloud",
|
|
1481
|
+
skillsDir: ".tinycloud/skills",
|
|
1482
|
+
globalSkillsDir: join(home, ".tinycloud/skills"),
|
|
1483
|
+
detectInstalled: async () => {
|
|
1484
|
+
return existsSync(join(home, ".tinycloud"));
|
|
1485
|
+
}
|
|
1486
|
+
},
|
|
1245
1487
|
trae: {
|
|
1246
1488
|
name: "trae",
|
|
1247
1489
|
displayName: "Trae",
|
|
@@ -1296,6 +1538,15 @@ const agents = {
|
|
|
1296
1538
|
return existsSync(join(home, ".zencoder"));
|
|
1297
1539
|
}
|
|
1298
1540
|
},
|
|
1541
|
+
zenflow: {
|
|
1542
|
+
name: "zenflow",
|
|
1543
|
+
displayName: "Zenflow",
|
|
1544
|
+
skillsDir: ".zencoder/skills",
|
|
1545
|
+
globalSkillsDir: join(home, ".zencoder/skills"),
|
|
1546
|
+
detectInstalled: async () => {
|
|
1547
|
+
return existsSync(join(home, ".zencoder"));
|
|
1548
|
+
}
|
|
1549
|
+
},
|
|
1299
1550
|
neovate: {
|
|
1300
1551
|
name: "neovate",
|
|
1301
1552
|
displayName: "Neovate",
|
|
@@ -1314,6 +1565,16 @@ const agents = {
|
|
|
1314
1565
|
return existsSync(join(home, ".pochi"));
|
|
1315
1566
|
}
|
|
1316
1567
|
},
|
|
1568
|
+
promptscript: {
|
|
1569
|
+
name: "promptscript",
|
|
1570
|
+
displayName: "PromptScript",
|
|
1571
|
+
skillsDir: ".agents/skills",
|
|
1572
|
+
globalSkillsDir: void 0,
|
|
1573
|
+
showInUniversalPrompt: false,
|
|
1574
|
+
detectInstalled: async () => {
|
|
1575
|
+
return existsSync(join(process.cwd(), ".promptscript")) || existsSync(join(process.cwd(), "promptscript.yaml"));
|
|
1576
|
+
}
|
|
1577
|
+
},
|
|
1317
1578
|
adal: {
|
|
1318
1579
|
name: "adal",
|
|
1319
1580
|
displayName: "AdaL",
|
|
@@ -1341,6 +1602,9 @@ async function detectInstalledAgents() {
|
|
|
1341
1602
|
function getUniversalAgents() {
|
|
1342
1603
|
return Object.entries(agents).filter(([_, config]) => config.skillsDir === ".agents/skills" && config.showInUniversalList !== false).map(([type]) => type);
|
|
1343
1604
|
}
|
|
1605
|
+
function getVisibleUniversalAgents() {
|
|
1606
|
+
return Object.entries(agents).filter(([_, config]) => config.skillsDir === ".agents/skills" && config.showInUniversalList !== false && config.showInUniversalPrompt !== false).map(([type]) => type);
|
|
1607
|
+
}
|
|
1344
1608
|
function getNonUniversalAgents() {
|
|
1345
1609
|
return Object.entries(agents).filter(([_, config]) => config.skillsDir !== ".agents/skills").map(([type]) => type);
|
|
1346
1610
|
}
|
|
@@ -1352,7 +1616,7 @@ const SKILLS_SUBDIR = "skills";
|
|
|
1352
1616
|
function sanitizeName(name) {
|
|
1353
1617
|
return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").substring(0, 255) || "unnamed-skill";
|
|
1354
1618
|
}
|
|
1355
|
-
function isPathSafe(basePath, targetPath) {
|
|
1619
|
+
function isPathSafe$1(basePath, targetPath) {
|
|
1356
1620
|
const normalizedBase = normalize(resolve(basePath));
|
|
1357
1621
|
const normalizedTarget = normalize(resolve(targetPath));
|
|
1358
1622
|
return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
|
|
@@ -1442,13 +1706,13 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
1442
1706
|
const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
|
|
1443
1707
|
const agentDir = join(agentBase, skillName);
|
|
1444
1708
|
const installMode = options.mode ?? "symlink";
|
|
1445
|
-
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1709
|
+
if (!isPathSafe$1(canonicalBase, canonicalDir)) return {
|
|
1446
1710
|
success: false,
|
|
1447
1711
|
path: agentDir,
|
|
1448
1712
|
mode: installMode,
|
|
1449
1713
|
error: "Invalid skill name: potential path traversal detected"
|
|
1450
1714
|
};
|
|
1451
|
-
if (!isPathSafe(agentBase, agentDir)) return {
|
|
1715
|
+
if (!isPathSafe$1(agentBase, agentDir)) return {
|
|
1452
1716
|
success: false,
|
|
1453
1717
|
path: agentDir,
|
|
1454
1718
|
mode: installMode,
|
|
@@ -1507,21 +1771,21 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
1507
1771
|
};
|
|
1508
1772
|
}
|
|
1509
1773
|
}
|
|
1510
|
-
const EXCLUDE_FILES = new Set(["metadata.json"]);
|
|
1511
|
-
const EXCLUDE_DIRS = new Set([
|
|
1774
|
+
const EXCLUDE_FILES$1 = new Set(["metadata.json"]);
|
|
1775
|
+
const EXCLUDE_DIRS$1 = new Set([
|
|
1512
1776
|
".git",
|
|
1513
1777
|
"__pycache__",
|
|
1514
1778
|
"__pypackages__"
|
|
1515
1779
|
]);
|
|
1516
|
-
const isExcluded = (name, isDirectory = false) => {
|
|
1517
|
-
if (EXCLUDE_FILES.has(name)) return true;
|
|
1518
|
-
if (isDirectory && EXCLUDE_DIRS.has(name)) return true;
|
|
1780
|
+
const isExcluded$1 = (name, isDirectory = false) => {
|
|
1781
|
+
if (EXCLUDE_FILES$1.has(name)) return true;
|
|
1782
|
+
if (isDirectory && EXCLUDE_DIRS$1.has(name)) return true;
|
|
1519
1783
|
return false;
|
|
1520
1784
|
};
|
|
1521
1785
|
async function copyDirectory(src, dest) {
|
|
1522
1786
|
await mkdir(dest, { recursive: true });
|
|
1523
1787
|
const entries = await readdir(src, { withFileTypes: true });
|
|
1524
|
-
await Promise.all(entries.filter((entry) => !isExcluded(entry.name, entry.isDirectory())).map(async (entry) => {
|
|
1788
|
+
await Promise.all(entries.filter((entry) => !isExcluded$1(entry.name, entry.isDirectory())).map(async (entry) => {
|
|
1525
1789
|
const srcPath = join(src, entry.name);
|
|
1526
1790
|
const destPath = join(dest, entry.name);
|
|
1527
1791
|
if (entry.isDirectory()) await copyDirectory(srcPath, destPath);
|
|
@@ -1542,7 +1806,7 @@ async function isSkillInstalled(skillName, agentType, options = {}) {
|
|
|
1542
1806
|
if (options.global && agent.globalSkillsDir === void 0) return false;
|
|
1543
1807
|
const targetBase = options.global ? agent.globalSkillsDir : join(options.cwd || process.cwd(), agent.skillsDir);
|
|
1544
1808
|
const skillDir = join(targetBase, sanitized);
|
|
1545
|
-
if (!isPathSafe(targetBase, skillDir)) return false;
|
|
1809
|
+
if (!isPathSafe$1(targetBase, skillDir)) return false;
|
|
1546
1810
|
try {
|
|
1547
1811
|
await access(skillDir);
|
|
1548
1812
|
return true;
|
|
@@ -1556,14 +1820,14 @@ function getInstallPath(skillName, agentType, options = {}) {
|
|
|
1556
1820
|
const sanitized = sanitizeName(skillName);
|
|
1557
1821
|
const targetBase = getAgentBaseDir(agentType, options.global ?? false, options.cwd);
|
|
1558
1822
|
const installPath = join(targetBase, sanitized);
|
|
1559
|
-
if (!isPathSafe(targetBase, installPath)) throw new Error("Invalid skill name: potential path traversal detected");
|
|
1823
|
+
if (!isPathSafe$1(targetBase, installPath)) throw new Error("Invalid skill name: potential path traversal detected");
|
|
1560
1824
|
return installPath;
|
|
1561
1825
|
}
|
|
1562
1826
|
function getCanonicalPath(skillName, options = {}) {
|
|
1563
1827
|
const sanitized = sanitizeName(skillName);
|
|
1564
1828
|
const canonicalBase = getCanonicalSkillsDir(options.global ?? false, options.cwd);
|
|
1565
1829
|
const canonicalPath = join(canonicalBase, sanitized);
|
|
1566
|
-
if (!isPathSafe(canonicalBase, canonicalPath)) throw new Error("Invalid skill name: potential path traversal detected");
|
|
1830
|
+
if (!isPathSafe$1(canonicalBase, canonicalPath)) throw new Error("Invalid skill name: potential path traversal detected");
|
|
1567
1831
|
return canonicalPath;
|
|
1568
1832
|
}
|
|
1569
1833
|
async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
@@ -1582,13 +1846,13 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1582
1846
|
const canonicalDir = join(canonicalBase, skillName);
|
|
1583
1847
|
const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
|
|
1584
1848
|
const agentDir = join(agentBase, skillName);
|
|
1585
|
-
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1849
|
+
if (!isPathSafe$1(canonicalBase, canonicalDir)) return {
|
|
1586
1850
|
success: false,
|
|
1587
1851
|
path: agentDir,
|
|
1588
1852
|
mode: installMode,
|
|
1589
1853
|
error: "Invalid skill name: potential path traversal detected"
|
|
1590
1854
|
};
|
|
1591
|
-
if (!isPathSafe(agentBase, agentDir)) return {
|
|
1855
|
+
if (!isPathSafe$1(agentBase, agentDir)) return {
|
|
1592
1856
|
success: false,
|
|
1593
1857
|
path: agentDir,
|
|
1594
1858
|
mode: installMode,
|
|
@@ -1597,7 +1861,7 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1597
1861
|
async function writeSkillFiles(targetDir) {
|
|
1598
1862
|
for (const [filePath, content] of skill.files) {
|
|
1599
1863
|
const fullPath = join(targetDir, filePath);
|
|
1600
|
-
if (!isPathSafe(targetDir, fullPath)) continue;
|
|
1864
|
+
if (!isPathSafe$1(targetDir, fullPath)) continue;
|
|
1601
1865
|
const parentDir = dirname(fullPath);
|
|
1602
1866
|
if (parentDir !== targetDir) await mkdir(parentDir, { recursive: true });
|
|
1603
1867
|
await writeFile(fullPath, content);
|
|
@@ -1663,13 +1927,13 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
1663
1927
|
const canonicalDir = join(canonicalBase, skillName);
|
|
1664
1928
|
const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
|
|
1665
1929
|
const agentDir = join(agentBase, skillName);
|
|
1666
|
-
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1930
|
+
if (!isPathSafe$1(canonicalBase, canonicalDir)) return {
|
|
1667
1931
|
success: false,
|
|
1668
1932
|
path: agentDir,
|
|
1669
1933
|
mode: installMode,
|
|
1670
1934
|
error: "Invalid skill name: potential path traversal detected"
|
|
1671
1935
|
};
|
|
1672
|
-
if (!isPathSafe(agentBase, agentDir)) return {
|
|
1936
|
+
if (!isPathSafe$1(agentBase, agentDir)) return {
|
|
1673
1937
|
success: false,
|
|
1674
1938
|
path: agentDir,
|
|
1675
1939
|
mode: installMode,
|
|
@@ -1678,7 +1942,7 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
1678
1942
|
async function writeSkillFiles(targetDir) {
|
|
1679
1943
|
for (const file of skill.files) {
|
|
1680
1944
|
const fullPath = join(targetDir, file.path);
|
|
1681
|
-
if (!isPathSafe(targetDir, fullPath)) continue;
|
|
1945
|
+
if (!isPathSafe$1(targetDir, fullPath)) continue;
|
|
1682
1946
|
const parentDir = dirname(fullPath);
|
|
1683
1947
|
if (parentDir !== targetDir) await mkdir(parentDir, { recursive: true });
|
|
1684
1948
|
await writeFile(fullPath, file.contents, "utf-8");
|
|
@@ -1810,7 +2074,7 @@ async function listInstalledSkills(options = {}) {
|
|
|
1810
2074
|
for (const agentType of agentsToCheck) {
|
|
1811
2075
|
const agent = agents[agentType];
|
|
1812
2076
|
if (scope.global && agent.globalSkillsDir === void 0) continue;
|
|
1813
|
-
const agentBase = scope.global
|
|
2077
|
+
const agentBase = getAgentBaseDir(agentType, scope.global, cwd);
|
|
1814
2078
|
let found = false;
|
|
1815
2079
|
const possibleNames = Array.from(new Set([
|
|
1816
2080
|
entry.name,
|
|
@@ -1819,7 +2083,7 @@ async function listInstalledSkills(options = {}) {
|
|
|
1819
2083
|
]));
|
|
1820
2084
|
for (const possibleName of possibleNames) {
|
|
1821
2085
|
const agentSkillDir = join(agentBase, possibleName);
|
|
1822
|
-
if (!isPathSafe(agentBase, agentSkillDir)) continue;
|
|
2086
|
+
if (!isPathSafe$1(agentBase, agentSkillDir)) continue;
|
|
1823
2087
|
try {
|
|
1824
2088
|
await access(agentSkillDir);
|
|
1825
2089
|
found = true;
|
|
@@ -1831,7 +2095,7 @@ async function listInstalledSkills(options = {}) {
|
|
|
1831
2095
|
for (const agentEntry of agentEntries) {
|
|
1832
2096
|
const candidateDir = join(agentBase, agentEntry.name);
|
|
1833
2097
|
if (!await isDirEntryOrSymlinkToDir(agentEntry, candidateDir)) continue;
|
|
1834
|
-
if (!isPathSafe(agentBase, candidateDir)) continue;
|
|
2098
|
+
if (!isPathSafe$1(agentBase, candidateDir)) continue;
|
|
1835
2099
|
try {
|
|
1836
2100
|
const candidateSkillMd = join(candidateDir, "SKILL.md");
|
|
1837
2101
|
await stat(candidateSkillMd);
|
|
@@ -2754,7 +3018,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
2754
3018
|
tree
|
|
2755
3019
|
};
|
|
2756
3020
|
}
|
|
2757
|
-
var version$1 = "1.5.
|
|
3021
|
+
var version$1 = "1.5.10";
|
|
2758
3022
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
2759
3023
|
async function isSourcePrivate(source) {
|
|
2760
3024
|
const ownerRepo = parseOwnerRepo(source);
|
|
@@ -2894,13 +3158,15 @@ async function promptForAgents(message, choices) {
|
|
|
2894
3158
|
async function selectAgentsInteractive(options) {
|
|
2895
3159
|
const supportsGlobalFilter = (a) => !options.global || agents[a].globalSkillsDir;
|
|
2896
3160
|
const universalAgents = getUniversalAgents().filter(supportsGlobalFilter);
|
|
3161
|
+
const visibleUniversalAgents = getVisibleUniversalAgents().filter(supportsGlobalFilter);
|
|
2897
3162
|
const otherAgents = getNonUniversalAgents().filter(supportsGlobalFilter);
|
|
2898
3163
|
const universalSection = {
|
|
2899
3164
|
title: "Universal (.agents/skills)",
|
|
2900
|
-
items:
|
|
3165
|
+
items: visibleUniversalAgents.map((a) => ({
|
|
2901
3166
|
value: a,
|
|
2902
3167
|
label: agents[a].displayName
|
|
2903
|
-
}))
|
|
3168
|
+
})),
|
|
3169
|
+
hiddenCount: universalAgents.length - visibleUniversalAgents.length
|
|
2904
3170
|
};
|
|
2905
3171
|
const otherChoices = otherAgents.map((a) => ({
|
|
2906
3172
|
value: a,
|
|
@@ -4210,6 +4476,7 @@ async function runSync(args, options = {}) {
|
|
|
4210
4476
|
let targetAgents;
|
|
4211
4477
|
const validAgents = Object.keys(agents);
|
|
4212
4478
|
const universalAgents = getUniversalAgents();
|
|
4479
|
+
const visibleUniversalAgents = getVisibleUniversalAgents();
|
|
4213
4480
|
if (options.agent?.includes("*")) {
|
|
4214
4481
|
targetAgents = validAgents;
|
|
4215
4482
|
M.info(`Installing to all ${targetAgents.length} agents`);
|
|
@@ -4240,10 +4507,11 @@ async function runSync(args, options = {}) {
|
|
|
4240
4507
|
initialSelected: [],
|
|
4241
4508
|
lockedSection: {
|
|
4242
4509
|
title: "Universal (.agents/skills)",
|
|
4243
|
-
items:
|
|
4510
|
+
items: visibleUniversalAgents.map((a) => ({
|
|
4244
4511
|
value: a,
|
|
4245
4512
|
label: agents[a].displayName
|
|
4246
|
-
}))
|
|
4513
|
+
})),
|
|
4514
|
+
hiddenCount: universalAgents.length - visibleUniversalAgents.length
|
|
4247
4515
|
}
|
|
4248
4516
|
});
|
|
4249
4517
|
if (isCancelled(selected)) {
|
|
@@ -4266,10 +4534,11 @@ async function runSync(args, options = {}) {
|
|
|
4266
4534
|
initialSelected: installedAgents.filter((a) => !universalAgents.includes(a)),
|
|
4267
4535
|
lockedSection: {
|
|
4268
4536
|
title: "Universal (.agents/skills)",
|
|
4269
|
-
items:
|
|
4537
|
+
items: visibleUniversalAgents.map((a) => ({
|
|
4270
4538
|
value: a,
|
|
4271
4539
|
label: agents[a].displayName
|
|
4272
|
-
}))
|
|
4540
|
+
})),
|
|
4541
|
+
hiddenCount: universalAgents.length - visibleUniversalAgents.length
|
|
4273
4542
|
}
|
|
4274
4543
|
});
|
|
4275
4544
|
if (isCancelled(selected)) {
|
|
@@ -4500,13 +4769,14 @@ async function runList(args) {
|
|
|
4500
4769
|
else console.log(`${DIM$2}Try listing global skills with -g${RESET$2}`);
|
|
4501
4770
|
return;
|
|
4502
4771
|
}
|
|
4503
|
-
function printSkill(skill, indent = false) {
|
|
4772
|
+
function printSkill(skill, indent = false, maxNameLength = 0, maxPathLength = 0) {
|
|
4504
4773
|
const prefix = indent ? " " : "";
|
|
4505
4774
|
const shortPath = shortenPath(skill.canonicalPath, cwd);
|
|
4506
4775
|
const agentNames = skill.agents.map((a) => agents[a].displayName);
|
|
4507
4776
|
const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$2}`;
|
|
4508
|
-
|
|
4509
|
-
|
|
4777
|
+
const paddedName = sanitizeMetadata(skill.name).padEnd(maxNameLength);
|
|
4778
|
+
const paddedPath = shortPath.padEnd(maxPathLength);
|
|
4779
|
+
console.log(`${prefix}${CYAN}${paddedName}${RESET$2} ${DIM$2}${paddedPath}${RESET$2} ${DIM$2}Agents:${RESET$2} ${agentInfo}`);
|
|
4510
4780
|
}
|
|
4511
4781
|
console.log(`${BOLD$2}${scopeLabel} Skills${RESET$2}`);
|
|
4512
4782
|
console.log();
|
|
@@ -4526,16 +4796,42 @@ async function runList(args) {
|
|
|
4526
4796
|
const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4527
4797
|
console.log(`${BOLD$2}${title}${RESET$2}`);
|
|
4528
4798
|
const skills = groupedSkills[group];
|
|
4529
|
-
if (skills)
|
|
4799
|
+
if (skills) {
|
|
4800
|
+
let maxNameLength = 0;
|
|
4801
|
+
let maxPathLength = 0;
|
|
4802
|
+
for (const skill of skills) {
|
|
4803
|
+
const nameLength = sanitizeMetadata(skill.name).length;
|
|
4804
|
+
const pathLength = shortenPath(skill.canonicalPath, cwd).length;
|
|
4805
|
+
if (nameLength > maxNameLength) maxNameLength = nameLength;
|
|
4806
|
+
if (pathLength > maxPathLength) maxPathLength = pathLength;
|
|
4807
|
+
}
|
|
4808
|
+
for (const skill of skills) printSkill(skill, true, maxNameLength, maxPathLength);
|
|
4809
|
+
}
|
|
4530
4810
|
console.log();
|
|
4531
4811
|
}
|
|
4532
4812
|
if (ungroupedSkills.length > 0) {
|
|
4533
4813
|
console.log(`${BOLD$2}General${RESET$2}`);
|
|
4534
|
-
|
|
4814
|
+
let maxNameLength = 0;
|
|
4815
|
+
let maxPathLength = 0;
|
|
4816
|
+
for (const skill of ungroupedSkills) {
|
|
4817
|
+
const nameLength = sanitizeMetadata(skill.name).length;
|
|
4818
|
+
const pathLength = shortenPath(skill.canonicalPath, cwd).length;
|
|
4819
|
+
if (nameLength > maxNameLength) maxNameLength = nameLength;
|
|
4820
|
+
if (pathLength > maxPathLength) maxPathLength = pathLength;
|
|
4821
|
+
}
|
|
4822
|
+
for (const skill of ungroupedSkills) printSkill(skill, true, maxNameLength, maxPathLength);
|
|
4535
4823
|
console.log();
|
|
4536
4824
|
}
|
|
4537
4825
|
} else {
|
|
4538
|
-
|
|
4826
|
+
let maxNameLength = 0;
|
|
4827
|
+
let maxPathLength = 0;
|
|
4828
|
+
for (const skill of installedSkills) {
|
|
4829
|
+
const nameLength = sanitizeMetadata(skill.name).length;
|
|
4830
|
+
const pathLength = shortenPath(skill.canonicalPath, cwd).length;
|
|
4831
|
+
if (nameLength > maxNameLength) maxNameLength = nameLength;
|
|
4832
|
+
if (pathLength > maxPathLength) maxPathLength = pathLength;
|
|
4833
|
+
}
|
|
4834
|
+
for (const skill of installedSkills) printSkill(skill, false, maxNameLength, maxPathLength);
|
|
4539
4835
|
console.log();
|
|
4540
4836
|
}
|
|
4541
4837
|
}
|
|
@@ -4745,7 +5041,19 @@ function deriveSkillFolder(skillPath) {
|
|
|
4745
5041
|
if (folder.endsWith("/")) folder = folder.slice(0, -1);
|
|
4746
5042
|
return folder;
|
|
4747
5043
|
}
|
|
5044
|
+
function supportsAppendedSubpath(source) {
|
|
5045
|
+
if (source.startsWith("git@")) return false;
|
|
5046
|
+
if (source.endsWith(".git")) return false;
|
|
5047
|
+
if (source.startsWith("http://") || source.startsWith("https://")) try {
|
|
5048
|
+
const host = new URL(source).hostname;
|
|
5049
|
+
return host === "github.com" || host === "gitlab.com";
|
|
5050
|
+
} catch {
|
|
5051
|
+
return false;
|
|
5052
|
+
}
|
|
5053
|
+
return true;
|
|
5054
|
+
}
|
|
4748
5055
|
function appendFolderAndRef(source, skillPath, ref) {
|
|
5056
|
+
if (!supportsAppendedSubpath(source)) return formatSourceInput(source, ref);
|
|
4749
5057
|
const folder = deriveSkillFolder(skillPath);
|
|
4750
5058
|
const withFolder = folder ? `${source}/${folder}` : source;
|
|
4751
5059
|
return ref ? `${withFolder}#${ref}` : withFolder;
|
|
@@ -4946,13 +5254,21 @@ async function updateGlobalSkills(options = {}) {
|
|
|
4946
5254
|
}
|
|
4947
5255
|
for (const [source, itemsForSource] of bySource) {
|
|
4948
5256
|
const firstEntry = itemsForSource[0].entry;
|
|
5257
|
+
const sourceUrl = firstEntry.sourceUrl || firstEntry.source;
|
|
5258
|
+
let tempDir = null;
|
|
4949
5259
|
process.stdout.write(`\r${DIM$1}Checking skills from source: ${source}${RESET$1}\x1b[K\n`);
|
|
4950
5260
|
try {
|
|
4951
|
-
|
|
4952
|
-
|
|
5261
|
+
if (firstEntry.sourceType === "github") {
|
|
5262
|
+
const tree = await fetchRepoTree(source, firstEntry.ref, getGitHubToken);
|
|
5263
|
+
if (!tree) {
|
|
5264
|
+
console.log(` ${DIM$1}✗ Failed to fetch tree for ${source}${RESET$1}`);
|
|
5265
|
+
continue;
|
|
5266
|
+
}
|
|
4953
5267
|
const discoveredPaths = findSkillMdPaths(tree);
|
|
4954
|
-
await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
|
|
5268
|
+
const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
|
|
5269
|
+
const deletedSkillSet = new Set(deletedSkills);
|
|
4955
5270
|
for (const { name: skillName, entry } of itemsForSource) {
|
|
5271
|
+
if (deletedSkillSet.has(skillName)) continue;
|
|
4956
5272
|
const latestHash = getSkillFolderHashFromTree(tree, entry.skillPath);
|
|
4957
5273
|
if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
|
|
4958
5274
|
name: skillName,
|
|
@@ -4960,9 +5276,29 @@ async function updateGlobalSkills(options = {}) {
|
|
|
4960
5276
|
entry
|
|
4961
5277
|
});
|
|
4962
5278
|
}
|
|
4963
|
-
|
|
5279
|
+
continue;
|
|
5280
|
+
}
|
|
5281
|
+
tempDir = await cloneRepo(sourceUrl, firstEntry.ref);
|
|
5282
|
+
const discoveredPaths = (await discoverSkills(tempDir)).map((skill) => {
|
|
5283
|
+
return join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/");
|
|
5284
|
+
});
|
|
5285
|
+
const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
|
|
5286
|
+
const deletedSkillSet = new Set(deletedSkills);
|
|
5287
|
+
for (const { name: skillName, entry } of itemsForSource) {
|
|
5288
|
+
if (deletedSkillSet.has(skillName)) continue;
|
|
5289
|
+
const skillPath = entry.skillPath;
|
|
5290
|
+
if (!discoveredPaths.includes(skillPath)) continue;
|
|
5291
|
+
const latestHash = await computeSkillFolderHash(join(tempDir, dirname(skillPath)));
|
|
5292
|
+
if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
|
|
5293
|
+
name: skillName,
|
|
5294
|
+
source,
|
|
5295
|
+
entry
|
|
5296
|
+
});
|
|
5297
|
+
}
|
|
4964
5298
|
} catch (error) {
|
|
4965
|
-
console.log(` ${DIM$1}✗
|
|
5299
|
+
console.log(` ${DIM$1}✗ Failed to check skills from ${source}${RESET$1}`);
|
|
5300
|
+
} finally {
|
|
5301
|
+
if (tempDir) await cleanupTempDir(tempDir);
|
|
4966
5302
|
}
|
|
4967
5303
|
}
|
|
4968
5304
|
if (checkable.length > 0) process.stdout.write("\r\x1B[K");
|
|
@@ -5191,6 +5527,368 @@ async function runUpdate(args = []) {
|
|
|
5191
5527
|
});
|
|
5192
5528
|
console.log();
|
|
5193
5529
|
}
|
|
5530
|
+
const BLOB_ALLOWED_OWNERS = [
|
|
5531
|
+
"vercel",
|
|
5532
|
+
"vercel-labs",
|
|
5533
|
+
"heygen-com"
|
|
5534
|
+
];
|
|
5535
|
+
const EXCLUDE_FILES = new Set(["metadata.json"]);
|
|
5536
|
+
const EXCLUDE_DIRS = new Set([
|
|
5537
|
+
".git",
|
|
5538
|
+
"__pycache__",
|
|
5539
|
+
"__pypackages__"
|
|
5540
|
+
]);
|
|
5541
|
+
const USE_AGENT_CONFIGS = {
|
|
5542
|
+
"claude-code": {
|
|
5543
|
+
command: "claude",
|
|
5544
|
+
args: []
|
|
5545
|
+
},
|
|
5546
|
+
codex: {
|
|
5547
|
+
command: "codex",
|
|
5548
|
+
args: []
|
|
5549
|
+
}
|
|
5550
|
+
};
|
|
5551
|
+
const SUPPORTED_USE_AGENTS = Object.keys(USE_AGENT_CONFIGS);
|
|
5552
|
+
function parseUseOptions(args) {
|
|
5553
|
+
const source = [];
|
|
5554
|
+
const options = {};
|
|
5555
|
+
const errors = [];
|
|
5556
|
+
for (let i = 0; i < args.length; i++) {
|
|
5557
|
+
const arg = args[i];
|
|
5558
|
+
if (!arg) continue;
|
|
5559
|
+
if (arg === "--help" || arg === "-h") options.help = true;
|
|
5560
|
+
else if (arg === "--full-depth") options.fullDepth = true;
|
|
5561
|
+
else if (arg === "--dangerously-accept-openclaw-risks") options.dangerouslyAcceptOpenclawRisks = true;
|
|
5562
|
+
else if (arg === "--skill" || arg === "-s") {
|
|
5563
|
+
const value = args[i + 1];
|
|
5564
|
+
if (!value || value.startsWith("-")) errors.push(`${arg} requires a skill name`);
|
|
5565
|
+
else if (options.skill) {
|
|
5566
|
+
errors.push("Only one --skill value can be provided");
|
|
5567
|
+
i++;
|
|
5568
|
+
} else {
|
|
5569
|
+
options.skill = value;
|
|
5570
|
+
i++;
|
|
5571
|
+
}
|
|
5572
|
+
} else if (arg === "--agent" || arg === "-a") {
|
|
5573
|
+
options.agent = options.agent || [];
|
|
5574
|
+
i++;
|
|
5575
|
+
let nextArg = args[i];
|
|
5576
|
+
const startCount = options.agent.length;
|
|
5577
|
+
while (i < args.length && nextArg && !nextArg.startsWith("-")) {
|
|
5578
|
+
options.agent.push(nextArg);
|
|
5579
|
+
i++;
|
|
5580
|
+
nextArg = args[i];
|
|
5581
|
+
}
|
|
5582
|
+
if (options.agent.length === startCount) errors.push(`${arg} requires an agent name`);
|
|
5583
|
+
i--;
|
|
5584
|
+
} else if (arg.startsWith("-")) errors.push(`Unknown option: ${arg}`);
|
|
5585
|
+
else source.push(arg);
|
|
5586
|
+
}
|
|
5587
|
+
errors.push(...validateUseAgentOption(options.agent));
|
|
5588
|
+
return {
|
|
5589
|
+
source,
|
|
5590
|
+
options,
|
|
5591
|
+
errors
|
|
5592
|
+
};
|
|
5593
|
+
}
|
|
5594
|
+
function buildUsePrompt(input) {
|
|
5595
|
+
const sections = [
|
|
5596
|
+
"You are being given a Skill to execute for the user's next request.",
|
|
5597
|
+
"Use the following SKILL.md as your instructions:",
|
|
5598
|
+
`<SKILL.md>\n${input.skillMd}\n</SKILL.md>`
|
|
5599
|
+
];
|
|
5600
|
+
if (input.hasSupportingFiles && input.supportDir) sections.push(`Supporting files for this skill were downloaded to:\n${input.supportDir}\n\nWhen the SKILL.md references relative paths, read them from that directory.`);
|
|
5601
|
+
return sections.join("\n\n") + "\n";
|
|
5602
|
+
}
|
|
5603
|
+
async function materializeUseSkill(skill) {
|
|
5604
|
+
const tempRoot = await mkdtemp(join(tmpdir(), "skills-use-"));
|
|
5605
|
+
const skillDir = join(tempRoot, sanitizeName(skill.directoryName || skill.name));
|
|
5606
|
+
if (!isPathSafe(tempRoot, skillDir)) throw new Error("Invalid skill name: potential path traversal detected");
|
|
5607
|
+
await mkdir(skillDir, { recursive: true });
|
|
5608
|
+
if (skill.kind === "blob") await writeSnapshotFiles(skillDir, skill.files);
|
|
5609
|
+
else if (skill.kind === "well-known") await writeMapFiles(skillDir, skill.files);
|
|
5610
|
+
else await copySkillDirectory(skill.path, skillDir);
|
|
5611
|
+
return {
|
|
5612
|
+
tempRoot,
|
|
5613
|
+
skillDir,
|
|
5614
|
+
skillMd: skill.rawContent ?? await readFile(join(skillDir, "SKILL.md"), "utf-8"),
|
|
5615
|
+
hasSupportingFiles: await containsSupportingFiles(skillDir, skillDir)
|
|
5616
|
+
};
|
|
5617
|
+
}
|
|
5618
|
+
async function runUse(sourceArgs, options = {}, parseErrors = []) {
|
|
5619
|
+
let cloneTempDir = null;
|
|
5620
|
+
try {
|
|
5621
|
+
if (options.help) {
|
|
5622
|
+
console.log(getUseHelp());
|
|
5623
|
+
return;
|
|
5624
|
+
}
|
|
5625
|
+
if (parseErrors.length > 0) fail(parseErrors.join("\n"));
|
|
5626
|
+
if (sourceArgs.length === 0) fail(`Missing required argument: source\n\n${getUseHelp()}`);
|
|
5627
|
+
if (sourceArgs.length > 1) fail(`Expected one source, received ${sourceArgs.length}: ${sourceArgs.join(", ")}`);
|
|
5628
|
+
const useAgent = options.agent?.[0];
|
|
5629
|
+
if (useAgent && !USE_AGENT_CONFIGS[useAgent]) fail(formatUnsupportedAgentError(useAgent));
|
|
5630
|
+
const source = sourceArgs[0];
|
|
5631
|
+
const parsed = parseSource(source);
|
|
5632
|
+
if (getOwnerRepo(parsed)?.split("/")[0]?.toLowerCase() === "openclaw" && !options.dangerouslyAcceptOpenclawRisks) fail([
|
|
5633
|
+
"OpenClaw skills are unverified community submissions.",
|
|
5634
|
+
"Skills run with full agent permissions and could be malicious.",
|
|
5635
|
+
`If you understand the risks, re-run with: skills use ${source} --dangerously-accept-openclaw-risks`
|
|
5636
|
+
].join("\n"));
|
|
5637
|
+
const selector = resolveSelector(parsed.skillFilter, options.skill);
|
|
5638
|
+
const includeInternal = selector !== void 0;
|
|
5639
|
+
let selectedSkill;
|
|
5640
|
+
if (parsed.type === "well-known") selectedSkill = selectWellKnownSkill(await wellKnownProvider.fetchAllSkills(parsed.url), selector, source);
|
|
5641
|
+
else {
|
|
5642
|
+
let skills;
|
|
5643
|
+
let blobResult = null;
|
|
5644
|
+
if (parsed.type === "local") {
|
|
5645
|
+
if (!existsSync(parsed.localPath)) fail(`Local path does not exist: ${parsed.localPath}`);
|
|
5646
|
+
skills = await discoverSkills(parsed.localPath, parsed.subpath, {
|
|
5647
|
+
includeInternal,
|
|
5648
|
+
fullDepth: options.fullDepth
|
|
5649
|
+
});
|
|
5650
|
+
} else if (parsed.type === "github" && !options.fullDepth) {
|
|
5651
|
+
const ownerRepo = getOwnerRepo(parsed);
|
|
5652
|
+
const owner = ownerRepo?.split("/")[0]?.toLowerCase();
|
|
5653
|
+
if (ownerRepo && owner && BLOB_ALLOWED_OWNERS.includes(owner)) blobResult = await tryBlobInstall(ownerRepo, {
|
|
5654
|
+
subpath: parsed.subpath,
|
|
5655
|
+
skillFilter: selector,
|
|
5656
|
+
ref: parsed.ref,
|
|
5657
|
+
getToken: getGitHubToken,
|
|
5658
|
+
includeInternal
|
|
5659
|
+
});
|
|
5660
|
+
if (blobResult) skills = blobResult.skills;
|
|
5661
|
+
else {
|
|
5662
|
+
cloneTempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
5663
|
+
skills = await discoverSkills(cloneTempDir, parsed.subpath, {
|
|
5664
|
+
includeInternal,
|
|
5665
|
+
fullDepth: options.fullDepth
|
|
5666
|
+
});
|
|
5667
|
+
}
|
|
5668
|
+
} else {
|
|
5669
|
+
cloneTempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
5670
|
+
skills = await discoverSkills(cloneTempDir, parsed.subpath, {
|
|
5671
|
+
includeInternal,
|
|
5672
|
+
fullDepth: options.fullDepth
|
|
5673
|
+
});
|
|
5674
|
+
}
|
|
5675
|
+
const selected = selectSkill(skills, selector, source);
|
|
5676
|
+
if (blobResult && isBlobSkill(selected)) selectedSkill = {
|
|
5677
|
+
kind: "blob",
|
|
5678
|
+
name: selected.name,
|
|
5679
|
+
directoryName: selected.name,
|
|
5680
|
+
rawContent: selected.rawContent ?? getSkillMdFromSnapshot(selected.files),
|
|
5681
|
+
files: selected.files
|
|
5682
|
+
};
|
|
5683
|
+
else selectedSkill = {
|
|
5684
|
+
kind: "disk",
|
|
5685
|
+
name: selected.name,
|
|
5686
|
+
directoryName: selected.name,
|
|
5687
|
+
rawContent: selected.rawContent,
|
|
5688
|
+
path: selected.path
|
|
5689
|
+
};
|
|
5690
|
+
}
|
|
5691
|
+
const materialized = await materializeUseSkill(selectedSkill);
|
|
5692
|
+
await cleanupClone(cloneTempDir);
|
|
5693
|
+
cloneTempDir = null;
|
|
5694
|
+
const prompt = buildUsePrompt({
|
|
5695
|
+
skillMd: materialized.skillMd,
|
|
5696
|
+
supportDir: materialized.skillDir,
|
|
5697
|
+
hasSupportingFiles: materialized.hasSupportingFiles
|
|
5698
|
+
});
|
|
5699
|
+
if (useAgent) {
|
|
5700
|
+
const exitCode = await launchAgentInteractively(useAgent, prompt);
|
|
5701
|
+
if (exitCode !== 0) process.exit(exitCode);
|
|
5702
|
+
return;
|
|
5703
|
+
}
|
|
5704
|
+
process.stdout.write(prompt);
|
|
5705
|
+
} catch (error) {
|
|
5706
|
+
await cleanupClone(cloneTempDir);
|
|
5707
|
+
if (error instanceof GitCloneError) fail(error.message);
|
|
5708
|
+
if (error instanceof UseCommandError) fail(error.message);
|
|
5709
|
+
fail(error instanceof Error ? error.message : "Unknown error");
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
async function launchAgentInteractively(agent, prompt, spawnImpl = spawnAgent) {
|
|
5713
|
+
const config = USE_AGENT_CONFIGS[agent];
|
|
5714
|
+
if (!config) throw new UseCommandError(formatUnsupportedAgentError(agent));
|
|
5715
|
+
return new Promise((resolve, reject) => {
|
|
5716
|
+
const child = spawnImpl(config.command, [...config.args, prompt], { stdio: "inherit" });
|
|
5717
|
+
let settled = false;
|
|
5718
|
+
child.on("error", (error) => {
|
|
5719
|
+
if (settled) return;
|
|
5720
|
+
settled = true;
|
|
5721
|
+
if (error.code === "ENOENT") {
|
|
5722
|
+
reject(new UseCommandError(`Could not launch ${agents[agent].displayName}: command not found: ${config.command}`));
|
|
5723
|
+
return;
|
|
5724
|
+
}
|
|
5725
|
+
reject(error);
|
|
5726
|
+
});
|
|
5727
|
+
child.on("close", (code) => {
|
|
5728
|
+
if (settled) return;
|
|
5729
|
+
settled = true;
|
|
5730
|
+
resolve(code ?? 1);
|
|
5731
|
+
});
|
|
5732
|
+
});
|
|
5733
|
+
}
|
|
5734
|
+
function spawnAgent(command, args) {
|
|
5735
|
+
return spawn(command, args, { stdio: "inherit" });
|
|
5736
|
+
}
|
|
5737
|
+
function getUseHelp() {
|
|
5738
|
+
return `Usage: skills use <source>[@<skill>] [options]
|
|
5739
|
+
|
|
5740
|
+
Generate a prompt for using one skill without installing it.
|
|
5741
|
+
|
|
5742
|
+
Options:
|
|
5743
|
+
-s, --skill <skill> Select the skill to use
|
|
5744
|
+
-a, --agent <agent> Start one supported agent interactively (${SUPPORTED_USE_AGENTS.join(", ")})
|
|
5745
|
+
--full-depth Search nested directories like skills add --full-depth
|
|
5746
|
+
--dangerously-accept-openclaw-risks
|
|
5747
|
+
Allow unverified OpenClaw community skills
|
|
5748
|
+
-h, --help Show this help message
|
|
5749
|
+
|
|
5750
|
+
Examples:
|
|
5751
|
+
skills use vercel-labs/agent-skills@nextjs | claude
|
|
5752
|
+
skills use vercel-labs/agent-skills --skill nextjs --agent claude-code
|
|
5753
|
+
skills use vercel-labs/agent-skills@nextjs --agent codex`;
|
|
5754
|
+
}
|
|
5755
|
+
function resolveSelector(sourceSelector, optionSelector) {
|
|
5756
|
+
if (sourceSelector && optionSelector) {
|
|
5757
|
+
if (sourceSelector.toLowerCase() !== optionSelector.toLowerCase()) throw new UseCommandError(`Conflicting skill selectors: source selects "${sourceSelector}" but --skill selects "${optionSelector}". Provide one selector.`);
|
|
5758
|
+
return optionSelector;
|
|
5759
|
+
}
|
|
5760
|
+
return optionSelector ?? sourceSelector;
|
|
5761
|
+
}
|
|
5762
|
+
function selectSkill(skills, selector, source) {
|
|
5763
|
+
if (skills.length === 0) throw new UseCommandError("No valid skills found. Skills require a SKILL.md with name and description.");
|
|
5764
|
+
if (!selector) {
|
|
5765
|
+
if (skills.length === 1) return skills[0];
|
|
5766
|
+
throw new UseCommandError(formatMultipleSkillsError(source, skills.map(getSkillDisplayName)));
|
|
5767
|
+
}
|
|
5768
|
+
const selected = filterSkills(skills, [selector]);
|
|
5769
|
+
if (selected.length === 0) throw new UseCommandError(formatNoMatchError(selector, skills.map(getSkillDisplayName)));
|
|
5770
|
+
if (selected.length > 1) throw new UseCommandError(`Skill selector "${selector}" matched multiple skills.`);
|
|
5771
|
+
return selected[0];
|
|
5772
|
+
}
|
|
5773
|
+
function selectWellKnownSkill(skills, selector, source) {
|
|
5774
|
+
if (skills.length === 0) throw new UseCommandError("No skills found at this URL. Make sure the server has a /.well-known/agent-skills/index.json or /.well-known/skills/index.json file.");
|
|
5775
|
+
let selected;
|
|
5776
|
+
if (!selector) {
|
|
5777
|
+
if (skills.length !== 1) throw new UseCommandError(formatMultipleSkillsError(source, skills.map((s) => s.installName)));
|
|
5778
|
+
selected = skills;
|
|
5779
|
+
} else {
|
|
5780
|
+
selected = skills.filter((skill) => skill.installName.toLowerCase() === selector.toLowerCase() || skill.name.toLowerCase() === selector.toLowerCase());
|
|
5781
|
+
if (selected.length === 0) throw new UseCommandError(formatNoMatchError(selector, skills.map((s) => s.installName)));
|
|
5782
|
+
if (selected.length > 1) throw new UseCommandError(`Skill selector "${selector}" matched multiple skills.`);
|
|
5783
|
+
}
|
|
5784
|
+
const skill = selected[0];
|
|
5785
|
+
return {
|
|
5786
|
+
kind: "well-known",
|
|
5787
|
+
name: skill.name,
|
|
5788
|
+
directoryName: skill.installName,
|
|
5789
|
+
rawContent: skill.content,
|
|
5790
|
+
files: skill.files
|
|
5791
|
+
};
|
|
5792
|
+
}
|
|
5793
|
+
function formatMultipleSkillsError(source, names) {
|
|
5794
|
+
return [
|
|
5795
|
+
"This source contains multiple skills. Specify exactly one skill:",
|
|
5796
|
+
...names.map((name) => ` - ${name}`),
|
|
5797
|
+
"",
|
|
5798
|
+
`Examples:\n skills use ${source}@${names[0] ?? "<skill>"}\n skills use ${source} --skill ${names[0] ?? "<skill>"}`
|
|
5799
|
+
].join("\n");
|
|
5800
|
+
}
|
|
5801
|
+
function formatNoMatchError(selector, names) {
|
|
5802
|
+
return [
|
|
5803
|
+
`No matching skill found for: ${selector}`,
|
|
5804
|
+
"Available skills:",
|
|
5805
|
+
...names.map((name) => ` - ${name}`)
|
|
5806
|
+
].join("\n");
|
|
5807
|
+
}
|
|
5808
|
+
function validateUseAgentOption(agentValues) {
|
|
5809
|
+
if (!agentValues || agentValues.length === 0) return [];
|
|
5810
|
+
const errors = [];
|
|
5811
|
+
const validAgents = Object.keys(agents);
|
|
5812
|
+
const invalidAgents = agentValues.filter((agent) => agent !== "*" && !validAgents.includes(agent));
|
|
5813
|
+
if (agentValues.includes("*")) errors.push("skills use --agent does not support '*'; specify exactly one agent.");
|
|
5814
|
+
if (agentValues.length > 1) errors.push("skills use --agent accepts exactly one agent.");
|
|
5815
|
+
if (invalidAgents.length > 0) errors.push(`Invalid agents: ${invalidAgents.join(", ")}\nValid agents: ${validAgents.join(", ")}`);
|
|
5816
|
+
return errors;
|
|
5817
|
+
}
|
|
5818
|
+
function formatUnsupportedAgentError(agent) {
|
|
5819
|
+
return [`Running ${agents[agent].displayName} is not supported yet.`, `Supported agents for skills use --agent: ${SUPPORTED_USE_AGENTS.join(", ")}`].join("\n");
|
|
5820
|
+
}
|
|
5821
|
+
async function writeSnapshotFiles(targetDir, files) {
|
|
5822
|
+
for (const file of files) await writeSafeFile(targetDir, file.path, file.contents);
|
|
5823
|
+
}
|
|
5824
|
+
async function writeMapFiles(targetDir, files) {
|
|
5825
|
+
for (const [path, contents] of files) await writeSafeFile(targetDir, path, contents);
|
|
5826
|
+
}
|
|
5827
|
+
async function writeSafeFile(targetDir, filePath, contents) {
|
|
5828
|
+
const fullPath = join(targetDir, filePath);
|
|
5829
|
+
if (!isPathSafe(targetDir, fullPath)) return;
|
|
5830
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
5831
|
+
if (typeof contents === "string") await writeFile(fullPath, contents, "utf-8");
|
|
5832
|
+
else await writeFile(fullPath, contents);
|
|
5833
|
+
}
|
|
5834
|
+
async function copySkillDirectory(src, dest) {
|
|
5835
|
+
await mkdir(dest, { recursive: true });
|
|
5836
|
+
const entries = await readdir(src, { withFileTypes: true });
|
|
5837
|
+
await Promise.all(entries.filter((entry) => !isExcluded(entry.name, entry.isDirectory())).map(async (entry) => {
|
|
5838
|
+
const srcPath = join(src, entry.name);
|
|
5839
|
+
const destPath = join(dest, entry.name);
|
|
5840
|
+
if (!isPathSafe(dest, destPath)) return;
|
|
5841
|
+
if (entry.isDirectory()) {
|
|
5842
|
+
await copySkillDirectory(srcPath, destPath);
|
|
5843
|
+
return;
|
|
5844
|
+
}
|
|
5845
|
+
try {
|
|
5846
|
+
await cp(srcPath, destPath, {
|
|
5847
|
+
dereference: true,
|
|
5848
|
+
recursive: true
|
|
5849
|
+
});
|
|
5850
|
+
} catch (err) {
|
|
5851
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT" && entry.isSymbolicLink()) {
|
|
5852
|
+
console.error(`Skipping broken symlink: ${srcPath}`);
|
|
5853
|
+
return;
|
|
5854
|
+
}
|
|
5855
|
+
throw err;
|
|
5856
|
+
}
|
|
5857
|
+
}));
|
|
5858
|
+
}
|
|
5859
|
+
async function containsSupportingFiles(rootDir, currentDir) {
|
|
5860
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
5861
|
+
for (const entry of entries) {
|
|
5862
|
+
const entryPath = join(currentDir, entry.name);
|
|
5863
|
+
const relPath = relative(rootDir, entryPath).split(sep).join("/");
|
|
5864
|
+
if (entry.isDirectory()) {
|
|
5865
|
+
if (await containsSupportingFiles(rootDir, entryPath)) return true;
|
|
5866
|
+
} else if (relPath.toLowerCase() !== "skill.md") return true;
|
|
5867
|
+
}
|
|
5868
|
+
return false;
|
|
5869
|
+
}
|
|
5870
|
+
function isBlobSkill(skill) {
|
|
5871
|
+
return Array.isArray(skill.files);
|
|
5872
|
+
}
|
|
5873
|
+
function getSkillMdFromSnapshot(files) {
|
|
5874
|
+
return files.find((file) => file.path.toLowerCase() === "skill.md")?.contents ?? "";
|
|
5875
|
+
}
|
|
5876
|
+
function isExcluded(name, isDirectory) {
|
|
5877
|
+
return EXCLUDE_FILES.has(name) || isDirectory && EXCLUDE_DIRS.has(name);
|
|
5878
|
+
}
|
|
5879
|
+
function isPathSafe(basePath, targetPath) {
|
|
5880
|
+
const normalizedBase = normalize(resolve(basePath));
|
|
5881
|
+
const normalizedTarget = normalize(resolve(targetPath));
|
|
5882
|
+
return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
|
|
5883
|
+
}
|
|
5884
|
+
async function cleanupClone(tempDir) {
|
|
5885
|
+
if (tempDir) await cleanupTempDir(tempDir).catch(() => {});
|
|
5886
|
+
}
|
|
5887
|
+
function fail(message) {
|
|
5888
|
+
console.error(message);
|
|
5889
|
+
process.exit(1);
|
|
5890
|
+
}
|
|
5891
|
+
var UseCommandError = class extends Error {};
|
|
5194
5892
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5195
5893
|
function getVersion() {
|
|
5196
5894
|
try {
|
|
@@ -5234,6 +5932,7 @@ function showBanner() {
|
|
|
5234
5932
|
console.log(`${DIM}The open agent skills ecosystem${RESET}`);
|
|
5235
5933
|
console.log();
|
|
5236
5934
|
console.log(` ${DIM}$${RESET} ${TEXT}npx skills add ${DIM}<package>${RESET} ${DIM}Add a new skill${RESET}`);
|
|
5935
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills use ${DIM}<package>@<skill>${RESET} ${DIM}Use a skill without installing${RESET}`);
|
|
5237
5936
|
console.log(` ${DIM}$${RESET} ${TEXT}npx skills remove${RESET} ${DIM}Remove installed skills${RESET}`);
|
|
5238
5937
|
console.log(` ${DIM}$${RESET} ${TEXT}npx skills list${RESET} ${DIM}List installed skills${RESET}`);
|
|
5239
5938
|
console.log(` ${DIM}$${RESET} ${TEXT}npx skills find ${DIM}[query]${RESET} ${DIM}Search for skills${RESET}`);
|
|
@@ -5257,6 +5956,8 @@ ${BOLD}Manage Skills:${RESET}
|
|
|
5257
5956
|
add <package> Add a skill package (alias: a)
|
|
5258
5957
|
e.g. vercel-labs/agent-skills
|
|
5259
5958
|
https://github.com/vercel-labs/agent-skills
|
|
5959
|
+
use <package>@<skill>
|
|
5960
|
+
Generate a prompt for using one skill without installing it
|
|
5260
5961
|
remove [skills] Remove installed skills
|
|
5261
5962
|
list, ls List installed skills
|
|
5262
5963
|
find [query] Search for skills interactively
|
|
@@ -5284,6 +5985,13 @@ ${BOLD}Add Options:${RESET}
|
|
|
5284
5985
|
--all Shorthand for --skill '*' --agent '*' -y
|
|
5285
5986
|
--full-depth Search all subdirectories even when a root SKILL.md exists
|
|
5286
5987
|
|
|
5988
|
+
${BOLD}Use Options:${RESET}
|
|
5989
|
+
-s, --skill <skill> Specify the skill to use
|
|
5990
|
+
-a, --agent <agent> Start one supported agent interactively
|
|
5991
|
+
--full-depth Search all subdirectories even when a root SKILL.md exists
|
|
5992
|
+
--dangerously-accept-openclaw-risks
|
|
5993
|
+
Allow unverified OpenClaw community skills
|
|
5994
|
+
|
|
5287
5995
|
${BOLD}Remove Options:${RESET}
|
|
5288
5996
|
-g, --global Remove from global scope
|
|
5289
5997
|
-a, --agent <agents> Remove from specific agents (use '*' for all agents)
|
|
@@ -5306,6 +6014,8 @@ ${BOLD}Options:${RESET}
|
|
|
5306
6014
|
|
|
5307
6015
|
${BOLD}Examples:${RESET}
|
|
5308
6016
|
${DIM}$${RESET} skills add vercel-labs/agent-skills
|
|
6017
|
+
${DIM}$${RESET} skills use vercel-labs/agent-skills@vercel-optimize | claude
|
|
6018
|
+
${DIM}$${RESET} skills use vercel-labs/agent-skills --skill vercel-optimize --agent claude-code
|
|
5309
6019
|
${DIM}$${RESET} skills add vercel-labs/agent-skills -g
|
|
5310
6020
|
${DIM}$${RESET} skills add vercel-labs/agent-skills --agent claude-code cursor
|
|
5311
6021
|
${DIM}$${RESET} skills add vercel-labs/agent-skills --skill pr-review commit
|
|
@@ -5442,6 +6152,11 @@ async function main() {
|
|
|
5442
6152
|
await runAdd(addSource, addOpts);
|
|
5443
6153
|
break;
|
|
5444
6154
|
}
|
|
6155
|
+
case "use": {
|
|
6156
|
+
const { source: useSource, options: useOptions, errors: useErrors } = parseUseOptions(restArgs);
|
|
6157
|
+
await runUse(useSource, useOptions, useErrors);
|
|
6158
|
+
break;
|
|
6159
|
+
}
|
|
5445
6160
|
case "remove":
|
|
5446
6161
|
case "rm":
|
|
5447
6162
|
case "r":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skills",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.10",
|
|
4
4
|
"description": "The open agent skills ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,6 +39,9 @@
|
|
|
39
39
|
"aider-desk",
|
|
40
40
|
"amp",
|
|
41
41
|
"antigravity",
|
|
42
|
+
"antigravity-cli",
|
|
43
|
+
"astrbot",
|
|
44
|
+
"autohand-code",
|
|
42
45
|
"augment",
|
|
43
46
|
"bob",
|
|
44
47
|
"claude-code",
|
|
@@ -64,31 +67,44 @@
|
|
|
64
67
|
"github-copilot",
|
|
65
68
|
"goose",
|
|
66
69
|
"hermes-agent",
|
|
70
|
+
"inference-sh",
|
|
71
|
+
"jazz",
|
|
67
72
|
"junie",
|
|
68
73
|
"iflow-cli",
|
|
69
74
|
"kilo",
|
|
70
|
-
"kimi-cli",
|
|
75
|
+
"kimi-code-cli",
|
|
71
76
|
"kiro-cli",
|
|
72
77
|
"kode",
|
|
78
|
+
"lingma",
|
|
79
|
+
"loaf",
|
|
73
80
|
"mcpjam",
|
|
74
81
|
"mistral-vibe",
|
|
82
|
+
"moxby",
|
|
75
83
|
"mux",
|
|
76
84
|
"opencode",
|
|
77
85
|
"openhands",
|
|
86
|
+
"ona",
|
|
78
87
|
"pi",
|
|
79
88
|
"qoder",
|
|
89
|
+
"qoder-cn",
|
|
80
90
|
"qwen-code",
|
|
81
91
|
"replit",
|
|
92
|
+
"reasonix",
|
|
82
93
|
"rovodev",
|
|
83
94
|
"roo",
|
|
84
95
|
"tabnine-cli",
|
|
96
|
+
"terramind",
|
|
97
|
+
"tinycloud",
|
|
85
98
|
"trae",
|
|
86
99
|
"trae-cn",
|
|
87
100
|
"warp",
|
|
88
101
|
"windsurf",
|
|
102
|
+
"zed",
|
|
89
103
|
"zencoder",
|
|
104
|
+
"zenflow",
|
|
90
105
|
"neovate",
|
|
91
106
|
"pochi",
|
|
107
|
+
"promptscript",
|
|
92
108
|
"adal",
|
|
93
109
|
"universal"
|
|
94
110
|
],
|