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.
Files changed (3) hide show
  1. package/README.md +17 -1
  2. package/dist/cli.mjs +794 -80
  3. 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. Publish: Push to GitHub, it's automatically indexed
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.2";
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: readFile4 } = await import("fs/promises");
699
- const lastCheck = await readFile4(UPDATE_CHECK_FILE, "utf-8");
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: mkdir4, writeFile: writeFile4 } = await import("fs/promises");
709
- await mkdir4(dirname2(UPDATE_CHECK_FILE), { recursive: true });
710
- await writeFile4(UPDATE_CHECK_FILE, String(Date.now()), "utf-8");
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 checkForUpdates(force = false) {
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 readFile2, stat } from "fs/promises";
1131
- import { join as join5 } from "path";
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 = join5(dir, "SKILL.md");
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 = join5(dir, "SKILL.md");
1145
- const content = await readFile2(skillPath, "utf-8");
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(join5(dir, entry.name), depth + 1, maxDepth))
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 ? join5(basePath, subpath) : basePath;
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
- join5(searchPath, "skills"),
1190
- join5(searchPath, "skills/.curated"),
1191
- join5(searchPath, "skills/.experimental"),
1192
- join5(searchPath, ".agents/skills"),
1193
- join5(searchPath, ".claude/skills"),
1194
- join5(searchPath, ".opencode/skills"),
1195
- join5(searchPath, ".cursor/skills"),
1196
- join5(searchPath, ".codex/skills"),
1197
- join5(searchPath, ".cline/skills"),
1198
- join5(searchPath, ".gemini/skills"),
1199
- join5(searchPath, ".windsurf/skills"),
1200
- join5(searchPath, ".roo/skills"),
1201
- join5(searchPath, ".github/skills"),
1202
- join5(searchPath, ".goose/skills")
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 = join5(dir, entry.name);
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 rm2 } from "fs/promises";
1245
- import { join as join6, normalize as normalize2, resolve as resolve3, sep as sep2 } from "path";
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(join6(tmpdir(), "askill-"));
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 rm2(tempDir, { recursive: true, force: true }).catch(() => {
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 rm2(dir, { recursive: true, force: true });
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 readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1320
- import { join as join7, dirname as dirname5 } from "path";
1321
- import { homedir as homedir5 } from "os";
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 join7(homedir5(), AGENTS_DIR2, LOCK_FILE);
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 readFile3(lockPath, "utf-8");
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 mkdir3(dirname5(lockPath), { recursive: true });
1418
+ await mkdir4(dirname5(lockPath), { recursive: true });
1353
1419
  const content = JSON.stringify(lock, null, 2);
1354
- await writeFile3(lockPath, content, "utf-8");
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 join8 } from "path";
1429
- import { homedir as homedir6 } from "os";
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
- console.log();
1658
- p.intro(pc.bgCyan(pc.black(" askill install ")));
1659
- const spinner2 = p.spinner();
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
- p.outro(`Browse skills at ${pc.cyan("https://askill.sh")}`);
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
- p.outro(`Install with: ${pc.cyan(`askill add ${skillName} --all`)}`);
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
- p.outro(pc.green("Done!"));
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.join(" ");
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
- console.log(` ${pc.dim(description.slice(0, 80))}${description.length > 80 ? "..." : ""}`);
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 skillName = args[0];
2052
- if (!skillName) {
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 = join8(cwd, AGENTS_DIR, SKILLS_SUBDIR, sanitized);
2832
+ const projectCanonical = join9(cwd, AGENTS_DIR, SKILLS_SUBDIR, sanitized);
2316
2833
  try {
2317
- await fsAccess(join8(projectCanonical, "SKILL.md"));
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 = join8(cwd, dir, sanitized);
2840
+ const agentPath = join9(cwd, dir, sanitized);
2324
2841
  try {
2325
- await fsAccess(join8(agentPath, "SKILL.md"));
2842
+ await fsAccess(join9(agentPath, "SKILL.md"));
2326
2843
  return agentPath;
2327
2844
  } catch {
2328
2845
  }
2329
2846
  }
2330
- const home2 = homedir6();
2331
- const globalCanonical = join8(home2, AGENTS_DIR, SKILLS_SUBDIR, sanitized);
2847
+ const home2 = homedir7();
2848
+ const globalCanonical = join9(home2, AGENTS_DIR, SKILLS_SUBDIR, sanitized);
2332
2849
  try {
2333
- await fsAccess(join8(globalCanonical, "SKILL.md"));
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 = join8(home2, dir, sanitized);
2856
+ const agentPath = join9(home2, dir, sanitized);
2340
2857
  try {
2341
- await fsAccess(join8(agentPath, "SKILL.md"));
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 = join8(skillDir, "SKILL.md");
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 = join8(targetPath, "SKILL.md");
3040
+ targetPath = join9(targetPath, "SKILL.md");
2524
3041
  }
2525
- const absolutePath = join8(process.cwd(), targetPath);
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 = join8(process.cwd(), targetDir, "SKILL.md");
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 = join8(process.cwd(), targetDir);
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":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "askill-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "askill - The Agent Skill Package Manager",
5
5
  "type": "module",
6
6
  "bin": {