askill-cli 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/cli.mjs +794 -80
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,9 @@ You are an AI agent helping a user with askill - a package manager for AI agent
|
|
|
12
12
|
|
|
13
13
|
**Quick Commands:**
|
|
14
14
|
```bash
|
|
15
|
+
# Install a published skill (@author/skill-name)
|
|
16
|
+
askill add @johndoe/awesome-tool -y
|
|
17
|
+
|
|
15
18
|
# Install a skill
|
|
16
19
|
askill add owner/repo@skill-name -y
|
|
17
20
|
|
|
@@ -24,6 +27,13 @@ askill list
|
|
|
24
27
|
# Search for skills
|
|
25
28
|
askill find <query>
|
|
26
29
|
|
|
30
|
+
# Submit a GitHub skill URL for indexing
|
|
31
|
+
askill submit https://github.com/owner/repo
|
|
32
|
+
|
|
33
|
+
# Login and publish your own skill
|
|
34
|
+
askill login --token ask_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
35
|
+
askill publish
|
|
36
|
+
|
|
27
37
|
# Run a skill command
|
|
28
38
|
askill run skill-name:command
|
|
29
39
|
```
|
|
@@ -50,12 +60,17 @@ After installation, read the skill's `SKILL.md` file for usage instructions. Ski
|
|
|
50
60
|
|
|
51
61
|
askill is a universal package manager for AI agent skills. It enables agents to discover, install, and use skills across Claude Code, Cursor, Windsurf, and 40+ other AI coding assistants.
|
|
52
62
|
|
|
63
|
+
Every skill on [askill.sh](https://askill.sh) is automatically reviewed by AI across 5 quality dimensions — safety, clarity, reusability, completeness, and actionability — so you can evaluate quality before you install.
|
|
64
|
+
|
|
53
65
|
## Quick Start
|
|
54
66
|
|
|
55
67
|
```bash
|
|
56
68
|
# Install
|
|
57
69
|
curl -fsSL https://askill.sh | sh
|
|
58
70
|
|
|
71
|
+
# Install a published skill
|
|
72
|
+
askill add @johndoe/awesome-tool
|
|
73
|
+
|
|
59
74
|
# Install a skill
|
|
60
75
|
askill add owner/repo@skill-name
|
|
61
76
|
|
|
@@ -175,7 +190,8 @@ Instructions for the agent...
|
|
|
175
190
|
|
|
176
191
|
2. Test locally: `askill add ./my-skill`
|
|
177
192
|
3. Validate: `askill validate`
|
|
178
|
-
4.
|
|
193
|
+
4. Submit for indexing: `askill submit https://github.com/<owner>/<repo>`
|
|
194
|
+
5. Publish under your author scope: `askill login` then `askill publish`
|
|
179
195
|
|
|
180
196
|
See [Publishing Guide](./docs/publishing.md) for details.
|
|
181
197
|
|
package/dist/cli.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { existsSync } from "fs";
|
|
7
|
-
var VERSION = "0.1.
|
|
7
|
+
var VERSION = "0.1.4";
|
|
8
8
|
var API_BASE_URL = "https://askill.sh/api/v1";
|
|
9
9
|
var REGISTRY_URL = "https://askill.sh";
|
|
10
10
|
var RESET = "\x1B[0m";
|
|
@@ -396,6 +396,40 @@ var APIClient = class {
|
|
|
396
396
|
async checkCLIVersion() {
|
|
397
397
|
return this.fetch("/cli/version");
|
|
398
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Submit GitHub URL for indexing
|
|
401
|
+
*/
|
|
402
|
+
async submit(url) {
|
|
403
|
+
return this.fetch("/submit", {
|
|
404
|
+
method: "POST",
|
|
405
|
+
body: JSON.stringify({ url })
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Verify API token and fetch user profile
|
|
410
|
+
*/
|
|
411
|
+
async authMe(token) {
|
|
412
|
+
return this.fetch("/auth/me", {
|
|
413
|
+
headers: {
|
|
414
|
+
Authorization: `Bearer ${token}`
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Publish a skill from local content or GitHub URL
|
|
420
|
+
*/
|
|
421
|
+
async publish(payload) {
|
|
422
|
+
return this.fetch("/publish", {
|
|
423
|
+
method: "POST",
|
|
424
|
+
headers: {
|
|
425
|
+
Authorization: `Bearer ${payload.token}`
|
|
426
|
+
},
|
|
427
|
+
body: JSON.stringify({
|
|
428
|
+
content: payload.content,
|
|
429
|
+
githubUrl: payload.githubUrl
|
|
430
|
+
})
|
|
431
|
+
});
|
|
432
|
+
}
|
|
399
433
|
};
|
|
400
434
|
var APIError = class extends Error {
|
|
401
435
|
constructor(status, code, message) {
|
|
@@ -695,8 +729,8 @@ function getPlatformKey() {
|
|
|
695
729
|
}
|
|
696
730
|
async function shouldCheckUpdate() {
|
|
697
731
|
try {
|
|
698
|
-
const { readFile:
|
|
699
|
-
const lastCheck = await
|
|
732
|
+
const { readFile: readFile5 } = await import("fs/promises");
|
|
733
|
+
const lastCheck = await readFile5(UPDATE_CHECK_FILE, "utf-8");
|
|
700
734
|
const lastCheckTime = parseInt(lastCheck, 10);
|
|
701
735
|
return Date.now() - lastCheckTime > UPDATE_INTERVAL_MS;
|
|
702
736
|
} catch {
|
|
@@ -705,9 +739,9 @@ async function shouldCheckUpdate() {
|
|
|
705
739
|
}
|
|
706
740
|
async function saveUpdateCheckTime() {
|
|
707
741
|
try {
|
|
708
|
-
const { mkdir:
|
|
709
|
-
await
|
|
710
|
-
await
|
|
742
|
+
const { mkdir: mkdir5, writeFile: writeFile5 } = await import("fs/promises");
|
|
743
|
+
await mkdir5(dirname2(UPDATE_CHECK_FILE), { recursive: true });
|
|
744
|
+
await writeFile5(UPDATE_CHECK_FILE, String(Date.now()), "utf-8");
|
|
711
745
|
} catch {
|
|
712
746
|
}
|
|
713
747
|
}
|
|
@@ -754,28 +788,29 @@ async function fetchVersionInfo() {
|
|
|
754
788
|
return null;
|
|
755
789
|
}
|
|
756
790
|
}
|
|
757
|
-
async function
|
|
791
|
+
async function getAvailableUpdate(force = false) {
|
|
758
792
|
if (!force && !await shouldCheckUpdate()) {
|
|
759
|
-
return;
|
|
793
|
+
return null;
|
|
760
794
|
}
|
|
761
795
|
await saveUpdateCheckTime();
|
|
762
796
|
const versionInfo = await fetchVersionInfo();
|
|
763
|
-
if (!versionInfo) return;
|
|
797
|
+
if (!versionInfo) return null;
|
|
764
798
|
const current = VERSION;
|
|
765
|
-
const latest = versionInfo.latest;
|
|
766
|
-
if (semver.lt(current, latest)) {
|
|
767
|
-
console.log();
|
|
768
|
-
console.log(`${YELLOW}\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E${RESET}`);
|
|
769
|
-
console.log(`${YELLOW}\u2502${RESET} Update available: ${DIM}${current}${RESET} \u2192 ${GREEN}${latest}${RESET} ${YELLOW}\u2502${RESET}`);
|
|
770
|
-
console.log(`${YELLOW}\u2502${RESET} Run ${CYAN}askill upgrade${RESET} to update ${YELLOW}\u2502${RESET}`);
|
|
771
|
-
console.log(`${YELLOW}\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F${RESET}`);
|
|
772
|
-
console.log();
|
|
773
|
-
}
|
|
774
799
|
if (semver.lt(current, versionInfo.minimum)) {
|
|
775
800
|
console.log(`${RED}Your askill version is too old. Please update to continue.${RESET}`);
|
|
776
801
|
console.log(`Minimum required: ${versionInfo.minimum}`);
|
|
777
802
|
process.exit(1);
|
|
778
803
|
}
|
|
804
|
+
if (!semver.lt(current, versionInfo.latest)) {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
current,
|
|
809
|
+
latest: versionInfo.latest,
|
|
810
|
+
minimum: versionInfo.minimum,
|
|
811
|
+
releaseNotes: versionInfo.releaseNotes,
|
|
812
|
+
releaseUrl: versionInfo.releaseUrl
|
|
813
|
+
};
|
|
779
814
|
}
|
|
780
815
|
async function selfUpdate() {
|
|
781
816
|
console.log(`${CYAN}Checking for updates...${RESET}`);
|
|
@@ -865,6 +900,37 @@ async function getPreferredAgents() {
|
|
|
865
900
|
return config.preferredAgents;
|
|
866
901
|
}
|
|
867
902
|
|
|
903
|
+
// src/credentials.ts
|
|
904
|
+
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3, rm as rm2 } from "fs/promises";
|
|
905
|
+
import { join as join5 } from "path";
|
|
906
|
+
import { homedir as homedir5 } from "os";
|
|
907
|
+
var ASKILL_DIR = join5(homedir5(), ".askill");
|
|
908
|
+
var CREDENTIALS_FILE = join5(ASKILL_DIR, "credentials.json");
|
|
909
|
+
async function loadCredentials() {
|
|
910
|
+
try {
|
|
911
|
+
const content = await readFile2(CREDENTIALS_FILE, "utf-8");
|
|
912
|
+
const parsed = JSON.parse(content);
|
|
913
|
+
if (!parsed.token) return null;
|
|
914
|
+
return parsed;
|
|
915
|
+
} catch {
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
async function saveCredentials(credentials) {
|
|
920
|
+
await mkdir3(ASKILL_DIR, { recursive: true });
|
|
921
|
+
await writeFile3(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), "utf-8");
|
|
922
|
+
}
|
|
923
|
+
async function clearCredentials() {
|
|
924
|
+
try {
|
|
925
|
+
await rm2(CREDENTIALS_FILE);
|
|
926
|
+
} catch {
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
function maskToken(token) {
|
|
930
|
+
if (token.length <= 8) return token;
|
|
931
|
+
return `${token.slice(0, 8)}****`;
|
|
932
|
+
}
|
|
933
|
+
|
|
868
934
|
// src/parser.ts
|
|
869
935
|
function parseSkillMd(content) {
|
|
870
936
|
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
@@ -1127,12 +1193,12 @@ function isLocalPath(input) {
|
|
|
1127
1193
|
}
|
|
1128
1194
|
|
|
1129
1195
|
// src/discover.ts
|
|
1130
|
-
import { readdir as readdir2, readFile as
|
|
1131
|
-
import { join as
|
|
1196
|
+
import { readdir as readdir2, readFile as readFile3, stat } from "fs/promises";
|
|
1197
|
+
import { join as join6 } from "path";
|
|
1132
1198
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "__pycache__"]);
|
|
1133
1199
|
async function hasSkillMd(dir) {
|
|
1134
1200
|
try {
|
|
1135
|
-
const skillPath =
|
|
1201
|
+
const skillPath = join6(dir, "SKILL.md");
|
|
1136
1202
|
const stats = await stat(skillPath);
|
|
1137
1203
|
return stats.isFile();
|
|
1138
1204
|
} catch {
|
|
@@ -1141,8 +1207,8 @@ async function hasSkillMd(dir) {
|
|
|
1141
1207
|
}
|
|
1142
1208
|
async function parseSkillDir(dir) {
|
|
1143
1209
|
try {
|
|
1144
|
-
const skillPath =
|
|
1145
|
-
const content = await
|
|
1210
|
+
const skillPath = join6(dir, "SKILL.md");
|
|
1211
|
+
const content = await readFile3(skillPath, "utf-8");
|
|
1146
1212
|
const parsed = parseSkillMd(content);
|
|
1147
1213
|
if (!parsed.frontmatter.name || !parsed.frontmatter.description) {
|
|
1148
1214
|
return null;
|
|
@@ -1167,7 +1233,7 @@ async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
|
|
|
1167
1233
|
]);
|
|
1168
1234
|
const currentDir = hasSkill ? [dir] : [];
|
|
1169
1235
|
const subDirResults = await Promise.all(
|
|
1170
|
-
entries.filter((entry) => entry.isDirectory() && !SKIP_DIRS.has(entry.name)).map((entry) => findSkillDirs(
|
|
1236
|
+
entries.filter((entry) => entry.isDirectory() && !SKIP_DIRS.has(entry.name)).map((entry) => findSkillDirs(join6(dir, entry.name), depth + 1, maxDepth))
|
|
1171
1237
|
);
|
|
1172
1238
|
return [...currentDir, ...subDirResults.flat()];
|
|
1173
1239
|
} catch {
|
|
@@ -1177,7 +1243,7 @@ async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
|
|
|
1177
1243
|
async function discoverSkills(basePath, subpath) {
|
|
1178
1244
|
const skills = [];
|
|
1179
1245
|
const seenNames = /* @__PURE__ */ new Set();
|
|
1180
|
-
const searchPath = subpath ?
|
|
1246
|
+
const searchPath = subpath ? join6(basePath, subpath) : basePath;
|
|
1181
1247
|
if (await hasSkillMd(searchPath)) {
|
|
1182
1248
|
const skill = await parseSkillDir(searchPath);
|
|
1183
1249
|
if (skill) {
|
|
@@ -1186,27 +1252,27 @@ async function discoverSkills(basePath, subpath) {
|
|
|
1186
1252
|
}
|
|
1187
1253
|
const prioritySearchDirs = [
|
|
1188
1254
|
searchPath,
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1255
|
+
join6(searchPath, "skills"),
|
|
1256
|
+
join6(searchPath, "skills/.curated"),
|
|
1257
|
+
join6(searchPath, "skills/.experimental"),
|
|
1258
|
+
join6(searchPath, ".agents/skills"),
|
|
1259
|
+
join6(searchPath, ".claude/skills"),
|
|
1260
|
+
join6(searchPath, ".opencode/skills"),
|
|
1261
|
+
join6(searchPath, ".cursor/skills"),
|
|
1262
|
+
join6(searchPath, ".codex/skills"),
|
|
1263
|
+
join6(searchPath, ".cline/skills"),
|
|
1264
|
+
join6(searchPath, ".gemini/skills"),
|
|
1265
|
+
join6(searchPath, ".windsurf/skills"),
|
|
1266
|
+
join6(searchPath, ".roo/skills"),
|
|
1267
|
+
join6(searchPath, ".github/skills"),
|
|
1268
|
+
join6(searchPath, ".goose/skills")
|
|
1203
1269
|
];
|
|
1204
1270
|
for (const dir of prioritySearchDirs) {
|
|
1205
1271
|
try {
|
|
1206
1272
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
1207
1273
|
for (const entry of entries) {
|
|
1208
1274
|
if (entry.isDirectory()) {
|
|
1209
|
-
const skillDir =
|
|
1275
|
+
const skillDir = join6(dir, entry.name);
|
|
1210
1276
|
if (await hasSkillMd(skillDir)) {
|
|
1211
1277
|
const skill = await parseSkillDir(skillDir);
|
|
1212
1278
|
if (skill && !seenNames.has(skill.name)) {
|
|
@@ -1241,8 +1307,8 @@ function filterSkills(skills, names) {
|
|
|
1241
1307
|
|
|
1242
1308
|
// src/git.ts
|
|
1243
1309
|
import { execFile } from "child_process";
|
|
1244
|
-
import { mkdtemp, rm as
|
|
1245
|
-
import { join as
|
|
1310
|
+
import { mkdtemp, rm as rm3 } from "fs/promises";
|
|
1311
|
+
import { join as join7, normalize as normalize2, resolve as resolve3, sep as sep2 } from "path";
|
|
1246
1312
|
import { tmpdir } from "os";
|
|
1247
1313
|
var CLONE_TIMEOUT_MS = 6e4;
|
|
1248
1314
|
var GitCloneError = class extends Error {
|
|
@@ -1258,7 +1324,7 @@ var GitCloneError = class extends Error {
|
|
|
1258
1324
|
}
|
|
1259
1325
|
};
|
|
1260
1326
|
async function cloneRepo(url, ref) {
|
|
1261
|
-
const tempDir = await mkdtemp(
|
|
1327
|
+
const tempDir = await mkdtemp(join7(tmpdir(), "askill-"));
|
|
1262
1328
|
const args = ["clone", "--depth", "1"];
|
|
1263
1329
|
if (ref) {
|
|
1264
1330
|
args.push("--branch", ref);
|
|
@@ -1268,7 +1334,7 @@ async function cloneRepo(url, ref) {
|
|
|
1268
1334
|
await execGit(args);
|
|
1269
1335
|
return tempDir;
|
|
1270
1336
|
} catch (error) {
|
|
1271
|
-
await
|
|
1337
|
+
await rm3(tempDir, { recursive: true, force: true }).catch(() => {
|
|
1272
1338
|
});
|
|
1273
1339
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1274
1340
|
const isTimeout = errorMessage.includes("timed out") || errorMessage.includes("timeout");
|
|
@@ -1301,7 +1367,7 @@ async function cleanupTempDir(dir) {
|
|
|
1301
1367
|
if (!normalizedDir.startsWith(normalizedTmpDir + sep2) && normalizedDir !== normalizedTmpDir) {
|
|
1302
1368
|
throw new Error("Attempted to clean up directory outside of temp directory");
|
|
1303
1369
|
}
|
|
1304
|
-
await
|
|
1370
|
+
await rm3(dir, { recursive: true, force: true });
|
|
1305
1371
|
}
|
|
1306
1372
|
function execGit(args) {
|
|
1307
1373
|
return new Promise((resolve4, reject) => {
|
|
@@ -1316,14 +1382,14 @@ function execGit(args) {
|
|
|
1316
1382
|
}
|
|
1317
1383
|
|
|
1318
1384
|
// src/lock.ts
|
|
1319
|
-
import { readFile as
|
|
1320
|
-
import { join as
|
|
1321
|
-
import { homedir as
|
|
1385
|
+
import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
1386
|
+
import { join as join8, dirname as dirname5 } from "path";
|
|
1387
|
+
import { homedir as homedir6 } from "os";
|
|
1322
1388
|
var AGENTS_DIR2 = ".agents";
|
|
1323
1389
|
var LOCK_FILE = ".skill-lock.json";
|
|
1324
1390
|
var CURRENT_VERSION = 3;
|
|
1325
1391
|
function getSkillLockPath() {
|
|
1326
|
-
return
|
|
1392
|
+
return join8(homedir6(), AGENTS_DIR2, LOCK_FILE);
|
|
1327
1393
|
}
|
|
1328
1394
|
function createEmptyLockFile() {
|
|
1329
1395
|
return {
|
|
@@ -1334,7 +1400,7 @@ function createEmptyLockFile() {
|
|
|
1334
1400
|
async function readSkillLock() {
|
|
1335
1401
|
const lockPath = getSkillLockPath();
|
|
1336
1402
|
try {
|
|
1337
|
-
const content = await
|
|
1403
|
+
const content = await readFile4(lockPath, "utf-8");
|
|
1338
1404
|
const parsed = JSON.parse(content);
|
|
1339
1405
|
if (typeof parsed.version !== "number" || !parsed.skills) {
|
|
1340
1406
|
return createEmptyLockFile();
|
|
@@ -1349,9 +1415,9 @@ async function readSkillLock() {
|
|
|
1349
1415
|
}
|
|
1350
1416
|
async function writeSkillLock(lock) {
|
|
1351
1417
|
const lockPath = getSkillLockPath();
|
|
1352
|
-
await
|
|
1418
|
+
await mkdir4(dirname5(lockPath), { recursive: true });
|
|
1353
1419
|
const content = JSON.stringify(lock, null, 2);
|
|
1354
|
-
await
|
|
1420
|
+
await writeFile4(lockPath, content, "utf-8");
|
|
1355
1421
|
}
|
|
1356
1422
|
async function addSkillToLock(skillName, entry) {
|
|
1357
1423
|
const lock = await readSkillLock();
|
|
@@ -1425,8 +1491,8 @@ async function fetchSkillFolderHash(ownerRepo, skillPath) {
|
|
|
1425
1491
|
}
|
|
1426
1492
|
|
|
1427
1493
|
// src/cli.ts
|
|
1428
|
-
import { join as
|
|
1429
|
-
import { homedir as
|
|
1494
|
+
import { join as join9 } from "path";
|
|
1495
|
+
import { homedir as homedir7 } from "os";
|
|
1430
1496
|
import * as p from "@clack/prompts";
|
|
1431
1497
|
import pc from "picocolors";
|
|
1432
1498
|
var LOGO = `
|
|
@@ -1455,6 +1521,9 @@ function showBanner() {
|
|
|
1455
1521
|
console.log(` ${DIM}$${RESET} askill list${RESET} ${DIM}List installed skills${RESET}`);
|
|
1456
1522
|
console.log(` ${DIM}$${RESET} askill remove ${DIM}<skill>${RESET} ${DIM}Remove a skill${RESET}`);
|
|
1457
1523
|
console.log(` ${DIM}$${RESET} askill init${RESET} ${DIM}Create a new skill${RESET}`);
|
|
1524
|
+
console.log(` ${DIM}$${RESET} askill submit ${DIM}<url>${RESET} ${DIM}Submit GitHub skill URL${RESET}`);
|
|
1525
|
+
console.log(` ${DIM}$${RESET} askill login${RESET} ${DIM}Login with API token${RESET}`);
|
|
1526
|
+
console.log(` ${DIM}$${RESET} askill publish${RESET} ${DIM}Publish a skill${RESET}`);
|
|
1458
1527
|
console.log(` ${DIM}$${RESET} askill run ${DIM}<skill:cmd>${RESET} ${DIM}Run a skill command${RESET}`);
|
|
1459
1528
|
console.log();
|
|
1460
1529
|
console.log(`${DIM}Browse skills at${RESET} ${CYAN}https://askill.sh${RESET}`);
|
|
@@ -1474,10 +1543,17 @@ ${BOLD}Commands:${RESET}
|
|
|
1474
1543
|
validate [path] Validate a SKILL.md file
|
|
1475
1544
|
check Check installed skills for updates
|
|
1476
1545
|
update [skill] Update installed skills
|
|
1546
|
+
submit <github-url> Submit GitHub URL for indexing
|
|
1547
|
+
login [--token <token>] Login with API token
|
|
1548
|
+
logout Clear saved API token
|
|
1549
|
+
whoami Show current authenticated user
|
|
1550
|
+
publish [path] Publish SKILL.md from local path
|
|
1551
|
+
publish --github <url> Publish SKILL.md from GitHub URL
|
|
1477
1552
|
run <skill:cmd> Run a skill command
|
|
1478
1553
|
upgrade Update askill CLI to latest version
|
|
1479
1554
|
|
|
1480
1555
|
${BOLD}Skill Source Formats:${RESET}
|
|
1556
|
+
@author/skill-name Published skill from askill registry
|
|
1481
1557
|
owner/repo All skills from a GitHub repo
|
|
1482
1558
|
owner/repo@skill-name Specific skill by name
|
|
1483
1559
|
owner/repo/path/to/skill Specific skill by path
|
|
@@ -1486,6 +1562,7 @@ ${BOLD}Skill Source Formats:${RESET}
|
|
|
1486
1562
|
gh:owner/repo@skill-name Explicit GitHub prefix (optional)
|
|
1487
1563
|
|
|
1488
1564
|
${BOLD}Install Options:${RESET}
|
|
1565
|
+
(default) Install to current project: .agents/skills/
|
|
1489
1566
|
-g, --global Install globally (user-level)
|
|
1490
1567
|
-a, --agent <agents> Install to specific agents
|
|
1491
1568
|
-y, --yes Skip confirmation prompts
|
|
@@ -1496,21 +1573,297 @@ ${BOLD}Install Options:${RESET}
|
|
|
1496
1573
|
${BOLD}Run Options:${RESET}
|
|
1497
1574
|
askill run <skill>:<command> Run a skill's command
|
|
1498
1575
|
|
|
1576
|
+
${BOLD}Search Options:${RESET}
|
|
1577
|
+
--full-desc Show full skill descriptions in find/search
|
|
1578
|
+
|
|
1499
1579
|
${BOLD}Options:${RESET}
|
|
1500
1580
|
--help, -h Show this help message
|
|
1501
1581
|
--version, -v Show version number
|
|
1502
1582
|
|
|
1583
|
+
${BOLD}Per-command Help:${RESET}
|
|
1584
|
+
askill <command> --help
|
|
1585
|
+
askill help <command>
|
|
1586
|
+
|
|
1587
|
+
${BOLD}For Agents:${RESET}
|
|
1588
|
+
Official usage guide: ${CYAN}https://github.com/avibe-bot/askill/tree/main/skills/use-askill${RESET}
|
|
1589
|
+
|
|
1503
1590
|
${BOLD}Examples:${RESET}
|
|
1504
1591
|
${DIM}$${RESET} askill add anthropic/courses@prompt-eng
|
|
1505
1592
|
${DIM}$${RESET} askill add anthropic/courses
|
|
1506
1593
|
${DIM}$${RESET} askill add ./my-skills/custom-skill
|
|
1507
1594
|
${DIM}$${RESET} askill find memory
|
|
1595
|
+
${DIM}$${RESET} askill find memory --full-desc
|
|
1508
1596
|
${DIM}$${RESET} askill list -g
|
|
1509
1597
|
${DIM}$${RESET} askill info gh:anthropic/courses@prompt-eng
|
|
1510
1598
|
|
|
1511
1599
|
${DIM}Browse more at${RESET} ${CYAN}https://askill.sh${RESET}
|
|
1512
1600
|
`);
|
|
1513
1601
|
}
|
|
1602
|
+
function createSpinner(plain) {
|
|
1603
|
+
if (!plain) {
|
|
1604
|
+
return p.spinner();
|
|
1605
|
+
}
|
|
1606
|
+
return {
|
|
1607
|
+
start: () => {
|
|
1608
|
+
},
|
|
1609
|
+
stop: () => {
|
|
1610
|
+
},
|
|
1611
|
+
message: () => {
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
function normalizeCommand(command) {
|
|
1616
|
+
switch (command) {
|
|
1617
|
+
case "install":
|
|
1618
|
+
case "i":
|
|
1619
|
+
return "add";
|
|
1620
|
+
case "search":
|
|
1621
|
+
case "s":
|
|
1622
|
+
return "find";
|
|
1623
|
+
case "ls":
|
|
1624
|
+
return "list";
|
|
1625
|
+
case "rm":
|
|
1626
|
+
case "uninstall":
|
|
1627
|
+
return "remove";
|
|
1628
|
+
case "show":
|
|
1629
|
+
return "info";
|
|
1630
|
+
default:
|
|
1631
|
+
return command;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
function showCommandHelp(commandInput) {
|
|
1635
|
+
const command = normalizeCommand(commandInput);
|
|
1636
|
+
const helps = {
|
|
1637
|
+
add: `${BOLD}askill add${RESET}
|
|
1638
|
+
|
|
1639
|
+
Usage:
|
|
1640
|
+
askill add <source> [options]
|
|
1641
|
+
|
|
1642
|
+
Description:
|
|
1643
|
+
Install skills from published slugs, GitHub, or local directories.
|
|
1644
|
+
|
|
1645
|
+
Sources:
|
|
1646
|
+
@author/skill-name
|
|
1647
|
+
gh:owner/repo@skill-name
|
|
1648
|
+
gh:owner/repo/path/to/skill
|
|
1649
|
+
owner/repo
|
|
1650
|
+
./local/path
|
|
1651
|
+
|
|
1652
|
+
Scope:
|
|
1653
|
+
default: current project (.agents/skills/)
|
|
1654
|
+
-g, --global: user-level install
|
|
1655
|
+
|
|
1656
|
+
Options:
|
|
1657
|
+
-g, --global Install globally
|
|
1658
|
+
-a, --agent <agents...> Install to specific agents
|
|
1659
|
+
-y, --yes Skip confirmation prompts
|
|
1660
|
+
--copy Copy files instead of symlink
|
|
1661
|
+
-l, --list Preview discovered skills only
|
|
1662
|
+
--all Install all discovered skills
|
|
1663
|
+
|
|
1664
|
+
Examples:
|
|
1665
|
+
askill add @johndoe/awesome-tool -y
|
|
1666
|
+
askill add gh:facebook/react@extract-errors
|
|
1667
|
+
askill add owner/repo --all -a claude-code opencode -y
|
|
1668
|
+
|
|
1669
|
+
Guide:
|
|
1670
|
+
https://github.com/avibe-bot/askill/tree/main/skills/use-askill`,
|
|
1671
|
+
remove: `${BOLD}askill remove${RESET}
|
|
1672
|
+
|
|
1673
|
+
Usage:
|
|
1674
|
+
askill remove <skill> [options]
|
|
1675
|
+
|
|
1676
|
+
Description:
|
|
1677
|
+
Remove an installed skill from detected agents.
|
|
1678
|
+
|
|
1679
|
+
Options:
|
|
1680
|
+
-g, --global Remove global installation
|
|
1681
|
+
|
|
1682
|
+
Examples:
|
|
1683
|
+
askill remove memory
|
|
1684
|
+
askill remove memory -g`,
|
|
1685
|
+
list: `${BOLD}askill list${RESET}
|
|
1686
|
+
|
|
1687
|
+
Usage:
|
|
1688
|
+
askill list [options]
|
|
1689
|
+
|
|
1690
|
+
Description:
|
|
1691
|
+
List installed skills and where they are available.
|
|
1692
|
+
|
|
1693
|
+
Options:
|
|
1694
|
+
-g, --global Show global skills only
|
|
1695
|
+
|
|
1696
|
+
Examples:
|
|
1697
|
+
askill list
|
|
1698
|
+
askill list -g`,
|
|
1699
|
+
find: `${BOLD}askill find${RESET}
|
|
1700
|
+
|
|
1701
|
+
Usage:
|
|
1702
|
+
askill find [query] [options]
|
|
1703
|
+
|
|
1704
|
+
Description:
|
|
1705
|
+
Search indexed and published skills on askill.sh.
|
|
1706
|
+
|
|
1707
|
+
Options:
|
|
1708
|
+
--full-desc Show full descriptions
|
|
1709
|
+
|
|
1710
|
+
Examples:
|
|
1711
|
+
askill find memory
|
|
1712
|
+
askill find code review --full-desc`,
|
|
1713
|
+
info: `${BOLD}askill info${RESET}
|
|
1714
|
+
|
|
1715
|
+
Usage:
|
|
1716
|
+
askill info <slug>
|
|
1717
|
+
|
|
1718
|
+
Description:
|
|
1719
|
+
Show detailed metadata and installation info for one skill.
|
|
1720
|
+
|
|
1721
|
+
Examples:
|
|
1722
|
+
askill info @johndoe/awesome-tool
|
|
1723
|
+
askill info gh:facebook/react@extract-errors`,
|
|
1724
|
+
check: `${BOLD}askill check${RESET}
|
|
1725
|
+
|
|
1726
|
+
Usage:
|
|
1727
|
+
askill check [skill]
|
|
1728
|
+
|
|
1729
|
+
Description:
|
|
1730
|
+
Check installed skills for available updates without installing.
|
|
1731
|
+
|
|
1732
|
+
Examples:
|
|
1733
|
+
askill check
|
|
1734
|
+
askill check memory`,
|
|
1735
|
+
update: `${BOLD}askill update${RESET}
|
|
1736
|
+
|
|
1737
|
+
Usage:
|
|
1738
|
+
askill update [skill]
|
|
1739
|
+
|
|
1740
|
+
Description:
|
|
1741
|
+
Update one installed skill or all installed skills.
|
|
1742
|
+
|
|
1743
|
+
Examples:
|
|
1744
|
+
askill update
|
|
1745
|
+
askill update memory`,
|
|
1746
|
+
run: `${BOLD}askill run${RESET}
|
|
1747
|
+
|
|
1748
|
+
Usage:
|
|
1749
|
+
askill run <skill>:<command> [args...]
|
|
1750
|
+
|
|
1751
|
+
Description:
|
|
1752
|
+
Run a command declared in a skill's SKILL.md frontmatter.
|
|
1753
|
+
|
|
1754
|
+
Examples:
|
|
1755
|
+
askill run @anthropic/memory:save --key name --value "Alice"
|
|
1756
|
+
askill run my-skill:_setup`,
|
|
1757
|
+
validate: `${BOLD}askill validate${RESET}
|
|
1758
|
+
|
|
1759
|
+
Usage:
|
|
1760
|
+
askill validate [path]
|
|
1761
|
+
|
|
1762
|
+
Description:
|
|
1763
|
+
Validate SKILL.md frontmatter and command structure.
|
|
1764
|
+
|
|
1765
|
+
Examples:
|
|
1766
|
+
askill validate
|
|
1767
|
+
askill validate ./my-skill/SKILL.md`,
|
|
1768
|
+
init: `${BOLD}askill init${RESET}
|
|
1769
|
+
|
|
1770
|
+
Usage:
|
|
1771
|
+
askill init [dir] [options]
|
|
1772
|
+
|
|
1773
|
+
Description:
|
|
1774
|
+
Generate a new SKILL.md template interactively or non-interactively.
|
|
1775
|
+
|
|
1776
|
+
Options:
|
|
1777
|
+
-y, --yes Use defaults without prompts
|
|
1778
|
+
|
|
1779
|
+
Examples:
|
|
1780
|
+
askill init
|
|
1781
|
+
askill init ./my-skill -y`,
|
|
1782
|
+
submit: `${BOLD}askill submit${RESET}
|
|
1783
|
+
|
|
1784
|
+
Usage:
|
|
1785
|
+
askill submit <github-url>
|
|
1786
|
+
|
|
1787
|
+
Description:
|
|
1788
|
+
Submit a GitHub repository or SKILL.md URL for indexing on askill.sh.
|
|
1789
|
+
|
|
1790
|
+
Examples:
|
|
1791
|
+
askill submit https://github.com/owner/repo
|
|
1792
|
+
askill submit https://github.com/owner/repo/blob/main/skills/foo/SKILL.md`,
|
|
1793
|
+
login: `${BOLD}askill login${RESET}
|
|
1794
|
+
|
|
1795
|
+
Usage:
|
|
1796
|
+
askill login [--token <ask_xxx>]
|
|
1797
|
+
|
|
1798
|
+
Description:
|
|
1799
|
+
Save and verify an askill API token for publishing.
|
|
1800
|
+
|
|
1801
|
+
Examples:
|
|
1802
|
+
askill login
|
|
1803
|
+
askill login --token ask_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`,
|
|
1804
|
+
logout: `${BOLD}askill logout${RESET}
|
|
1805
|
+
|
|
1806
|
+
Usage:
|
|
1807
|
+
askill logout
|
|
1808
|
+
|
|
1809
|
+
Description:
|
|
1810
|
+
Clear saved local credentials.`,
|
|
1811
|
+
whoami: `${BOLD}askill whoami${RESET}
|
|
1812
|
+
|
|
1813
|
+
Usage:
|
|
1814
|
+
askill whoami
|
|
1815
|
+
|
|
1816
|
+
Description:
|
|
1817
|
+
Show current authenticated account and masked token.`,
|
|
1818
|
+
publish: `${BOLD}askill publish${RESET}
|
|
1819
|
+
|
|
1820
|
+
Usage:
|
|
1821
|
+
askill publish [path]
|
|
1822
|
+
askill publish --github <blob-url-to-SKILL.md>
|
|
1823
|
+
|
|
1824
|
+
Description:
|
|
1825
|
+
Publish a skill under your author scope (@author/skill-name).
|
|
1826
|
+
|
|
1827
|
+
Requirements:
|
|
1828
|
+
- Logged in via askill login
|
|
1829
|
+
- SKILL.md contains valid name and semver version
|
|
1830
|
+
|
|
1831
|
+
Examples:
|
|
1832
|
+
askill publish
|
|
1833
|
+
askill publish ./skills/my-skill
|
|
1834
|
+
askill publish --github https://github.com/owner/repo/blob/main/skills/my-skill/SKILL.md`,
|
|
1835
|
+
upgrade: `${BOLD}askill upgrade${RESET}
|
|
1836
|
+
|
|
1837
|
+
Usage:
|
|
1838
|
+
askill upgrade
|
|
1839
|
+
|
|
1840
|
+
Description:
|
|
1841
|
+
Self-update askill CLI to the latest available version.`,
|
|
1842
|
+
help: `${BOLD}askill help${RESET}
|
|
1843
|
+
|
|
1844
|
+
Usage:
|
|
1845
|
+
askill help
|
|
1846
|
+
askill help <command>
|
|
1847
|
+
|
|
1848
|
+
Description:
|
|
1849
|
+
Show global help or detailed help for a specific command.`
|
|
1850
|
+
};
|
|
1851
|
+
const text2 = helps[command];
|
|
1852
|
+
if (!text2) return false;
|
|
1853
|
+
console.log();
|
|
1854
|
+
console.log(text2);
|
|
1855
|
+
console.log();
|
|
1856
|
+
return true;
|
|
1857
|
+
}
|
|
1858
|
+
async function maybeAutoUpgradeOnStartup(commandInput) {
|
|
1859
|
+
const command = normalizeCommand(commandInput);
|
|
1860
|
+
const skipCommands = /* @__PURE__ */ new Set(["upgrade", "help", "version", "--version", "-v", "--help", "-h"]);
|
|
1861
|
+
if (skipCommands.has(command)) return;
|
|
1862
|
+
const available = await getAvailableUpdate(false).catch(() => null);
|
|
1863
|
+
if (!available) return;
|
|
1864
|
+
console.log(`${DIM}Auto-updating askill: ${available.current} -> ${available.latest}${RESET}`);
|
|
1865
|
+
await selfUpdate();
|
|
1866
|
+
}
|
|
1514
1867
|
function parseInstallOptions(args) {
|
|
1515
1868
|
const options = {};
|
|
1516
1869
|
let skillName = "";
|
|
@@ -1642,11 +1995,13 @@ async function resolveSkillsViaApi(parsed, spinner2, options) {
|
|
|
1642
1995
|
}
|
|
1643
1996
|
async function runInstall(args) {
|
|
1644
1997
|
const { skillName, options } = parseInstallOptions(args);
|
|
1998
|
+
const plainMode = Boolean(options.yes) || !process.stdout.isTTY;
|
|
1645
1999
|
if (!skillName) {
|
|
1646
2000
|
console.log(`${RED}Error: Missing skill identifier${RESET}`);
|
|
1647
2001
|
console.log(`Usage: askill add <source>`);
|
|
1648
2002
|
console.log(`
|
|
1649
2003
|
Formats supported:`);
|
|
2004
|
+
console.log(` askill add @author/skill-name ${DIM}# published skill${RESET}`);
|
|
1650
2005
|
console.log(` askill add owner/repo ${DIM}# all skills from repo${RESET}`);
|
|
1651
2006
|
console.log(` askill add owner/repo@skill-name ${DIM}# specific skill${RESET}`);
|
|
1652
2007
|
console.log(` askill add owner/repo/path/to/skill ${DIM}# skill by path${RESET}`);
|
|
@@ -1654,9 +2009,11 @@ Formats supported:`);
|
|
|
1654
2009
|
console.log(` askill add ./local/path ${DIM}# local directory${RESET}`);
|
|
1655
2010
|
process.exit(1);
|
|
1656
2011
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
2012
|
+
if (!plainMode) {
|
|
2013
|
+
console.log();
|
|
2014
|
+
p.intro(pc.bgCyan(pc.black(" askill install ")));
|
|
2015
|
+
}
|
|
2016
|
+
const spinner2 = createSpinner(plainMode);
|
|
1660
2017
|
const { skills: discoveredSkills, parsed: sourceParsed, tempDir } = await resolveSkills(skillName, spinner2, options);
|
|
1661
2018
|
const cleanup = async () => {
|
|
1662
2019
|
if (tempDir) await cleanupTempDir(tempDir).catch(() => {
|
|
@@ -1665,7 +2022,11 @@ Formats supported:`);
|
|
|
1665
2022
|
try {
|
|
1666
2023
|
if (discoveredSkills.length === 0) {
|
|
1667
2024
|
p.log.warning("No skills found");
|
|
1668
|
-
|
|
2025
|
+
if (plainMode) {
|
|
2026
|
+
console.log(`Browse skills at ${pc.cyan("https://askill.sh")}`);
|
|
2027
|
+
} else {
|
|
2028
|
+
p.outro(`Browse skills at ${pc.cyan("https://askill.sh")}`);
|
|
2029
|
+
}
|
|
1669
2030
|
return;
|
|
1670
2031
|
}
|
|
1671
2032
|
if (options.list) {
|
|
@@ -1682,7 +2043,11 @@ Formats supported:`);
|
|
|
1682
2043
|
}
|
|
1683
2044
|
console.log();
|
|
1684
2045
|
}
|
|
1685
|
-
|
|
2046
|
+
if (plainMode) {
|
|
2047
|
+
console.log(`Install with: ${pc.cyan(`askill add ${skillName} --all`)}`);
|
|
2048
|
+
} else {
|
|
2049
|
+
p.outro(`Install with: ${pc.cyan(`askill add ${skillName} --all`)}`);
|
|
2050
|
+
}
|
|
1686
2051
|
return;
|
|
1687
2052
|
}
|
|
1688
2053
|
let skillsToInstall;
|
|
@@ -1937,13 +2302,149 @@ Formats supported:`);
|
|
|
1937
2302
|
}
|
|
1938
2303
|
}
|
|
1939
2304
|
console.log();
|
|
1940
|
-
|
|
2305
|
+
if (plainMode) {
|
|
2306
|
+
console.log(pc.green("Done!"));
|
|
2307
|
+
} else {
|
|
2308
|
+
p.outro(pc.green("Done!"));
|
|
2309
|
+
}
|
|
1941
2310
|
} finally {
|
|
1942
2311
|
await cleanup();
|
|
1943
2312
|
}
|
|
1944
2313
|
}
|
|
2314
|
+
var SEARCH_DESCRIPTION_MAX_LENGTH = 500;
|
|
2315
|
+
function toNumber(value) {
|
|
2316
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2317
|
+
return null;
|
|
2318
|
+
}
|
|
2319
|
+
return value;
|
|
2320
|
+
}
|
|
2321
|
+
function parseJsonObject(value) {
|
|
2322
|
+
if (typeof value === "string") {
|
|
2323
|
+
try {
|
|
2324
|
+
const parsed = JSON.parse(value);
|
|
2325
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2326
|
+
return parsed;
|
|
2327
|
+
}
|
|
2328
|
+
return null;
|
|
2329
|
+
} catch {
|
|
2330
|
+
return null;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2334
|
+
return value;
|
|
2335
|
+
}
|
|
2336
|
+
return null;
|
|
2337
|
+
}
|
|
2338
|
+
function formatScore(score) {
|
|
2339
|
+
if (score === null) {
|
|
2340
|
+
return pc.dim("N/A");
|
|
2341
|
+
}
|
|
2342
|
+
return pc.green(Number.isInteger(score) ? String(score) : score.toFixed(1));
|
|
2343
|
+
}
|
|
2344
|
+
function toScoreLabel(key) {
|
|
2345
|
+
return key.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
2346
|
+
}
|
|
2347
|
+
function getScoreMeta(skill) {
|
|
2348
|
+
const withScoreMeta = skill;
|
|
2349
|
+
return parseJsonObject(withScoreMeta.llmScoreMeta);
|
|
2350
|
+
}
|
|
2351
|
+
function getTotalAIScore(skill) {
|
|
2352
|
+
const aiScore = toNumber(skill.aiScore);
|
|
2353
|
+
if (aiScore !== null) {
|
|
2354
|
+
return aiScore;
|
|
2355
|
+
}
|
|
2356
|
+
const directScore = toNumber(skill.llmScore);
|
|
2357
|
+
if (directScore !== null) {
|
|
2358
|
+
return directScore;
|
|
2359
|
+
}
|
|
2360
|
+
const meta = getScoreMeta(skill);
|
|
2361
|
+
if (!meta) {
|
|
2362
|
+
return null;
|
|
2363
|
+
}
|
|
2364
|
+
return toNumber(meta.score) ?? toNumber(meta.score_raw) ?? toNumber(meta.final_rank);
|
|
2365
|
+
}
|
|
2366
|
+
function getAIScoreDimensions(skill) {
|
|
2367
|
+
const directBreakdown = parseJsonObject(skill.aiBreakdown);
|
|
2368
|
+
if (directBreakdown) {
|
|
2369
|
+
const parsedDirect = Object.entries(directBreakdown).map(([key, value]) => {
|
|
2370
|
+
const score = toNumber(value);
|
|
2371
|
+
if (score === null) {
|
|
2372
|
+
return null;
|
|
2373
|
+
}
|
|
2374
|
+
return {
|
|
2375
|
+
key,
|
|
2376
|
+
label: toScoreLabel(key),
|
|
2377
|
+
score
|
|
2378
|
+
};
|
|
2379
|
+
}).filter((item) => item !== null);
|
|
2380
|
+
if (parsedDirect.length > 0) {
|
|
2381
|
+
const preferredOrder2 = ["completeness", "actionability", "reusability", "safety", "clarity", "internal_only"];
|
|
2382
|
+
return parsedDirect.sort((a, b) => {
|
|
2383
|
+
const indexA = preferredOrder2.indexOf(a.key);
|
|
2384
|
+
const indexB = preferredOrder2.indexOf(b.key);
|
|
2385
|
+
const rankA = indexA === -1 ? Number.MAX_SAFE_INTEGER : indexA;
|
|
2386
|
+
const rankB = indexB === -1 ? Number.MAX_SAFE_INTEGER : indexB;
|
|
2387
|
+
if (rankA !== rankB) {
|
|
2388
|
+
return rankA - rankB;
|
|
2389
|
+
}
|
|
2390
|
+
return a.label.localeCompare(b.label);
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
const meta = getScoreMeta(skill);
|
|
2395
|
+
if (!meta) {
|
|
2396
|
+
return [];
|
|
2397
|
+
}
|
|
2398
|
+
const dimensions = parseJsonObject(meta.dimensions);
|
|
2399
|
+
if (!dimensions) {
|
|
2400
|
+
return [];
|
|
2401
|
+
}
|
|
2402
|
+
const preferredOrder = ["completeness", "actionability", "reusability", "safety", "clarity", "internal_only"];
|
|
2403
|
+
const parsed = Object.entries(dimensions).map(([key, value]) => {
|
|
2404
|
+
const nested = parseJsonObject(value);
|
|
2405
|
+
const score = nested ? toNumber(nested.score) : toNumber(value);
|
|
2406
|
+
if (score === null) {
|
|
2407
|
+
return null;
|
|
2408
|
+
}
|
|
2409
|
+
return {
|
|
2410
|
+
key,
|
|
2411
|
+
label: toScoreLabel(key),
|
|
2412
|
+
score
|
|
2413
|
+
};
|
|
2414
|
+
}).filter((item) => item !== null);
|
|
2415
|
+
return parsed.sort((a, b) => {
|
|
2416
|
+
const indexA = preferredOrder.indexOf(a.key);
|
|
2417
|
+
const indexB = preferredOrder.indexOf(b.key);
|
|
2418
|
+
const rankA = indexA === -1 ? Number.MAX_SAFE_INTEGER : indexA;
|
|
2419
|
+
const rankB = indexB === -1 ? Number.MAX_SAFE_INTEGER : indexB;
|
|
2420
|
+
if (rankA !== rankB) {
|
|
2421
|
+
return rankA - rankB;
|
|
2422
|
+
}
|
|
2423
|
+
return a.label.localeCompare(b.label);
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
function normalizeInfoTarget(input) {
|
|
2427
|
+
const trimmed = input.trim();
|
|
2428
|
+
if (!trimmed) {
|
|
2429
|
+
return trimmed;
|
|
2430
|
+
}
|
|
2431
|
+
const withoutGh = trimmed.replace(/^gh:/i, "");
|
|
2432
|
+
const askillSkillUrl = withoutGh.match(/^https?:\/\/askill\.sh\/skills\/(.+)$/i);
|
|
2433
|
+
if (askillSkillUrl && askillSkillUrl[1]) {
|
|
2434
|
+
return askillSkillUrl[1];
|
|
2435
|
+
}
|
|
2436
|
+
return withoutGh;
|
|
2437
|
+
}
|
|
2438
|
+
function parseSearchOptions(args) {
|
|
2439
|
+
const fullDesc = args.includes("--full-desc");
|
|
2440
|
+
const query = args.filter((arg) => arg !== "--full-desc").join(" ");
|
|
2441
|
+
return {
|
|
2442
|
+
fullDesc,
|
|
2443
|
+
query
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
1945
2446
|
async function runSearch(args) {
|
|
1946
|
-
const query = args
|
|
2447
|
+
const { fullDesc, query } = parseSearchOptions(args);
|
|
1947
2448
|
console.log();
|
|
1948
2449
|
p.intro(pc.bgCyan(pc.black(" askill search ")));
|
|
1949
2450
|
const spinner2 = p.spinner();
|
|
@@ -1962,9 +2463,15 @@ async function runSearch(args) {
|
|
|
1962
2463
|
const displayName = skill.name || "unknown";
|
|
1963
2464
|
const owner = skill.owner || "unknown";
|
|
1964
2465
|
const description = skill.description || "";
|
|
2466
|
+
const aiScore = getTotalAIScore(skill);
|
|
1965
2467
|
console.log(` ${pc.cyan(displayName)} ${pc.dim(`by ${owner}`)}`);
|
|
2468
|
+
console.log(` ${pc.dim("AI score:")} ${formatScore(aiScore)}`);
|
|
1966
2469
|
if (description) {
|
|
1967
|
-
|
|
2470
|
+
if (fullDesc) {
|
|
2471
|
+
console.log(` ${pc.dim(description)}`);
|
|
2472
|
+
} else {
|
|
2473
|
+
console.log(` ${pc.dim(description.slice(0, SEARCH_DESCRIPTION_MAX_LENGTH))}${description.length > SEARCH_DESCRIPTION_MAX_LENGTH ? "..." : ""}`);
|
|
2474
|
+
}
|
|
1968
2475
|
}
|
|
1969
2476
|
const installCmd = skill.owner && skill.repo ? `gh:${skill.owner}/${skill.repo}@${displayName}` : `gh:${displayName}`;
|
|
1970
2477
|
console.log(` ${pc.dim("askill add")} ${installCmd}`);
|
|
@@ -2048,12 +2555,13 @@ async function runRemove(args) {
|
|
|
2048
2555
|
p.outro(pc.green(`Removed ${skillName} from ${agentsWithSkill.length} agent(s)`));
|
|
2049
2556
|
}
|
|
2050
2557
|
async function runInfo(args) {
|
|
2051
|
-
const
|
|
2052
|
-
if (!
|
|
2558
|
+
const inputTarget = args[0];
|
|
2559
|
+
if (!inputTarget) {
|
|
2053
2560
|
console.log(`${RED}Error: Missing skill name${RESET}`);
|
|
2054
2561
|
console.log(`Usage: askill info <skill-name>`);
|
|
2055
2562
|
process.exit(1);
|
|
2056
2563
|
}
|
|
2564
|
+
const skillName = normalizeInfoTarget(inputTarget);
|
|
2057
2565
|
console.log();
|
|
2058
2566
|
p.intro(pc.bgCyan(pc.black(" askill info ")));
|
|
2059
2567
|
const spinner2 = p.spinner();
|
|
@@ -2077,6 +2585,15 @@ async function runInfo(args) {
|
|
|
2077
2585
|
if (skill.stars !== null && skill.stars !== void 0) {
|
|
2078
2586
|
console.log(` ${pc.dim("Stars:")} ${skill.stars.toLocaleString()}`);
|
|
2079
2587
|
}
|
|
2588
|
+
const aiScore = getTotalAIScore(skill);
|
|
2589
|
+
console.log(` ${pc.dim("AI score:")} ${formatScore(aiScore)}`);
|
|
2590
|
+
const aiDimensions = getAIScoreDimensions(skill);
|
|
2591
|
+
if (aiDimensions.length > 0) {
|
|
2592
|
+
console.log(` ${pc.dim("AI breakdown:")}`);
|
|
2593
|
+
for (const dimension of aiDimensions) {
|
|
2594
|
+
console.log(` ${pc.dim(`${dimension.label}:`)} ${formatScore(dimension.score)}`);
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2080
2597
|
if (skill.tags && skill.tags.length > 0) {
|
|
2081
2598
|
console.log(` ${pc.dim("Tags:")} ${skill.tags.join(", ")}`);
|
|
2082
2599
|
}
|
|
@@ -2312,33 +2829,33 @@ async function findSkillDir(skillName) {
|
|
|
2312
2829
|
const { access: fsAccess } = await import("fs/promises");
|
|
2313
2830
|
const sanitized = sanitizeName(skillName);
|
|
2314
2831
|
const cwd = process.cwd();
|
|
2315
|
-
const projectCanonical =
|
|
2832
|
+
const projectCanonical = join9(cwd, AGENTS_DIR, SKILLS_SUBDIR, sanitized);
|
|
2316
2833
|
try {
|
|
2317
|
-
await fsAccess(
|
|
2834
|
+
await fsAccess(join9(projectCanonical, "SKILL.md"));
|
|
2318
2835
|
return projectCanonical;
|
|
2319
2836
|
} catch {
|
|
2320
2837
|
}
|
|
2321
2838
|
const commonAgentDirs = [".claude/skills", ".cursor/skills", ".opencode/skills", ".windsurf/skills"];
|
|
2322
2839
|
for (const dir of commonAgentDirs) {
|
|
2323
|
-
const agentPath =
|
|
2840
|
+
const agentPath = join9(cwd, dir, sanitized);
|
|
2324
2841
|
try {
|
|
2325
|
-
await fsAccess(
|
|
2842
|
+
await fsAccess(join9(agentPath, "SKILL.md"));
|
|
2326
2843
|
return agentPath;
|
|
2327
2844
|
} catch {
|
|
2328
2845
|
}
|
|
2329
2846
|
}
|
|
2330
|
-
const home2 =
|
|
2331
|
-
const globalCanonical =
|
|
2847
|
+
const home2 = homedir7();
|
|
2848
|
+
const globalCanonical = join9(home2, AGENTS_DIR, SKILLS_SUBDIR, sanitized);
|
|
2332
2849
|
try {
|
|
2333
|
-
await fsAccess(
|
|
2850
|
+
await fsAccess(join9(globalCanonical, "SKILL.md"));
|
|
2334
2851
|
return globalCanonical;
|
|
2335
2852
|
} catch {
|
|
2336
2853
|
}
|
|
2337
2854
|
const globalAgentDirs = [".claude/skills", ".cursor/skills", ".opencode/skills"];
|
|
2338
2855
|
for (const dir of globalAgentDirs) {
|
|
2339
|
-
const agentPath =
|
|
2856
|
+
const agentPath = join9(home2, dir, sanitized);
|
|
2340
2857
|
try {
|
|
2341
|
-
await fsAccess(
|
|
2858
|
+
await fsAccess(join9(agentPath, "SKILL.md"));
|
|
2342
2859
|
return agentPath;
|
|
2343
2860
|
} catch {
|
|
2344
2861
|
}
|
|
@@ -2376,7 +2893,7 @@ Examples:`);
|
|
|
2376
2893
|
process.exit(1);
|
|
2377
2894
|
}
|
|
2378
2895
|
const fs = await import("fs/promises");
|
|
2379
|
-
const skillMdPath =
|
|
2896
|
+
const skillMdPath = join9(skillDir, "SKILL.md");
|
|
2380
2897
|
const content = await fs.readFile(skillMdPath, "utf-8");
|
|
2381
2898
|
const { frontmatter } = parseSkillMd(content);
|
|
2382
2899
|
if (!frontmatter.commands || Object.keys(frontmatter.commands).length === 0) {
|
|
@@ -2520,9 +3037,9 @@ function validateFrontmatter(frontmatter) {
|
|
|
2520
3037
|
async function runValidate(args) {
|
|
2521
3038
|
let targetPath = args.find((a) => !a.startsWith("-")) || "SKILL.md";
|
|
2522
3039
|
if (!targetPath.endsWith("SKILL.md")) {
|
|
2523
|
-
targetPath =
|
|
3040
|
+
targetPath = join9(targetPath, "SKILL.md");
|
|
2524
3041
|
}
|
|
2525
|
-
const absolutePath =
|
|
3042
|
+
const absolutePath = join9(process.cwd(), targetPath);
|
|
2526
3043
|
console.log();
|
|
2527
3044
|
p.intro(pc.bgCyan(pc.black(" askill validate ")));
|
|
2528
3045
|
const spinner2 = p.spinner();
|
|
@@ -2607,7 +3124,7 @@ async function runInit(args) {
|
|
|
2607
3124
|
const isYes = args.includes("-y") || args.includes("--yes");
|
|
2608
3125
|
console.log();
|
|
2609
3126
|
p.intro(pc.bgCyan(pc.black(" askill init ")));
|
|
2610
|
-
const skillPath =
|
|
3127
|
+
const skillPath = join9(process.cwd(), targetDir, "SKILL.md");
|
|
2611
3128
|
try {
|
|
2612
3129
|
await import("fs").then((fs2) => fs2.promises.access(skillPath));
|
|
2613
3130
|
p.log.error(`SKILL.md already exists at ${pc.cyan(skillPath)}`);
|
|
@@ -2729,7 +3246,7 @@ async function runInit(args) {
|
|
|
2729
3246
|
`;
|
|
2730
3247
|
content += `Provide concrete examples of when and how to use this skill.
|
|
2731
3248
|
`;
|
|
2732
|
-
const targetPath =
|
|
3249
|
+
const targetPath = join9(process.cwd(), targetDir);
|
|
2733
3250
|
if (targetDir !== ".") {
|
|
2734
3251
|
const fs2 = await import("fs");
|
|
2735
3252
|
await fs2.promises.mkdir(targetPath, { recursive: true });
|
|
@@ -2745,16 +3262,195 @@ async function runInit(args) {
|
|
|
2745
3262
|
console.log();
|
|
2746
3263
|
p.outro(pc.green("Done!"));
|
|
2747
3264
|
}
|
|
3265
|
+
async function runSubmit(args) {
|
|
3266
|
+
const url = args.find((a) => !a.startsWith("-"));
|
|
3267
|
+
if (!url) {
|
|
3268
|
+
console.log(`${RED}Usage: askill submit <github-url>${RESET}`);
|
|
3269
|
+
process.exit(1);
|
|
3270
|
+
}
|
|
3271
|
+
console.log();
|
|
3272
|
+
p.intro(pc.bgCyan(pc.black(" askill submit ")));
|
|
3273
|
+
const spinner2 = p.spinner();
|
|
3274
|
+
spinner2.start("Submitting URL for indexing...");
|
|
3275
|
+
try {
|
|
3276
|
+
const result = await api.submit(url);
|
|
3277
|
+
spinner2.stop(pc.green(result.message));
|
|
3278
|
+
console.log();
|
|
3279
|
+
for (const skill of result.skills) {
|
|
3280
|
+
const statusColor = skill.status === "indexed" ? pc.green : skill.status === "skipped" ? pc.yellow : pc.red;
|
|
3281
|
+
console.log(` ${statusColor(skill.status.padEnd(7))} ${pc.dim(skill.path)}${skill.name ? ` (${skill.name})` : ""}`);
|
|
3282
|
+
}
|
|
3283
|
+
p.outro(pc.green(`Submitted ${pc.cyan(`${result.repoOwner}/${result.repoName}`)}`));
|
|
3284
|
+
} catch (error) {
|
|
3285
|
+
spinner2.stop(pc.red("Submit failed"));
|
|
3286
|
+
if (error instanceof APIError) {
|
|
3287
|
+
p.log.error(error.message);
|
|
3288
|
+
} else {
|
|
3289
|
+
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
3290
|
+
}
|
|
3291
|
+
p.outro(pc.red("Failed"));
|
|
3292
|
+
process.exit(1);
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
async function runLogin(args) {
|
|
3296
|
+
let token = "";
|
|
3297
|
+
const tokenFlagIndex = args.findIndex((a) => a === "--token");
|
|
3298
|
+
if (tokenFlagIndex >= 0 && args[tokenFlagIndex + 1]) {
|
|
3299
|
+
token = args[tokenFlagIndex + 1];
|
|
3300
|
+
}
|
|
3301
|
+
if (!token) {
|
|
3302
|
+
console.log();
|
|
3303
|
+
p.note(`To get your token, visit: ${pc.cyan(`${REGISTRY_URL}/account`)}`);
|
|
3304
|
+
const input = await p.password({
|
|
3305
|
+
message: "API token",
|
|
3306
|
+
placeholder: "ask_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
3307
|
+
mask: "*",
|
|
3308
|
+
validate: (value) => {
|
|
3309
|
+
if (!value) return "Token is required";
|
|
3310
|
+
if (!value.startsWith("ask_")) return "Token must start with ask_";
|
|
3311
|
+
return void 0;
|
|
3312
|
+
}
|
|
3313
|
+
});
|
|
3314
|
+
if (p.isCancel(input)) {
|
|
3315
|
+
p.cancel("Login cancelled");
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
token = input;
|
|
3319
|
+
}
|
|
3320
|
+
const spinner2 = p.spinner();
|
|
3321
|
+
spinner2.start("Verifying token...");
|
|
3322
|
+
try {
|
|
3323
|
+
const me = await api.authMe(token);
|
|
3324
|
+
const username = me.username ?? void 0;
|
|
3325
|
+
await saveCredentials({ token, username });
|
|
3326
|
+
spinner2.stop(pc.green(`Logged in as @${username ?? "unknown"}`));
|
|
3327
|
+
p.outro(pc.green("Authentication saved"));
|
|
3328
|
+
} catch (error) {
|
|
3329
|
+
spinner2.stop(pc.red("Invalid token"));
|
|
3330
|
+
if (error instanceof APIError) {
|
|
3331
|
+
p.log.error(error.message);
|
|
3332
|
+
} else {
|
|
3333
|
+
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
3334
|
+
}
|
|
3335
|
+
p.outro(pc.red("Login failed"));
|
|
3336
|
+
process.exit(1);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
async function runLogout() {
|
|
3340
|
+
await clearCredentials();
|
|
3341
|
+
p.outro(pc.green("Logged out"));
|
|
3342
|
+
}
|
|
3343
|
+
async function runWhoami() {
|
|
3344
|
+
const creds = await loadCredentials();
|
|
3345
|
+
if (!creds) {
|
|
3346
|
+
p.log.warn("Not logged in. Run askill login first.");
|
|
3347
|
+
return;
|
|
3348
|
+
}
|
|
3349
|
+
try {
|
|
3350
|
+
const me = await api.authMe(creds.token);
|
|
3351
|
+
const username = me.username ?? creds.username ?? "unknown";
|
|
3352
|
+
console.log(`@${username} (token: ${maskToken(creds.token)})`);
|
|
3353
|
+
} catch {
|
|
3354
|
+
console.log(`Stored token appears invalid (token: ${maskToken(creds.token)})`);
|
|
3355
|
+
process.exit(1);
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
async function runPublish(args) {
|
|
3359
|
+
const creds = await loadCredentials();
|
|
3360
|
+
if (!creds?.token) {
|
|
3361
|
+
p.log.error("Not logged in. Run askill login first.");
|
|
3362
|
+
process.exit(1);
|
|
3363
|
+
}
|
|
3364
|
+
const githubFlagIndex = args.findIndex((a) => a === "--github");
|
|
3365
|
+
const githubUrl = githubFlagIndex >= 0 ? args[githubFlagIndex + 1] : void 0;
|
|
3366
|
+
const localPath = args.find((a) => !a.startsWith("-")) || ".";
|
|
3367
|
+
let content = "";
|
|
3368
|
+
if (githubUrl) {
|
|
3369
|
+
const rawUrl = toRawGitHubUrl(githubUrl);
|
|
3370
|
+
if (!rawUrl) {
|
|
3371
|
+
p.log.error("Invalid GitHub file URL. Use a blob URL to SKILL.md");
|
|
3372
|
+
process.exit(1);
|
|
3373
|
+
}
|
|
3374
|
+
const spinner3 = p.spinner();
|
|
3375
|
+
spinner3.start("Fetching SKILL.md from GitHub...");
|
|
3376
|
+
const res = await fetch(rawUrl);
|
|
3377
|
+
if (!res.ok) {
|
|
3378
|
+
spinner3.stop(pc.red("Fetch failed"));
|
|
3379
|
+
p.log.error("Unable to fetch SKILL.md from GitHub URL");
|
|
3380
|
+
process.exit(1);
|
|
3381
|
+
}
|
|
3382
|
+
content = await res.text();
|
|
3383
|
+
spinner3.stop("Fetched");
|
|
3384
|
+
} else {
|
|
3385
|
+
const fs = await import("fs/promises");
|
|
3386
|
+
const skillPath = localPath.endsWith("SKILL.md") ? localPath : join9(localPath, "SKILL.md");
|
|
3387
|
+
try {
|
|
3388
|
+
content = await fs.readFile(join9(process.cwd(), skillPath), "utf-8");
|
|
3389
|
+
} catch {
|
|
3390
|
+
p.log.error(`Cannot read ${pc.cyan(skillPath)}`);
|
|
3391
|
+
process.exit(1);
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
const parsed = parseSkillMd(content);
|
|
3395
|
+
const name = typeof parsed.frontmatter.name === "string" ? parsed.frontmatter.name.trim() : "";
|
|
3396
|
+
const version = typeof parsed.frontmatter.version === "string" ? parsed.frontmatter.version.trim() : "";
|
|
3397
|
+
if (!name) {
|
|
3398
|
+
p.log.error("SKILL.md must include frontmatter name");
|
|
3399
|
+
process.exit(1);
|
|
3400
|
+
}
|
|
3401
|
+
if (!version) {
|
|
3402
|
+
p.log.error('SKILL.md is missing frontmatter field "version".');
|
|
3403
|
+
p.log.info("Add a semver version, for example: version: 0.1.0");
|
|
3404
|
+
p.log.info("Valid examples: 1.0.0, 1.2.3-beta.1, 2.0.0+build.5");
|
|
3405
|
+
process.exit(1);
|
|
3406
|
+
}
|
|
3407
|
+
if (!/^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/.test(version)) {
|
|
3408
|
+
p.log.error(`Invalid semver version in SKILL.md: "${version}"`);
|
|
3409
|
+
p.log.info("Expected format: MAJOR.MINOR.PATCH with optional prerelease/build");
|
|
3410
|
+
p.log.info("Examples: 1.0.0, 1.1.0-beta.1, 2.0.0+build.7");
|
|
3411
|
+
process.exit(1);
|
|
3412
|
+
}
|
|
3413
|
+
console.log();
|
|
3414
|
+
p.intro(pc.bgCyan(pc.black(" askill publish ")));
|
|
3415
|
+
const spinner2 = p.spinner();
|
|
3416
|
+
spinner2.start("Publishing skill...");
|
|
3417
|
+
try {
|
|
3418
|
+
const result = await api.publish({
|
|
3419
|
+
token: creds.token,
|
|
3420
|
+
githubUrl,
|
|
3421
|
+
content: githubUrl ? void 0 : content
|
|
3422
|
+
});
|
|
3423
|
+
spinner2.stop(pc.green(`Published ${result.slug}@${result.version}`));
|
|
3424
|
+
p.outro(pc.cyan(result.url));
|
|
3425
|
+
} catch (error) {
|
|
3426
|
+
spinner2.stop(pc.red("Publish failed"));
|
|
3427
|
+
if (error instanceof APIError) {
|
|
3428
|
+
p.log.error(error.message);
|
|
3429
|
+
} else {
|
|
3430
|
+
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
3431
|
+
}
|
|
3432
|
+
p.outro(pc.red("Failed"));
|
|
3433
|
+
process.exit(1);
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
function toRawGitHubUrl(url) {
|
|
3437
|
+
const match = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/[^/]+\/(.+)$/);
|
|
3438
|
+
if (!match) return null;
|
|
3439
|
+
const [, owner, repo, path] = match;
|
|
3440
|
+
return `https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${path}`;
|
|
3441
|
+
}
|
|
2748
3442
|
async function main() {
|
|
2749
3443
|
const args = process.argv.slice(2);
|
|
2750
|
-
checkForUpdates().catch(() => {
|
|
2751
|
-
});
|
|
2752
3444
|
if (args.length === 0) {
|
|
2753
3445
|
showBanner();
|
|
2754
3446
|
return;
|
|
2755
3447
|
}
|
|
2756
3448
|
const command = args[0];
|
|
2757
3449
|
const restArgs = args.slice(1);
|
|
3450
|
+
if ((restArgs.includes("--help") || restArgs.includes("-h")) && showCommandHelp(command)) {
|
|
3451
|
+
return;
|
|
3452
|
+
}
|
|
3453
|
+
await maybeAutoUpgradeOnStartup(command);
|
|
2758
3454
|
switch (command) {
|
|
2759
3455
|
case "install":
|
|
2760
3456
|
case "i":
|
|
@@ -2797,9 +3493,27 @@ async function main() {
|
|
|
2797
3493
|
case "init":
|
|
2798
3494
|
await runInit(restArgs);
|
|
2799
3495
|
break;
|
|
3496
|
+
case "submit":
|
|
3497
|
+
await runSubmit(restArgs);
|
|
3498
|
+
break;
|
|
3499
|
+
case "login":
|
|
3500
|
+
await runLogin(restArgs);
|
|
3501
|
+
break;
|
|
3502
|
+
case "logout":
|
|
3503
|
+
await runLogout();
|
|
3504
|
+
break;
|
|
3505
|
+
case "whoami":
|
|
3506
|
+
await runWhoami();
|
|
3507
|
+
break;
|
|
3508
|
+
case "publish":
|
|
3509
|
+
await runPublish(restArgs);
|
|
3510
|
+
break;
|
|
2800
3511
|
case "--help":
|
|
2801
3512
|
case "-h":
|
|
2802
3513
|
case "help":
|
|
3514
|
+
if (restArgs[0] && showCommandHelp(restArgs[0])) {
|
|
3515
|
+
break;
|
|
3516
|
+
}
|
|
2803
3517
|
showHelp();
|
|
2804
3518
|
break;
|
|
2805
3519
|
case "--version":
|