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.
- package/README.md +2 -0
- package/dist/index.js +193 -7
- 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
|
|
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,
|
|
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 ${
|
|
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 =
|
|
3433
|
-
const cyan =
|
|
3434
|
-
const bold =
|
|
3435
|
-
const 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.
|
|
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",
|