skild 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +193 -7
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -43,6 +43,7 @@ Your agent is now equipped with new capabilities!
43
43
  - **Multiple Sources** — Install from GitHub, the Skild Registry, or local directories
44
44
  - **Skillsets** — Install bundles of related skills with one command
45
45
  - **Sync** — Keep skills synchronized across all your platforms
46
+ - **Push** — Upload or update skills in a Git repository
46
47
  - **Publish** — Share your skills with the community via the registry
47
48
 
48
49
  ## 📦 Installation
@@ -76,6 +77,7 @@ Requires **Node.js ≥18**.
76
77
  | `skild search <query>` | Search the registry |
77
78
  | `skild init <name>` | Create a new skill |
78
79
  | `skild publish` | Publish to the registry |
80
+ | `skild push <repo>` | Push a skill to a Git repository |
79
81
 
80
82
  ## 📥 Installing Skills
81
83
 
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
 
9
9
  // src/index.ts
10
10
  import { Command } from "commander";
11
- import chalk20 from "chalk";
11
+ import chalk21 from "chalk";
12
12
  import { createRequire } from "module";
13
13
 
14
14
  // src/commands/install.ts
@@ -77,10 +77,10 @@ var logger = {
77
77
  /**
78
78
  * Log a skill entry with status indicator.
79
79
  */
80
- skillEntry: (name, path6, hasSkillMd) => {
80
+ skillEntry: (name, path7, hasSkillMd) => {
81
81
  const status = hasSkillMd ? chalk.green("\u2713") : chalk.yellow("\u26A0");
82
82
  console.log(` ${status} ${chalk.cyan(name)}`);
83
- console.log(chalk.dim(` \u2514\u2500 ${path6}`));
83
+ console.log(chalk.dim(` \u2514\u2500 ${path7}`));
84
84
  },
85
85
  /**
86
86
  * Log installation result details.
@@ -3401,6 +3401,191 @@ Cross-platform sync (scope: ${scope})`);
3401
3401
  }
3402
3402
  }
3403
3403
 
3404
+ // src/commands/push.ts
3405
+ import fs6 from "fs";
3406
+ import os3 from "os";
3407
+ import path6 from "path";
3408
+ import { execSync } from "child_process";
3409
+ import simpleGit from "simple-git";
3410
+ import chalk20 from "chalk";
3411
+ import { SkildError as SkildError12, validateSkillDir as validateSkillDir2 } from "@skild/core";
3412
+ function expandHome(input) {
3413
+ if (!input.startsWith("~")) return input;
3414
+ return path6.join(os3.homedir(), input.slice(1));
3415
+ }
3416
+ function isExplicitLocalSpec(input) {
3417
+ return input.startsWith(".") || input.startsWith("/") || input.startsWith("~") || input.startsWith("file://");
3418
+ }
3419
+ function resolveRepoSource(raw, preferLocal) {
3420
+ const trimmed = raw.trim();
3421
+ if (!trimmed) {
3422
+ throw new SkildError12("INVALID_SOURCE", "Missing repo. Provide a Git URL, local path, or owner/repo.");
3423
+ }
3424
+ if (preferLocal || isExplicitLocalSpec(trimmed)) {
3425
+ if (trimmed.startsWith("file://")) {
3426
+ return { url: trimmed, label: trimmed };
3427
+ }
3428
+ const expanded = expandHome(trimmed);
3429
+ const resolved = path6.resolve(expanded);
3430
+ if (!fs6.existsSync(resolved)) {
3431
+ throw new SkildError12("INVALID_SOURCE", `Local repo not found: ${resolved}`);
3432
+ }
3433
+ return { url: resolved, label: resolved };
3434
+ }
3435
+ if (/^[^/]+\/[^/]+$/.test(trimmed) && !trimmed.includes(":") && !trimmed.startsWith("http")) {
3436
+ const preferSsh = hasSshAgentKeys() || Boolean(process.env.GIT_SSH_COMMAND);
3437
+ const url = preferSsh ? `git@github.com:${trimmed}.git` : `https://github.com/${trimmed}.git`;
3438
+ return { url, label: trimmed };
3439
+ }
3440
+ if (/^(https?:|git@|ssh:)/i.test(trimmed) || trimmed.includes("github.com") || trimmed.includes("gitlab.com")) {
3441
+ return { url: trimmed, label: trimmed };
3442
+ }
3443
+ throw new SkildError12(
3444
+ "INVALID_SOURCE",
3445
+ `Unsupported repo "${raw}". Use owner/repo, a Git URL, or pass --local for a local path.`
3446
+ );
3447
+ }
3448
+ function hasSshAgentKeys() {
3449
+ if (!process.env.SSH_AUTH_SOCK) return false;
3450
+ try {
3451
+ const out = execSync("ssh-add -L", { stdio: ["ignore", "pipe", "ignore"] }).toString();
3452
+ return /ssh-(rsa|ed25519)|ecdsa-/.test(out);
3453
+ } catch {
3454
+ return false;
3455
+ }
3456
+ }
3457
+ function normalizeSkillSegment(name, fallback) {
3458
+ const base = name.replace(/^@/, "").trim();
3459
+ const segment = base.includes("/") ? base.split("/").pop() || "" : base;
3460
+ const sanitized = segment.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
3461
+ if (sanitized) return sanitized;
3462
+ const fallbackSanitized = fallback.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
3463
+ return fallbackSanitized || "skill";
3464
+ }
3465
+ function normalizeTargetPath(raw) {
3466
+ const trimmed = raw.trim();
3467
+ if (!trimmed || trimmed === "." || trimmed === "./") {
3468
+ throw new SkildError12("INVALID_SOURCE", "Target path cannot be repo root; provide a subdirectory.");
3469
+ }
3470
+ const normalized = path6.posix.normalize(trimmed.replace(/\\/g, "/")).replace(/^\/+/, "");
3471
+ if (!normalized || normalized === "." || normalized.startsWith("..")) {
3472
+ throw new SkildError12("INVALID_SOURCE", "Target path must stay within the repo.");
3473
+ }
3474
+ return normalized.replace(/\/+$/, "");
3475
+ }
3476
+ function resolveTargetAbs(repoRoot, targetRel) {
3477
+ const root = path6.resolve(repoRoot);
3478
+ const targetAbs = path6.resolve(root, targetRel);
3479
+ if (!targetAbs.startsWith(`${root}${path6.sep}`)) {
3480
+ throw new SkildError12("INVALID_SOURCE", "Target path escapes repo root.");
3481
+ }
3482
+ return targetAbs;
3483
+ }
3484
+ function copySkillDir(src, dest) {
3485
+ const blocked = /* @__PURE__ */ new Set([".git", ".skild", "node_modules", ".DS_Store"]);
3486
+ fs6.cpSync(src, dest, {
3487
+ recursive: true,
3488
+ filter: (source) => {
3489
+ const base = path6.basename(source);
3490
+ if (blocked.has(base)) return false;
3491
+ return true;
3492
+ }
3493
+ });
3494
+ }
3495
+ async function hasHeadCommit(git) {
3496
+ try {
3497
+ await git.revparse(["--verify", "HEAD"]);
3498
+ return true;
3499
+ } catch {
3500
+ return false;
3501
+ }
3502
+ }
3503
+ async function ensureBranch(git, requested) {
3504
+ const branchName = requested?.trim() || "";
3505
+ const hasHead = await hasHeadCommit(git);
3506
+ if (!hasHead) {
3507
+ const target = branchName || "main";
3508
+ await git.checkoutLocalBranch(target);
3509
+ return { branch: target, setUpstream: true };
3510
+ }
3511
+ if (branchName) {
3512
+ const branches = await git.branch(["-a"]);
3513
+ if (branches.all.includes(`remotes/origin/${branchName}`) || branches.all.includes(branchName)) {
3514
+ await git.checkout(branchName);
3515
+ } else {
3516
+ await git.checkoutLocalBranch(branchName);
3517
+ }
3518
+ return { branch: branchName, setUpstream: true };
3519
+ }
3520
+ const current = await git.branchLocal();
3521
+ return { branch: current.current || "main", setUpstream: false };
3522
+ }
3523
+ function buildCommitMessage(name, version2) {
3524
+ const v = version2?.trim();
3525
+ return v ? `skild push: ${name}@${v}` : `skild push: ${name}`;
3526
+ }
3527
+ async function push(repo, options = {}) {
3528
+ let repoSpec = repo.trim();
3529
+ let requestedBranch = options.branch;
3530
+ const hashIndex = repoSpec.indexOf("#");
3531
+ if (hashIndex !== -1) {
3532
+ const base = repoSpec.slice(0, hashIndex);
3533
+ const ref = repoSpec.slice(hashIndex + 1).trim();
3534
+ if (ref && !requestedBranch) requestedBranch = ref;
3535
+ repoSpec = base;
3536
+ }
3537
+ const { url, label } = resolveRepoSource(repoSpec, Boolean(options.local));
3538
+ const dir = path6.resolve(options.dir || process.cwd());
3539
+ const validation = validateSkillDir2(dir);
3540
+ if (!validation.ok) {
3541
+ console.error(chalk20.red("Skill validation failed:"));
3542
+ for (const issue of validation.issues) console.error(chalk20.red(`- ${issue.message}`));
3543
+ process.exitCode = 1;
3544
+ return;
3545
+ }
3546
+ const frontmatter = validation.frontmatter;
3547
+ const skillName = String(frontmatter.name || path6.basename(dir) || "skill");
3548
+ const fallback = path6.basename(dir) || "skill";
3549
+ const defaultTarget = `skills/${normalizeSkillSegment(skillName, fallback)}`;
3550
+ const targetRel = normalizeTargetPath(options.path || defaultTarget);
3551
+ const spinner = createSpinner(`Preparing ${chalk20.cyan(skillName)}...`);
3552
+ const tempRoot = fs6.mkdtempSync(path6.join(os3.tmpdir(), "skild-push-"));
3553
+ try {
3554
+ spinner.text = `Cloning ${chalk20.cyan(label)}...`;
3555
+ await simpleGit().clone(url, tempRoot);
3556
+ const repoGit = simpleGit(tempRoot);
3557
+ const { branch, setUpstream } = await ensureBranch(repoGit, requestedBranch);
3558
+ spinner.text = `Updating ${chalk20.cyan(targetRel)}...`;
3559
+ const targetAbs = resolveTargetAbs(tempRoot, targetRel);
3560
+ if (fs6.existsSync(targetAbs)) fs6.rmSync(targetAbs, { recursive: true, force: true });
3561
+ fs6.mkdirSync(path6.dirname(targetAbs), { recursive: true });
3562
+ copySkillDir(dir, targetAbs);
3563
+ await repoGit.add(["-A", targetRel]);
3564
+ const status = await repoGit.status();
3565
+ if (status.isClean()) {
3566
+ spinner.succeed(`No changes to push for ${chalk20.green(skillName)}.`);
3567
+ return;
3568
+ }
3569
+ spinner.text = "Committing changes...";
3570
+ const message = options.message?.trim() || buildCommitMessage(skillName, frontmatter.version);
3571
+ await repoGit.commit(message);
3572
+ spinner.text = "Pushing to remote...";
3573
+ if (setUpstream) {
3574
+ await repoGit.push(["-u", "origin", branch]);
3575
+ } else {
3576
+ await repoGit.push();
3577
+ }
3578
+ spinner.succeed(`Pushed ${chalk20.green(skillName)} to ${chalk20.cyan(label)}:${chalk20.cyan(targetRel)}`);
3579
+ } catch (error) {
3580
+ spinner.fail("Push failed");
3581
+ const message = error instanceof SkildError12 ? error.message : error instanceof Error ? error.message : String(error);
3582
+ console.error(chalk20.red(message));
3583
+ process.exitCode = 1;
3584
+ } finally {
3585
+ fs6.rmSync(tempRoot, { recursive: true, force: true });
3586
+ }
3587
+ }
3588
+
3404
3589
  // src/index.ts
3405
3590
  import { PLATFORMS as PLATFORMS6 } from "@skild/core";
3406
3591
  var require2 = createRequire(import.meta.url);
@@ -3421,6 +3606,7 @@ program.command("login").description("Login to a registry and store an access to
3421
3606
  program.command("logout").description("Remove stored registry credentials").action(async () => logout());
3422
3607
  program.command("whoami").description("Show current registry identity").action(async () => whoami());
3423
3608
  program.command("publish").description("Publish a Skill directory to the registry (hosted tarball)").option("--dir <path>", "Skill directory (defaults to cwd)").option("--name <@publisher/skill>", "Override skill name (defaults to SKILL.md frontmatter)").option("--skill-version <semver>", "Override version (defaults to SKILL.md frontmatter)").option("--alias <alias>", "Optional short identifier (global unique) for `skild install <alias>`").option("--description <text>", "Override description (defaults to SKILL.md frontmatter)").option("--targets <list>", "Comma-separated target platforms metadata (optional)").option("--tag <tag>", "Dist-tag (default: latest)", "latest").option("--registry <url>", "Registry base URL (defaults to saved login)").option("--json", "Output JSON").action(async (options) => publish(options));
3609
+ program.command("push <repo>").description("Upload/update a Skill directory to a Git repository").option("--dir <path>", "Skill directory (defaults to cwd)").option("--path <path>", "Target path inside repo (defaults to skills/<skill-name>)").option("--branch <branch>", "Target branch (defaults to repo default)").option("--message <text>", "Custom commit message").option("--local", "Treat <repo> as a local path (default: remote)").action(async (repo, options) => push(repo, options));
3424
3610
  program.command("search <query>").description("Search Skills in the registry").option("--registry <url>", "Registry base URL (default: https://registry.skild.sh)").option("--limit <n>", "Max results (default: 50)", "50").option("--json", "Output JSON").action(async (query, options) => search(query, options));
3425
3611
  program.command("extract-github-skills <source>").description("Extract GitHub skills into a local catalog directory").option("--out <dir>", "Output directory (default: ./skild-github-skills)").option("-f, --force", "Overwrite existing output directory").option("--depth <n>", "Max markdown recursion depth (default: 0)", "0").option("--scan-depth <n>", "Max directory depth to scan for SKILL.md (default: 6)", "6").option("--max-skills <n>", "Max discovered skills to export (default: 200)", "200").option("--json", "Output JSON").action(async (source, options) => {
3426
3612
  await extractGithubSkills(source, options);
@@ -3429,10 +3615,10 @@ program.command("sync [skills...]").description("Sync missing skills across plat
3429
3615
  await sync(skills, options);
3430
3616
  });
3431
3617
  program.action(() => {
3432
- const dim = chalk20.dim;
3433
- const cyan = chalk20.cyan;
3434
- const bold = chalk20.bold;
3435
- const green = chalk20.green;
3618
+ const dim = chalk21.dim;
3619
+ const cyan = chalk21.cyan;
3620
+ const bold = chalk21.bold;
3621
+ const green = chalk21.green;
3436
3622
  console.log("");
3437
3623
  console.log(cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 "));
3438
3624
  console.log(cyan(" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skild",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "The npm for Agent Skills — Discover, install, manage, and publish AI Agent Skills with ease.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -40,6 +40,7 @@
40
40
  "mdast-util-to-string": "^4.0.0",
41
41
  "ora": "^8.0.1",
42
42
  "remark-parse": "^11.0.0",
43
+ "simple-git": "^3.27.0",
43
44
  "string-width": "^8.1.0",
44
45
  "tar": "^7.4.3",
45
46
  "unified": "^11.0.4",