mirror of
https://github.com/NomaDamas/k-skill.git
synced 2026-06-24 02:04:11 +00:00
* docs: add Manus.ai GitHub skill import guide Manus.ai의 'GitHub에서 프로젝트 스킬 가져오기' 기능은 폴더 루트에 SKILL.md(YAML frontmatter name/description 필수)가 있는 디렉토리 URL을 받는다. k-skill의 모든 스킬은 이미 이 포맷을 만족하므로 코드 변경 없이 문서만 추가한다. - 사용자는 저장소 루트 URL(https://github.com/NomaDamas/k-skill) 대신 개별 스킬 폴더 URL(https://github.com/NomaDamas/k-skill/tree/main/<skill-name>)을 붙여 넣어야 한다. - 기존 frontmatter(license, metadata.*)는 Manus가 무시하지만 다른 코딩 에이전트와의 호환을 위해 그대로 유지한다. * feat: add build:manus-bundle for batch .skill upload to Manus.ai Per-folder GitHub URL import is tedious for 61 skills, so add 'npm run build:manus-bundle' which emits one .skill (ZIP) per skill into dist/manus/, plus a single k-skill-manus-all.zip convenience bundle and an INDEX.md listing. Each archive nests its content under <skill-name>/ to match the public Anthropic skill-creator packager layout. Manus does NOT support multi-skill bulk import in a single archive (verified against help.manus.im, manus.im/docs, and open.manus.ai API docs). The combined zip is purely a download convenience: users still drag-drop individual .skill files into Manus, but the file picker accepts multiple selections so it's still much faster than pasting 61 GitHub URLs. - scripts/build-manus-bundle.js: discovers root-level skills (mirrors validate-skills.sh exclusions), shells out to system zip with -X for reproducible archives, excludes node_modules/__pycache__/.DS_Store. - scripts/test_build_manus_bundle.js: validates discovery, frontmatter parsing, lockstep with validate-skills.sh, and docs coverage. - scripts/validate-skills.sh: also skip dist/ and .sisyphus/ so the validator stays clean after a build. - .gitignore: ignore dist/ and .sisyphus/. - docs/install-manus.md: document both Method A (GitHub URL) and Method B (.skill bundle). * ci: auto-publish Manus .skill bundle as rolling release on main push Every push to main that touches a skill folder or the bundler now builds the .skill bundle and publishes it to the GitHub Releases tag 'manus-bundle-latest' (marked prerelease so it does not pollute the Latest release pointer used by the npm release flow). Users get stable download URLs that always point to the latest build: - https://github.com/NomaDamas/k-skill/releases/download/manus-bundle-latest/k-skill-manus-all.zip - https://github.com/NomaDamas/k-skill/releases/download/manus-bundle-latest/INDEX.md This removes the 'clone the repo and run npm' step for non-developers. The direct-build path remains documented as the developer fallback. - .github/workflows/manus-bundle.yml: workflow_dispatch + push-to-main with paths filter, uses preinstalled gh CLI (no third-party release action), concurrency-grouped so overlapping pushes do not race on the same tag, --clobber upload to keep asset URLs stable. - docs/install-manus.md: new 'quick path' section with the rolling-release URLs; existing local-build section reframed as a developer fallback. - scripts/test_build_manus_bundle.js: 2 new tests pinning the doc URLs and key workflow invariants (trigger branch, build invocation, tag, asset name, prerelease flag, write permission).
104 lines
4.1 KiB
JavaScript
104 lines
4.1 KiB
JavaScript
"use strict";
|
|
|
|
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const { spawnSync } = require("node:child_process");
|
|
|
|
const repoRoot = path.resolve(__dirname, "..");
|
|
const buildScript = path.join(__dirname, "build-manus-bundle.js");
|
|
|
|
test("build-manus-bundle script exists and is executable as a Node module", () => {
|
|
assert.ok(fs.existsSync(buildScript), "build-manus-bundle.js must exist");
|
|
const checked = spawnSync(process.execPath, ["--check", buildScript], { encoding: "utf8" });
|
|
assert.equal(checked.status, 0, `node --check failed: ${checked.stderr}`);
|
|
});
|
|
|
|
test("discoverSkills finds every root-level skill with a SKILL.md and matches validate-skills.sh", () => {
|
|
const { discoverSkills, EXCLUDED_DIRS } = require("./build-manus-bundle.js");
|
|
|
|
const skills = discoverSkills();
|
|
assert.ok(skills.length >= 50, `expected at least 50 skills, got ${skills.length}`);
|
|
|
|
for (const name of skills) {
|
|
assert.ok(
|
|
fs.existsSync(path.join(repoRoot, name, "SKILL.md")),
|
|
`discovered skill ${name} must have a SKILL.md`,
|
|
);
|
|
assert.ok(!EXCLUDED_DIRS.has(name), `${name} must not be an excluded tooling dir`);
|
|
}
|
|
|
|
const validatorOutput = spawnSync(path.join(__dirname, "validate-skills.sh"), [], {
|
|
cwd: repoRoot,
|
|
encoding: "utf8",
|
|
});
|
|
assert.equal(validatorOutput.status, 0, `validate-skills.sh failed: ${validatorOutput.stderr}`);
|
|
});
|
|
|
|
test("readSkillMeta extracts name and description from YAML frontmatter", () => {
|
|
const { readSkillMeta } = require("./build-manus-bundle.js");
|
|
|
|
const sample = readSkillMeta("mfds-food-safety");
|
|
assert.equal(sample.name, "mfds-food-safety");
|
|
assert.ok(sample.description.length > 0, "description must be non-empty");
|
|
});
|
|
|
|
test("EXCLUDED_DIRS stays in lockstep with validate-skills.sh exclusions", () => {
|
|
const { EXCLUDED_DIRS } = require("./build-manus-bundle.js");
|
|
const validator = fs.readFileSync(path.join(__dirname, "validate-skills.sh"), "utf8");
|
|
|
|
const required = [
|
|
".git",
|
|
".github",
|
|
".codex",
|
|
".claude",
|
|
".changeset",
|
|
"docs",
|
|
"node_modules",
|
|
"packages",
|
|
"python-packages",
|
|
"scripts",
|
|
"examples",
|
|
];
|
|
for (const dir of required) {
|
|
assert.ok(
|
|
validator.includes(`! -name ${dir}`),
|
|
`validate-skills.sh must exclude ${dir} (or this list needs updating)`,
|
|
);
|
|
assert.ok(EXCLUDED_DIRS.has(dir), `EXCLUDED_DIRS must also skip ${dir}`);
|
|
}
|
|
});
|
|
|
|
test("docs/install-manus.md documents both the GitHub URL path and the .skill bundle path", () => {
|
|
const doc = fs.readFileSync(path.join(repoRoot, "docs", "install-manus.md"), "utf8");
|
|
assert.match(doc, /tree\/main\//, "must explain per-skill folder URL pattern");
|
|
assert.match(doc, /\.skill/, "must document the .skill file flow");
|
|
assert.match(doc, /build:manus-bundle/, "must reference the npm build script");
|
|
});
|
|
|
|
test("docs/install-manus.md advertises the rolling release download URL", () => {
|
|
const doc = fs.readFileSync(path.join(repoRoot, "docs", "install-manus.md"), "utf8");
|
|
assert.match(
|
|
doc,
|
|
/releases\/download\/manus-bundle-latest\/k-skill-manus-all\.zip/,
|
|
"must link to the stable rolling-release download URL",
|
|
);
|
|
assert.match(
|
|
doc,
|
|
/releases\/tag\/manus-bundle-latest/,
|
|
"must link to the rolling-release page",
|
|
);
|
|
});
|
|
|
|
test("manus-bundle workflow exists, targets main, and publishes the expected assets", () => {
|
|
const wfPath = path.join(repoRoot, ".github", "workflows", "manus-bundle.yml");
|
|
assert.ok(fs.existsSync(wfPath), "manus-bundle.yml workflow must exist");
|
|
const wf = fs.readFileSync(wfPath, "utf8");
|
|
assert.match(wf, /branches:\s*\n\s*-\s*main/, "workflow must trigger on push to main");
|
|
assert.match(wf, /npm run build:manus-bundle/, "workflow must invoke the build script");
|
|
assert.match(wf, /manus-bundle-latest/, "workflow must use the stable rolling tag");
|
|
assert.match(wf, /k-skill-manus-all\.zip/, "workflow must upload the combined archive");
|
|
assert.match(wf, /--prerelease/, "rolling release must be marked as prerelease");
|
|
assert.match(wf, /contents:\s*write/, "workflow needs write permission to publish releases");
|
|
});
|