askill-cli 0.1.1 → 0.1.3
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 +13 -3
- package/dist/cli.mjs +444 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,13 @@ askill list
|
|
|
24
24
|
# Search for skills
|
|
25
25
|
askill find <query>
|
|
26
26
|
|
|
27
|
+
# Submit a GitHub skill URL for indexing
|
|
28
|
+
askill submit https://github.com/owner/repo
|
|
29
|
+
|
|
30
|
+
# Login and publish your own skill
|
|
31
|
+
askill login --token ask_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
32
|
+
askill publish
|
|
33
|
+
|
|
27
34
|
# Run a skill command
|
|
28
35
|
askill run skill-name:command
|
|
29
36
|
```
|
|
@@ -50,11 +57,13 @@ After installation, read the skill's `SKILL.md` file for usage instructions. Ski
|
|
|
50
57
|
|
|
51
58
|
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
59
|
|
|
60
|
+
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.
|
|
61
|
+
|
|
53
62
|
## Quick Start
|
|
54
63
|
|
|
55
64
|
```bash
|
|
56
65
|
# Install
|
|
57
|
-
curl -fsSL https://askill.sh
|
|
66
|
+
curl -fsSL https://askill.sh | sh
|
|
58
67
|
|
|
59
68
|
# Install a skill
|
|
60
69
|
askill add owner/repo@skill-name
|
|
@@ -71,7 +80,7 @@ askill list
|
|
|
71
80
|
### One-line install (recommended)
|
|
72
81
|
|
|
73
82
|
```bash
|
|
74
|
-
curl -fsSL https://askill.sh
|
|
83
|
+
curl -fsSL https://askill.sh | sh
|
|
75
84
|
```
|
|
76
85
|
|
|
77
86
|
### npm
|
|
@@ -175,7 +184,8 @@ Instructions for the agent...
|
|
|
175
184
|
|
|
176
185
|
2. Test locally: `askill add ./my-skill`
|
|
177
186
|
3. Validate: `askill validate`
|
|
178
|
-
4.
|
|
187
|
+
4. Submit for indexing: `askill submit https://github.com/<owner>/<repo>`
|
|
188
|
+
5. Publish under your author scope: `askill login` then `askill publish`
|
|
179
189
|
|
|
180
190
|
See [Publishing Guide](./docs/publishing.md) for details.
|
|
181
191
|
|
package/dist/cli.mjs
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
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.3";
|
|
8
8
|
var API_BASE_URL = "https://askill.sh/api/v1";
|
|
9
|
+
var REGISTRY_URL = "https://askill.sh";
|
|
9
10
|
var RESET = "\x1B[0m";
|
|
10
11
|
var BOLD = "\x1B[1m";
|
|
11
12
|
var DIM = "\x1B[2m";
|
|
@@ -395,6 +396,40 @@ var APIClient = class {
|
|
|
395
396
|
async checkCLIVersion() {
|
|
396
397
|
return this.fetch("/cli/version");
|
|
397
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
|
+
}
|
|
398
433
|
};
|
|
399
434
|
var APIError = class extends Error {
|
|
400
435
|
constructor(status, code, message) {
|
|
@@ -694,8 +729,8 @@ function getPlatformKey() {
|
|
|
694
729
|
}
|
|
695
730
|
async function shouldCheckUpdate() {
|
|
696
731
|
try {
|
|
697
|
-
const { readFile:
|
|
698
|
-
const lastCheck = await
|
|
732
|
+
const { readFile: readFile5 } = await import("fs/promises");
|
|
733
|
+
const lastCheck = await readFile5(UPDATE_CHECK_FILE, "utf-8");
|
|
699
734
|
const lastCheckTime = parseInt(lastCheck, 10);
|
|
700
735
|
return Date.now() - lastCheckTime > UPDATE_INTERVAL_MS;
|
|
701
736
|
} catch {
|
|
@@ -704,9 +739,9 @@ async function shouldCheckUpdate() {
|
|
|
704
739
|
}
|
|
705
740
|
async function saveUpdateCheckTime() {
|
|
706
741
|
try {
|
|
707
|
-
const { mkdir:
|
|
708
|
-
await
|
|
709
|
-
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");
|
|
710
745
|
} catch {
|
|
711
746
|
}
|
|
712
747
|
}
|
|
@@ -795,7 +830,7 @@ async function selfUpdate() {
|
|
|
795
830
|
if (!downloadUrl) {
|
|
796
831
|
console.log(`${RED}No download available for your platform (${platformKey})${RESET}`);
|
|
797
832
|
console.log(`Please update manually:`);
|
|
798
|
-
console.log(` ${CYAN}curl -fsSL https://askill.sh
|
|
833
|
+
console.log(` ${CYAN}curl -fsSL https://askill.sh | sh${RESET}`);
|
|
799
834
|
console.log(` ${DIM}or${RESET}`);
|
|
800
835
|
console.log(` ${CYAN}npm install -g askill-cli@latest${RESET}`);
|
|
801
836
|
return false;
|
|
@@ -837,7 +872,7 @@ async function selfUpdate() {
|
|
|
837
872
|
} catch (error) {
|
|
838
873
|
console.log(`${RED}Update failed: ${error instanceof Error ? error.message : "Unknown error"}${RESET}`);
|
|
839
874
|
console.log(`Please update manually:`);
|
|
840
|
-
console.log(` ${CYAN}curl -fsSL https://askill.sh
|
|
875
|
+
console.log(` ${CYAN}curl -fsSL https://askill.sh | sh${RESET}`);
|
|
841
876
|
console.log(` ${DIM}or${RESET}`);
|
|
842
877
|
console.log(` ${CYAN}npm install -g askill-cli@latest${RESET}`);
|
|
843
878
|
return false;
|
|
@@ -864,6 +899,37 @@ async function getPreferredAgents() {
|
|
|
864
899
|
return config.preferredAgents;
|
|
865
900
|
}
|
|
866
901
|
|
|
902
|
+
// src/credentials.ts
|
|
903
|
+
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3, rm as rm2 } from "fs/promises";
|
|
904
|
+
import { join as join5 } from "path";
|
|
905
|
+
import { homedir as homedir5 } from "os";
|
|
906
|
+
var ASKILL_DIR = join5(homedir5(), ".askill");
|
|
907
|
+
var CREDENTIALS_FILE = join5(ASKILL_DIR, "credentials.json");
|
|
908
|
+
async function loadCredentials() {
|
|
909
|
+
try {
|
|
910
|
+
const content = await readFile2(CREDENTIALS_FILE, "utf-8");
|
|
911
|
+
const parsed = JSON.parse(content);
|
|
912
|
+
if (!parsed.token) return null;
|
|
913
|
+
return parsed;
|
|
914
|
+
} catch {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async function saveCredentials(credentials) {
|
|
919
|
+
await mkdir3(ASKILL_DIR, { recursive: true });
|
|
920
|
+
await writeFile3(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), "utf-8");
|
|
921
|
+
}
|
|
922
|
+
async function clearCredentials() {
|
|
923
|
+
try {
|
|
924
|
+
await rm2(CREDENTIALS_FILE);
|
|
925
|
+
} catch {
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function maskToken(token) {
|
|
929
|
+
if (token.length <= 8) return token;
|
|
930
|
+
return `${token.slice(0, 8)}****`;
|
|
931
|
+
}
|
|
932
|
+
|
|
867
933
|
// src/parser.ts
|
|
868
934
|
function parseSkillMd(content) {
|
|
869
935
|
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
@@ -1126,12 +1192,12 @@ function isLocalPath(input) {
|
|
|
1126
1192
|
}
|
|
1127
1193
|
|
|
1128
1194
|
// src/discover.ts
|
|
1129
|
-
import { readdir as readdir2, readFile as
|
|
1130
|
-
import { join as
|
|
1195
|
+
import { readdir as readdir2, readFile as readFile3, stat } from "fs/promises";
|
|
1196
|
+
import { join as join6 } from "path";
|
|
1131
1197
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "__pycache__"]);
|
|
1132
1198
|
async function hasSkillMd(dir) {
|
|
1133
1199
|
try {
|
|
1134
|
-
const skillPath =
|
|
1200
|
+
const skillPath = join6(dir, "SKILL.md");
|
|
1135
1201
|
const stats = await stat(skillPath);
|
|
1136
1202
|
return stats.isFile();
|
|
1137
1203
|
} catch {
|
|
@@ -1140,8 +1206,8 @@ async function hasSkillMd(dir) {
|
|
|
1140
1206
|
}
|
|
1141
1207
|
async function parseSkillDir(dir) {
|
|
1142
1208
|
try {
|
|
1143
|
-
const skillPath =
|
|
1144
|
-
const content = await
|
|
1209
|
+
const skillPath = join6(dir, "SKILL.md");
|
|
1210
|
+
const content = await readFile3(skillPath, "utf-8");
|
|
1145
1211
|
const parsed = parseSkillMd(content);
|
|
1146
1212
|
if (!parsed.frontmatter.name || !parsed.frontmatter.description) {
|
|
1147
1213
|
return null;
|
|
@@ -1166,7 +1232,7 @@ async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
|
|
|
1166
1232
|
]);
|
|
1167
1233
|
const currentDir = hasSkill ? [dir] : [];
|
|
1168
1234
|
const subDirResults = await Promise.all(
|
|
1169
|
-
entries.filter((entry) => entry.isDirectory() && !SKIP_DIRS.has(entry.name)).map((entry) => findSkillDirs(
|
|
1235
|
+
entries.filter((entry) => entry.isDirectory() && !SKIP_DIRS.has(entry.name)).map((entry) => findSkillDirs(join6(dir, entry.name), depth + 1, maxDepth))
|
|
1170
1236
|
);
|
|
1171
1237
|
return [...currentDir, ...subDirResults.flat()];
|
|
1172
1238
|
} catch {
|
|
@@ -1176,7 +1242,7 @@ async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
|
|
|
1176
1242
|
async function discoverSkills(basePath, subpath) {
|
|
1177
1243
|
const skills = [];
|
|
1178
1244
|
const seenNames = /* @__PURE__ */ new Set();
|
|
1179
|
-
const searchPath = subpath ?
|
|
1245
|
+
const searchPath = subpath ? join6(basePath, subpath) : basePath;
|
|
1180
1246
|
if (await hasSkillMd(searchPath)) {
|
|
1181
1247
|
const skill = await parseSkillDir(searchPath);
|
|
1182
1248
|
if (skill) {
|
|
@@ -1185,27 +1251,27 @@ async function discoverSkills(basePath, subpath) {
|
|
|
1185
1251
|
}
|
|
1186
1252
|
const prioritySearchDirs = [
|
|
1187
1253
|
searchPath,
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1254
|
+
join6(searchPath, "skills"),
|
|
1255
|
+
join6(searchPath, "skills/.curated"),
|
|
1256
|
+
join6(searchPath, "skills/.experimental"),
|
|
1257
|
+
join6(searchPath, ".agents/skills"),
|
|
1258
|
+
join6(searchPath, ".claude/skills"),
|
|
1259
|
+
join6(searchPath, ".opencode/skills"),
|
|
1260
|
+
join6(searchPath, ".cursor/skills"),
|
|
1261
|
+
join6(searchPath, ".codex/skills"),
|
|
1262
|
+
join6(searchPath, ".cline/skills"),
|
|
1263
|
+
join6(searchPath, ".gemini/skills"),
|
|
1264
|
+
join6(searchPath, ".windsurf/skills"),
|
|
1265
|
+
join6(searchPath, ".roo/skills"),
|
|
1266
|
+
join6(searchPath, ".github/skills"),
|
|
1267
|
+
join6(searchPath, ".goose/skills")
|
|
1202
1268
|
];
|
|
1203
1269
|
for (const dir of prioritySearchDirs) {
|
|
1204
1270
|
try {
|
|
1205
1271
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
1206
1272
|
for (const entry of entries) {
|
|
1207
1273
|
if (entry.isDirectory()) {
|
|
1208
|
-
const skillDir =
|
|
1274
|
+
const skillDir = join6(dir, entry.name);
|
|
1209
1275
|
if (await hasSkillMd(skillDir)) {
|
|
1210
1276
|
const skill = await parseSkillDir(skillDir);
|
|
1211
1277
|
if (skill && !seenNames.has(skill.name)) {
|
|
@@ -1240,8 +1306,8 @@ function filterSkills(skills, names) {
|
|
|
1240
1306
|
|
|
1241
1307
|
// src/git.ts
|
|
1242
1308
|
import { execFile } from "child_process";
|
|
1243
|
-
import { mkdtemp, rm as
|
|
1244
|
-
import { join as
|
|
1309
|
+
import { mkdtemp, rm as rm3 } from "fs/promises";
|
|
1310
|
+
import { join as join7, normalize as normalize2, resolve as resolve3, sep as sep2 } from "path";
|
|
1245
1311
|
import { tmpdir } from "os";
|
|
1246
1312
|
var CLONE_TIMEOUT_MS = 6e4;
|
|
1247
1313
|
var GitCloneError = class extends Error {
|
|
@@ -1257,7 +1323,7 @@ var GitCloneError = class extends Error {
|
|
|
1257
1323
|
}
|
|
1258
1324
|
};
|
|
1259
1325
|
async function cloneRepo(url, ref) {
|
|
1260
|
-
const tempDir = await mkdtemp(
|
|
1326
|
+
const tempDir = await mkdtemp(join7(tmpdir(), "askill-"));
|
|
1261
1327
|
const args = ["clone", "--depth", "1"];
|
|
1262
1328
|
if (ref) {
|
|
1263
1329
|
args.push("--branch", ref);
|
|
@@ -1267,7 +1333,7 @@ async function cloneRepo(url, ref) {
|
|
|
1267
1333
|
await execGit(args);
|
|
1268
1334
|
return tempDir;
|
|
1269
1335
|
} catch (error) {
|
|
1270
|
-
await
|
|
1336
|
+
await rm3(tempDir, { recursive: true, force: true }).catch(() => {
|
|
1271
1337
|
});
|
|
1272
1338
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1273
1339
|
const isTimeout = errorMessage.includes("timed out") || errorMessage.includes("timeout");
|
|
@@ -1300,7 +1366,7 @@ async function cleanupTempDir(dir) {
|
|
|
1300
1366
|
if (!normalizedDir.startsWith(normalizedTmpDir + sep2) && normalizedDir !== normalizedTmpDir) {
|
|
1301
1367
|
throw new Error("Attempted to clean up directory outside of temp directory");
|
|
1302
1368
|
}
|
|
1303
|
-
await
|
|
1369
|
+
await rm3(dir, { recursive: true, force: true });
|
|
1304
1370
|
}
|
|
1305
1371
|
function execGit(args) {
|
|
1306
1372
|
return new Promise((resolve4, reject) => {
|
|
@@ -1315,14 +1381,14 @@ function execGit(args) {
|
|
|
1315
1381
|
}
|
|
1316
1382
|
|
|
1317
1383
|
// src/lock.ts
|
|
1318
|
-
import { readFile as
|
|
1319
|
-
import { join as
|
|
1320
|
-
import { homedir as
|
|
1384
|
+
import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
1385
|
+
import { join as join8, dirname as dirname5 } from "path";
|
|
1386
|
+
import { homedir as homedir6 } from "os";
|
|
1321
1387
|
var AGENTS_DIR2 = ".agents";
|
|
1322
1388
|
var LOCK_FILE = ".skill-lock.json";
|
|
1323
1389
|
var CURRENT_VERSION = 3;
|
|
1324
1390
|
function getSkillLockPath() {
|
|
1325
|
-
return
|
|
1391
|
+
return join8(homedir6(), AGENTS_DIR2, LOCK_FILE);
|
|
1326
1392
|
}
|
|
1327
1393
|
function createEmptyLockFile() {
|
|
1328
1394
|
return {
|
|
@@ -1333,7 +1399,7 @@ function createEmptyLockFile() {
|
|
|
1333
1399
|
async function readSkillLock() {
|
|
1334
1400
|
const lockPath = getSkillLockPath();
|
|
1335
1401
|
try {
|
|
1336
|
-
const content = await
|
|
1402
|
+
const content = await readFile4(lockPath, "utf-8");
|
|
1337
1403
|
const parsed = JSON.parse(content);
|
|
1338
1404
|
if (typeof parsed.version !== "number" || !parsed.skills) {
|
|
1339
1405
|
return createEmptyLockFile();
|
|
@@ -1348,9 +1414,9 @@ async function readSkillLock() {
|
|
|
1348
1414
|
}
|
|
1349
1415
|
async function writeSkillLock(lock) {
|
|
1350
1416
|
const lockPath = getSkillLockPath();
|
|
1351
|
-
await
|
|
1417
|
+
await mkdir4(dirname5(lockPath), { recursive: true });
|
|
1352
1418
|
const content = JSON.stringify(lock, null, 2);
|
|
1353
|
-
await
|
|
1419
|
+
await writeFile4(lockPath, content, "utf-8");
|
|
1354
1420
|
}
|
|
1355
1421
|
async function addSkillToLock(skillName, entry) {
|
|
1356
1422
|
const lock = await readSkillLock();
|
|
@@ -1424,8 +1490,8 @@ async function fetchSkillFolderHash(ownerRepo, skillPath) {
|
|
|
1424
1490
|
}
|
|
1425
1491
|
|
|
1426
1492
|
// src/cli.ts
|
|
1427
|
-
import { join as
|
|
1428
|
-
import { homedir as
|
|
1493
|
+
import { join as join9 } from "path";
|
|
1494
|
+
import { homedir as homedir7 } from "os";
|
|
1429
1495
|
import * as p from "@clack/prompts";
|
|
1430
1496
|
import pc from "picocolors";
|
|
1431
1497
|
var LOGO = `
|
|
@@ -1454,6 +1520,9 @@ function showBanner() {
|
|
|
1454
1520
|
console.log(` ${DIM}$${RESET} askill list${RESET} ${DIM}List installed skills${RESET}`);
|
|
1455
1521
|
console.log(` ${DIM}$${RESET} askill remove ${DIM}<skill>${RESET} ${DIM}Remove a skill${RESET}`);
|
|
1456
1522
|
console.log(` ${DIM}$${RESET} askill init${RESET} ${DIM}Create a new skill${RESET}`);
|
|
1523
|
+
console.log(` ${DIM}$${RESET} askill submit ${DIM}<url>${RESET} ${DIM}Submit GitHub skill URL${RESET}`);
|
|
1524
|
+
console.log(` ${DIM}$${RESET} askill login${RESET} ${DIM}Login with API token${RESET}`);
|
|
1525
|
+
console.log(` ${DIM}$${RESET} askill publish${RESET} ${DIM}Publish a skill${RESET}`);
|
|
1457
1526
|
console.log(` ${DIM}$${RESET} askill run ${DIM}<skill:cmd>${RESET} ${DIM}Run a skill command${RESET}`);
|
|
1458
1527
|
console.log();
|
|
1459
1528
|
console.log(`${DIM}Browse skills at${RESET} ${CYAN}https://askill.sh${RESET}`);
|
|
@@ -1473,6 +1542,12 @@ ${BOLD}Commands:${RESET}
|
|
|
1473
1542
|
validate [path] Validate a SKILL.md file
|
|
1474
1543
|
check Check installed skills for updates
|
|
1475
1544
|
update [skill] Update installed skills
|
|
1545
|
+
submit <github-url> Submit GitHub URL for indexing
|
|
1546
|
+
login [--token <token>] Login with API token
|
|
1547
|
+
logout Clear saved API token
|
|
1548
|
+
whoami Show current authenticated user
|
|
1549
|
+
publish [path] Publish SKILL.md from local path
|
|
1550
|
+
publish --github <url> Publish SKILL.md from GitHub URL
|
|
1476
1551
|
run <skill:cmd> Run a skill command
|
|
1477
1552
|
upgrade Update askill CLI to latest version
|
|
1478
1553
|
|
|
@@ -1941,6 +2016,118 @@ Formats supported:`);
|
|
|
1941
2016
|
await cleanup();
|
|
1942
2017
|
}
|
|
1943
2018
|
}
|
|
2019
|
+
var SEARCH_DESCRIPTION_MAX_LENGTH = 180;
|
|
2020
|
+
function toNumber(value) {
|
|
2021
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2022
|
+
return null;
|
|
2023
|
+
}
|
|
2024
|
+
return value;
|
|
2025
|
+
}
|
|
2026
|
+
function parseJsonObject(value) {
|
|
2027
|
+
if (typeof value === "string") {
|
|
2028
|
+
try {
|
|
2029
|
+
const parsed = JSON.parse(value);
|
|
2030
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2031
|
+
return parsed;
|
|
2032
|
+
}
|
|
2033
|
+
return null;
|
|
2034
|
+
} catch {
|
|
2035
|
+
return null;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2039
|
+
return value;
|
|
2040
|
+
}
|
|
2041
|
+
return null;
|
|
2042
|
+
}
|
|
2043
|
+
function formatScore(score) {
|
|
2044
|
+
if (score === null) {
|
|
2045
|
+
return pc.dim("N/A");
|
|
2046
|
+
}
|
|
2047
|
+
return pc.green(Number.isInteger(score) ? String(score) : score.toFixed(1));
|
|
2048
|
+
}
|
|
2049
|
+
function toScoreLabel(key) {
|
|
2050
|
+
return key.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
2051
|
+
}
|
|
2052
|
+
function getScoreMeta(skill) {
|
|
2053
|
+
const withScoreMeta = skill;
|
|
2054
|
+
return parseJsonObject(withScoreMeta.llmScoreMeta);
|
|
2055
|
+
}
|
|
2056
|
+
function getTotalAIScore(skill) {
|
|
2057
|
+
const aiScore = toNumber(skill.aiScore);
|
|
2058
|
+
if (aiScore !== null) {
|
|
2059
|
+
return aiScore;
|
|
2060
|
+
}
|
|
2061
|
+
const directScore = toNumber(skill.llmScore);
|
|
2062
|
+
if (directScore !== null) {
|
|
2063
|
+
return directScore;
|
|
2064
|
+
}
|
|
2065
|
+
const meta = getScoreMeta(skill);
|
|
2066
|
+
if (!meta) {
|
|
2067
|
+
return null;
|
|
2068
|
+
}
|
|
2069
|
+
return toNumber(meta.score) ?? toNumber(meta.score_raw) ?? toNumber(meta.final_rank);
|
|
2070
|
+
}
|
|
2071
|
+
function getAIScoreDimensions(skill) {
|
|
2072
|
+
const directBreakdown = parseJsonObject(skill.aiBreakdown);
|
|
2073
|
+
if (directBreakdown) {
|
|
2074
|
+
const parsedDirect = Object.entries(directBreakdown).map(([key, value]) => {
|
|
2075
|
+
const score = toNumber(value);
|
|
2076
|
+
if (score === null) {
|
|
2077
|
+
return null;
|
|
2078
|
+
}
|
|
2079
|
+
return {
|
|
2080
|
+
key,
|
|
2081
|
+
label: toScoreLabel(key),
|
|
2082
|
+
score
|
|
2083
|
+
};
|
|
2084
|
+
}).filter((item) => item !== null);
|
|
2085
|
+
if (parsedDirect.length > 0) {
|
|
2086
|
+
const preferredOrder2 = ["completeness", "actionability", "reusability", "safety", "clarity", "internal_only"];
|
|
2087
|
+
return parsedDirect.sort((a, b) => {
|
|
2088
|
+
const indexA = preferredOrder2.indexOf(a.key);
|
|
2089
|
+
const indexB = preferredOrder2.indexOf(b.key);
|
|
2090
|
+
const rankA = indexA === -1 ? Number.MAX_SAFE_INTEGER : indexA;
|
|
2091
|
+
const rankB = indexB === -1 ? Number.MAX_SAFE_INTEGER : indexB;
|
|
2092
|
+
if (rankA !== rankB) {
|
|
2093
|
+
return rankA - rankB;
|
|
2094
|
+
}
|
|
2095
|
+
return a.label.localeCompare(b.label);
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
const meta = getScoreMeta(skill);
|
|
2100
|
+
if (!meta) {
|
|
2101
|
+
return [];
|
|
2102
|
+
}
|
|
2103
|
+
const dimensions = parseJsonObject(meta.dimensions);
|
|
2104
|
+
if (!dimensions) {
|
|
2105
|
+
return [];
|
|
2106
|
+
}
|
|
2107
|
+
const preferredOrder = ["completeness", "actionability", "reusability", "safety", "clarity", "internal_only"];
|
|
2108
|
+
const parsed = Object.entries(dimensions).map(([key, value]) => {
|
|
2109
|
+
const nested = parseJsonObject(value);
|
|
2110
|
+
const score = nested ? toNumber(nested.score) : toNumber(value);
|
|
2111
|
+
if (score === null) {
|
|
2112
|
+
return null;
|
|
2113
|
+
}
|
|
2114
|
+
return {
|
|
2115
|
+
key,
|
|
2116
|
+
label: toScoreLabel(key),
|
|
2117
|
+
score
|
|
2118
|
+
};
|
|
2119
|
+
}).filter((item) => item !== null);
|
|
2120
|
+
return parsed.sort((a, b) => {
|
|
2121
|
+
const indexA = preferredOrder.indexOf(a.key);
|
|
2122
|
+
const indexB = preferredOrder.indexOf(b.key);
|
|
2123
|
+
const rankA = indexA === -1 ? Number.MAX_SAFE_INTEGER : indexA;
|
|
2124
|
+
const rankB = indexB === -1 ? Number.MAX_SAFE_INTEGER : indexB;
|
|
2125
|
+
if (rankA !== rankB) {
|
|
2126
|
+
return rankA - rankB;
|
|
2127
|
+
}
|
|
2128
|
+
return a.label.localeCompare(b.label);
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
1944
2131
|
async function runSearch(args) {
|
|
1945
2132
|
const query = args.join(" ");
|
|
1946
2133
|
console.log();
|
|
@@ -1961,12 +2148,17 @@ async function runSearch(args) {
|
|
|
1961
2148
|
const displayName = skill.name || "unknown";
|
|
1962
2149
|
const owner = skill.owner || "unknown";
|
|
1963
2150
|
const description = skill.description || "";
|
|
2151
|
+
const aiScore = getTotalAIScore(skill);
|
|
1964
2152
|
console.log(` ${pc.cyan(displayName)} ${pc.dim(`by ${owner}`)}`);
|
|
2153
|
+
console.log(` ${pc.dim("AI score:")} ${formatScore(aiScore)}`);
|
|
1965
2154
|
if (description) {
|
|
1966
|
-
console.log(` ${pc.dim(description.slice(0,
|
|
2155
|
+
console.log(` ${pc.dim(description.slice(0, SEARCH_DESCRIPTION_MAX_LENGTH))}${description.length > SEARCH_DESCRIPTION_MAX_LENGTH ? "..." : ""}`);
|
|
1967
2156
|
}
|
|
1968
2157
|
const installCmd = skill.owner && skill.repo ? `gh:${skill.owner}/${skill.repo}@${displayName}` : `gh:${displayName}`;
|
|
1969
2158
|
console.log(` ${pc.dim("askill add")} ${installCmd}`);
|
|
2159
|
+
if (skill.id) {
|
|
2160
|
+
console.log(` ${pc.dim(REGISTRY_URL + "/skills/" + skill.id)}`);
|
|
2161
|
+
}
|
|
1970
2162
|
console.log();
|
|
1971
2163
|
}
|
|
1972
2164
|
p.outro(`Browse more at ${pc.cyan("https://askill.sh")}`);
|
|
@@ -2073,6 +2265,15 @@ async function runInfo(args) {
|
|
|
2073
2265
|
if (skill.stars !== null && skill.stars !== void 0) {
|
|
2074
2266
|
console.log(` ${pc.dim("Stars:")} ${skill.stars.toLocaleString()}`);
|
|
2075
2267
|
}
|
|
2268
|
+
const aiScore = getTotalAIScore(skill);
|
|
2269
|
+
console.log(` ${pc.dim("AI score:")} ${formatScore(aiScore)}`);
|
|
2270
|
+
const aiDimensions = getAIScoreDimensions(skill);
|
|
2271
|
+
if (aiDimensions.length > 0) {
|
|
2272
|
+
console.log(` ${pc.dim("AI breakdown:")}`);
|
|
2273
|
+
for (const dimension of aiDimensions) {
|
|
2274
|
+
console.log(` ${pc.dim(`${dimension.label}:`)} ${formatScore(dimension.score)}`);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2076
2277
|
if (skill.tags && skill.tags.length > 0) {
|
|
2077
2278
|
console.log(` ${pc.dim("Tags:")} ${skill.tags.join(", ")}`);
|
|
2078
2279
|
}
|
|
@@ -2308,33 +2509,33 @@ async function findSkillDir(skillName) {
|
|
|
2308
2509
|
const { access: fsAccess } = await import("fs/promises");
|
|
2309
2510
|
const sanitized = sanitizeName(skillName);
|
|
2310
2511
|
const cwd = process.cwd();
|
|
2311
|
-
const projectCanonical =
|
|
2512
|
+
const projectCanonical = join9(cwd, AGENTS_DIR, SKILLS_SUBDIR, sanitized);
|
|
2312
2513
|
try {
|
|
2313
|
-
await fsAccess(
|
|
2514
|
+
await fsAccess(join9(projectCanonical, "SKILL.md"));
|
|
2314
2515
|
return projectCanonical;
|
|
2315
2516
|
} catch {
|
|
2316
2517
|
}
|
|
2317
2518
|
const commonAgentDirs = [".claude/skills", ".cursor/skills", ".opencode/skills", ".windsurf/skills"];
|
|
2318
2519
|
for (const dir of commonAgentDirs) {
|
|
2319
|
-
const agentPath =
|
|
2520
|
+
const agentPath = join9(cwd, dir, sanitized);
|
|
2320
2521
|
try {
|
|
2321
|
-
await fsAccess(
|
|
2522
|
+
await fsAccess(join9(agentPath, "SKILL.md"));
|
|
2322
2523
|
return agentPath;
|
|
2323
2524
|
} catch {
|
|
2324
2525
|
}
|
|
2325
2526
|
}
|
|
2326
|
-
const home2 =
|
|
2327
|
-
const globalCanonical =
|
|
2527
|
+
const home2 = homedir7();
|
|
2528
|
+
const globalCanonical = join9(home2, AGENTS_DIR, SKILLS_SUBDIR, sanitized);
|
|
2328
2529
|
try {
|
|
2329
|
-
await fsAccess(
|
|
2530
|
+
await fsAccess(join9(globalCanonical, "SKILL.md"));
|
|
2330
2531
|
return globalCanonical;
|
|
2331
2532
|
} catch {
|
|
2332
2533
|
}
|
|
2333
2534
|
const globalAgentDirs = [".claude/skills", ".cursor/skills", ".opencode/skills"];
|
|
2334
2535
|
for (const dir of globalAgentDirs) {
|
|
2335
|
-
const agentPath =
|
|
2536
|
+
const agentPath = join9(home2, dir, sanitized);
|
|
2336
2537
|
try {
|
|
2337
|
-
await fsAccess(
|
|
2538
|
+
await fsAccess(join9(agentPath, "SKILL.md"));
|
|
2338
2539
|
return agentPath;
|
|
2339
2540
|
} catch {
|
|
2340
2541
|
}
|
|
@@ -2372,7 +2573,7 @@ Examples:`);
|
|
|
2372
2573
|
process.exit(1);
|
|
2373
2574
|
}
|
|
2374
2575
|
const fs = await import("fs/promises");
|
|
2375
|
-
const skillMdPath =
|
|
2576
|
+
const skillMdPath = join9(skillDir, "SKILL.md");
|
|
2376
2577
|
const content = await fs.readFile(skillMdPath, "utf-8");
|
|
2377
2578
|
const { frontmatter } = parseSkillMd(content);
|
|
2378
2579
|
if (!frontmatter.commands || Object.keys(frontmatter.commands).length === 0) {
|
|
@@ -2516,9 +2717,9 @@ function validateFrontmatter(frontmatter) {
|
|
|
2516
2717
|
async function runValidate(args) {
|
|
2517
2718
|
let targetPath = args.find((a) => !a.startsWith("-")) || "SKILL.md";
|
|
2518
2719
|
if (!targetPath.endsWith("SKILL.md")) {
|
|
2519
|
-
targetPath =
|
|
2720
|
+
targetPath = join9(targetPath, "SKILL.md");
|
|
2520
2721
|
}
|
|
2521
|
-
const absolutePath =
|
|
2722
|
+
const absolutePath = join9(process.cwd(), targetPath);
|
|
2522
2723
|
console.log();
|
|
2523
2724
|
p.intro(pc.bgCyan(pc.black(" askill validate ")));
|
|
2524
2725
|
const spinner2 = p.spinner();
|
|
@@ -2603,7 +2804,7 @@ async function runInit(args) {
|
|
|
2603
2804
|
const isYes = args.includes("-y") || args.includes("--yes");
|
|
2604
2805
|
console.log();
|
|
2605
2806
|
p.intro(pc.bgCyan(pc.black(" askill init ")));
|
|
2606
|
-
const skillPath =
|
|
2807
|
+
const skillPath = join9(process.cwd(), targetDir, "SKILL.md");
|
|
2607
2808
|
try {
|
|
2608
2809
|
await import("fs").then((fs2) => fs2.promises.access(skillPath));
|
|
2609
2810
|
p.log.error(`SKILL.md already exists at ${pc.cyan(skillPath)}`);
|
|
@@ -2725,7 +2926,7 @@ async function runInit(args) {
|
|
|
2725
2926
|
`;
|
|
2726
2927
|
content += `Provide concrete examples of when and how to use this skill.
|
|
2727
2928
|
`;
|
|
2728
|
-
const targetPath =
|
|
2929
|
+
const targetPath = join9(process.cwd(), targetDir);
|
|
2729
2930
|
if (targetDir !== ".") {
|
|
2730
2931
|
const fs2 = await import("fs");
|
|
2731
2932
|
await fs2.promises.mkdir(targetPath, { recursive: true });
|
|
@@ -2741,6 +2942,175 @@ async function runInit(args) {
|
|
|
2741
2942
|
console.log();
|
|
2742
2943
|
p.outro(pc.green("Done!"));
|
|
2743
2944
|
}
|
|
2945
|
+
async function runSubmit(args) {
|
|
2946
|
+
const url = args.find((a) => !a.startsWith("-"));
|
|
2947
|
+
if (!url) {
|
|
2948
|
+
console.log(`${RED}Usage: askill submit <github-url>${RESET}`);
|
|
2949
|
+
process.exit(1);
|
|
2950
|
+
}
|
|
2951
|
+
console.log();
|
|
2952
|
+
p.intro(pc.bgCyan(pc.black(" askill submit ")));
|
|
2953
|
+
const spinner2 = p.spinner();
|
|
2954
|
+
spinner2.start("Submitting URL for indexing...");
|
|
2955
|
+
try {
|
|
2956
|
+
const result = await api.submit(url);
|
|
2957
|
+
spinner2.stop(pc.green(result.message));
|
|
2958
|
+
console.log();
|
|
2959
|
+
for (const skill of result.skills) {
|
|
2960
|
+
const statusColor = skill.status === "indexed" ? pc.green : skill.status === "skipped" ? pc.yellow : pc.red;
|
|
2961
|
+
console.log(` ${statusColor(skill.status.padEnd(7))} ${pc.dim(skill.path)}${skill.name ? ` (${skill.name})` : ""}`);
|
|
2962
|
+
}
|
|
2963
|
+
p.outro(pc.green(`Submitted ${pc.cyan(`${result.repoOwner}/${result.repoName}`)}`));
|
|
2964
|
+
} catch (error) {
|
|
2965
|
+
spinner2.stop(pc.red("Submit failed"));
|
|
2966
|
+
if (error instanceof APIError) {
|
|
2967
|
+
p.log.error(error.message);
|
|
2968
|
+
} else {
|
|
2969
|
+
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
2970
|
+
}
|
|
2971
|
+
p.outro(pc.red("Failed"));
|
|
2972
|
+
process.exit(1);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
async function runLogin(args) {
|
|
2976
|
+
let token = "";
|
|
2977
|
+
const tokenFlagIndex = args.findIndex((a) => a === "--token");
|
|
2978
|
+
if (tokenFlagIndex >= 0 && args[tokenFlagIndex + 1]) {
|
|
2979
|
+
token = args[tokenFlagIndex + 1];
|
|
2980
|
+
}
|
|
2981
|
+
if (!token) {
|
|
2982
|
+
console.log();
|
|
2983
|
+
p.note(`To get your token, visit: ${pc.cyan(`${REGISTRY_URL}/account`)}`);
|
|
2984
|
+
const input = await p.password({
|
|
2985
|
+
message: "API token",
|
|
2986
|
+
placeholder: "ask_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
2987
|
+
mask: "*",
|
|
2988
|
+
validate: (value) => {
|
|
2989
|
+
if (!value) return "Token is required";
|
|
2990
|
+
if (!value.startsWith("ask_")) return "Token must start with ask_";
|
|
2991
|
+
return void 0;
|
|
2992
|
+
}
|
|
2993
|
+
});
|
|
2994
|
+
if (p.isCancel(input)) {
|
|
2995
|
+
p.cancel("Login cancelled");
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
token = input;
|
|
2999
|
+
}
|
|
3000
|
+
const spinner2 = p.spinner();
|
|
3001
|
+
spinner2.start("Verifying token...");
|
|
3002
|
+
try {
|
|
3003
|
+
const me = await api.authMe(token);
|
|
3004
|
+
const username = me.username ?? void 0;
|
|
3005
|
+
await saveCredentials({ token, username });
|
|
3006
|
+
spinner2.stop(pc.green(`Logged in as @${username ?? "unknown"}`));
|
|
3007
|
+
p.outro(pc.green("Authentication saved"));
|
|
3008
|
+
} catch (error) {
|
|
3009
|
+
spinner2.stop(pc.red("Invalid token"));
|
|
3010
|
+
if (error instanceof APIError) {
|
|
3011
|
+
p.log.error(error.message);
|
|
3012
|
+
} else {
|
|
3013
|
+
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
3014
|
+
}
|
|
3015
|
+
p.outro(pc.red("Login failed"));
|
|
3016
|
+
process.exit(1);
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
async function runLogout() {
|
|
3020
|
+
await clearCredentials();
|
|
3021
|
+
p.outro(pc.green("Logged out"));
|
|
3022
|
+
}
|
|
3023
|
+
async function runWhoami() {
|
|
3024
|
+
const creds = await loadCredentials();
|
|
3025
|
+
if (!creds) {
|
|
3026
|
+
p.log.warn("Not logged in. Run askill login first.");
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
try {
|
|
3030
|
+
const me = await api.authMe(creds.token);
|
|
3031
|
+
const username = me.username ?? creds.username ?? "unknown";
|
|
3032
|
+
console.log(`@${username} (token: ${maskToken(creds.token)})`);
|
|
3033
|
+
} catch {
|
|
3034
|
+
console.log(`Stored token appears invalid (token: ${maskToken(creds.token)})`);
|
|
3035
|
+
process.exit(1);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
async function runPublish(args) {
|
|
3039
|
+
const creds = await loadCredentials();
|
|
3040
|
+
if (!creds?.token) {
|
|
3041
|
+
p.log.error("Not logged in. Run askill login first.");
|
|
3042
|
+
process.exit(1);
|
|
3043
|
+
}
|
|
3044
|
+
const githubFlagIndex = args.findIndex((a) => a === "--github");
|
|
3045
|
+
const githubUrl = githubFlagIndex >= 0 ? args[githubFlagIndex + 1] : void 0;
|
|
3046
|
+
const localPath = args.find((a) => !a.startsWith("-")) || ".";
|
|
3047
|
+
let content = "";
|
|
3048
|
+
if (githubUrl) {
|
|
3049
|
+
const rawUrl = toRawGitHubUrl(githubUrl);
|
|
3050
|
+
if (!rawUrl) {
|
|
3051
|
+
p.log.error("Invalid GitHub file URL. Use a blob URL to SKILL.md");
|
|
3052
|
+
process.exit(1);
|
|
3053
|
+
}
|
|
3054
|
+
const spinner3 = p.spinner();
|
|
3055
|
+
spinner3.start("Fetching SKILL.md from GitHub...");
|
|
3056
|
+
const res = await fetch(rawUrl);
|
|
3057
|
+
if (!res.ok) {
|
|
3058
|
+
spinner3.stop(pc.red("Fetch failed"));
|
|
3059
|
+
p.log.error("Unable to fetch SKILL.md from GitHub URL");
|
|
3060
|
+
process.exit(1);
|
|
3061
|
+
}
|
|
3062
|
+
content = await res.text();
|
|
3063
|
+
spinner3.stop("Fetched");
|
|
3064
|
+
} else {
|
|
3065
|
+
const fs = await import("fs/promises");
|
|
3066
|
+
const skillPath = localPath.endsWith("SKILL.md") ? localPath : join9(localPath, "SKILL.md");
|
|
3067
|
+
try {
|
|
3068
|
+
content = await fs.readFile(join9(process.cwd(), skillPath), "utf-8");
|
|
3069
|
+
} catch {
|
|
3070
|
+
p.log.error(`Cannot read ${pc.cyan(skillPath)}`);
|
|
3071
|
+
process.exit(1);
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
const parsed = parseSkillMd(content);
|
|
3075
|
+
const name = typeof parsed.frontmatter.name === "string" ? parsed.frontmatter.name.trim() : "";
|
|
3076
|
+
const version = typeof parsed.frontmatter.version === "string" ? parsed.frontmatter.version.trim() : "";
|
|
3077
|
+
if (!name) {
|
|
3078
|
+
p.log.error("SKILL.md must include frontmatter name");
|
|
3079
|
+
process.exit(1);
|
|
3080
|
+
}
|
|
3081
|
+
if (!version || !/^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/.test(version)) {
|
|
3082
|
+
p.log.error("SKILL.md must include a valid semver version");
|
|
3083
|
+
process.exit(1);
|
|
3084
|
+
}
|
|
3085
|
+
console.log();
|
|
3086
|
+
p.intro(pc.bgCyan(pc.black(" askill publish ")));
|
|
3087
|
+
const spinner2 = p.spinner();
|
|
3088
|
+
spinner2.start("Publishing skill...");
|
|
3089
|
+
try {
|
|
3090
|
+
const result = await api.publish({
|
|
3091
|
+
token: creds.token,
|
|
3092
|
+
githubUrl,
|
|
3093
|
+
content: githubUrl ? void 0 : content
|
|
3094
|
+
});
|
|
3095
|
+
spinner2.stop(pc.green(`Published ${result.slug}@${result.version}`));
|
|
3096
|
+
p.outro(pc.cyan(result.url));
|
|
3097
|
+
} catch (error) {
|
|
3098
|
+
spinner2.stop(pc.red("Publish failed"));
|
|
3099
|
+
if (error instanceof APIError) {
|
|
3100
|
+
p.log.error(error.message);
|
|
3101
|
+
} else {
|
|
3102
|
+
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
3103
|
+
}
|
|
3104
|
+
p.outro(pc.red("Failed"));
|
|
3105
|
+
process.exit(1);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
function toRawGitHubUrl(url) {
|
|
3109
|
+
const match = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/[^/]+\/(.+)$/);
|
|
3110
|
+
if (!match) return null;
|
|
3111
|
+
const [, owner, repo, path] = match;
|
|
3112
|
+
return `https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${path}`;
|
|
3113
|
+
}
|
|
2744
3114
|
async function main() {
|
|
2745
3115
|
const args = process.argv.slice(2);
|
|
2746
3116
|
checkForUpdates().catch(() => {
|
|
@@ -2793,6 +3163,21 @@ async function main() {
|
|
|
2793
3163
|
case "init":
|
|
2794
3164
|
await runInit(restArgs);
|
|
2795
3165
|
break;
|
|
3166
|
+
case "submit":
|
|
3167
|
+
await runSubmit(restArgs);
|
|
3168
|
+
break;
|
|
3169
|
+
case "login":
|
|
3170
|
+
await runLogin(restArgs);
|
|
3171
|
+
break;
|
|
3172
|
+
case "logout":
|
|
3173
|
+
await runLogout();
|
|
3174
|
+
break;
|
|
3175
|
+
case "whoami":
|
|
3176
|
+
await runWhoami();
|
|
3177
|
+
break;
|
|
3178
|
+
case "publish":
|
|
3179
|
+
await runPublish(restArgs);
|
|
3180
|
+
break;
|
|
2796
3181
|
case "--help":
|
|
2797
3182
|
case "-h":
|
|
2798
3183
|
case "help":
|