| name | sync-skill-to-manager |
| description | Sync a locally-developed OpenCode skill to the skill-manager npm package and (if private) the private-skills GitHub repo. Handles version bumps, npm publish, and respects the public/private boundary. Use this skill whenever the user says 'sync skill', 'publish skill', 'push skill to manager', '/sync-skill-to-manager <name>', or asks to release/distribute a skill they just edited. |
| compatibility | OpenCode with gh CLI authenticated as kokorolx + npm logged in as a skill-manager maintainer (nano-step001 or nhonh) |
| metadata | {"author":"Sisyphus","version":"1.0.0"} |
sync-skill-to-manager
Distribute a local OpenCode skill to its publishing channels with one command. Picks up the source skill from the user's project or global config, classifies it as public or private, copies to the right repo(s), bumps versions, builds, commits, pushes, and npm publishes skill-manager.
TL;DR
/sync-skill-to-manager <skill-name> # full sync + publish
/sync-skill-to-manager <skill-name> --dry-run # preview only
/sync-skill-to-manager <skill-name> --no-publish # commit + push, skip npm
/sync-skill-to-manager <skill-name> --no-push # local commit only
Hard-coded paths (this skill is opinionated about layout)
| Var | Value |
|---|
SKILL_MANAGER_REPO | /Users/tamlh/workspaces/self/AI/Tools/skill-manager |
PRIVATE_SKILLS_REPO | /Users/tamlh/workspaces/self/AI/Tools/private-skills |
PRIVATE_CATALOG | ${SKILL_MANAGER_REPO}/private-catalog.json |
If those paths don't exist, abort with a clear error ā don't try to "find" them.
Source skill resolution
- Project:
${PWD}/.opencode/skills/<name>/SKILL.md (if it exists)
- Global:
~/.config/opencode/skills/<name>/SKILL.md
- If both exist ā ask the user which to use
- If neither exists ā abort
The "source skill" is the directory that contains SKILL.md. We always copy the whole directory (references/, assets/, scripts/, checklists/, CHANGELOG.md, etc.).
Public vs private classification
Read ${PRIVATE_CATALOG} once. Then:
| Condition | Classification |
|---|
<name> IS in private-catalog.json | private |
<name> is NOT in private-catalog.json AND ${SKILL_MANAGER_REPO}/skills/<name>/ exists | public |
<name> exists in neither place (brand new) | ASK USER: public or private? |
Routing rules (privacy-respecting)
| Source | Destination | What gets bundled in npm |
|---|
| Public skill | ${SKILL_MANAGER_REPO}/skills/<name>/ | YES ā full skill files |
| Private skill | ${PRIVATE_SKILLS_REPO}/skills/<name>/ AND private-catalog.json entry | NO files ā only the catalog entry. Users with a token fetch from GitHub at install time. |
Privacy leak fix: skill-manager's installer prefers local skills/ over the GitHub private fetch. If a private skill is physically present in ${SKILL_MANAGER_REPO}/skills/, it will install without auth. The sync MUST detect this and offer to remove the leaked copy:
ā ļø '<name>' is private but a copy exists at ${SKILL_MANAGER_REPO}/skills/<name>/.
This bypasses authentication. Remove it? (yes/no)
Only proceed with the sync after the user decides.
Workflow
Pre-flight runs the helper script scripts/sync.sh for the heavy lifting. The orchestrator (this skill) handles user prompts, classification decisions, and final summary. The helper handles git/npm operations.
Step 1 ā Pre-flight checks
Run scripts/preflight.sh <name>. It exits non-zero with a specific message if any check fails:
${SKILL_MANAGER_REPO} and ${PRIVATE_SKILLS_REPO} exist
- Source skill directory found
- Source
SKILL.md exists and has parseable YAML frontmatter
- Source
skill.json exists and is valid JSON with name, version, description
gh auth status shows kokorolx as active
npm whoami returns nano-step001 or nhonh ā the two maintainers (only enforced if not --no-publish)
- Token-leak scan on source:
grep -rE 'gho_[A-Za-z0-9]{30,}|ghp_[A-Za-z0-9]{30,}|AKIA[0-9A-Z]{16}' ā abort if matches found
git -C ${SKILL_MANAGER_REPO} status --porcelain is empty (or only matches the files we're about to touch)
git -C ${PRIVATE_SKILLS_REPO} status --porcelain is empty (only checked if private)
If any fails: report the exact failure and stop. Do not auto-fix.
Step 2 ā Classify and confirm
After preflight:
- Check
private-catalog.json for <name>
- Check
${SKILL_MANAGER_REPO}/skills/<name>/ and ${PRIVATE_SKILLS_REPO}/skills/<name>/
- Resolve classification per the table above
- If brand-new, ask: "New skill detected. Publish as public or private? (public/private)"
- If private leak detected, ask: "Remove leaked copy at ${SKILL_MANAGER_REPO}/skills/? (yes/no)"
- Print a plan to the user:
š¦ sync plan for '<name>'
Classification: <public|private>
Source: <resolved-source-path> (v<source-version>)
Targets:
⢠<target-path-1> (currently v<remote-version>)
⢠<target-path-2> (catalog entry only)
Version action: <use-as-is | bump-patch | abort-source-behind>
skill-manager: v<old> ā v<new>
Commits: <count> across <repo-count> repo(s)
Push: <yes|no>
npm publish: <yes|no>
Proceed? (yes/no)
Step 3 ā Version handling
Source-of-truth for version: source skill's skill.json (skill-manager reads this).
Source vs remote skill.json version | Action |
|---|
| Source > remote | Use source as-is. Copy SKILL.md frontmatter version too. |
| Source == remote | Auto-bump patch in source (and propagate). Print "auto-bumped v ā v". |
| Source < remote | Abort with: "Source v is behind remote v. Pull or set source version manually." |
If the version is bumped, also update:
SKILL.md YAML frontmatter metadata.version
skill.json version
private-catalog.json entry's version (private only)
Use scripts/version-bump.sh for the patch bump (semver-aware).
Step 4 ā Brand-new skill: README + catalog updates
If brand-new:
- Public: build the table row from
skill.json name + description (truncated to one line). Insert into ${SKILL_MANAGER_REPO}/README.md's "Public Skills" table, alphabetized. Show diff. Ask confirm.
- Private: append entry to
private-catalog.json (alphabetized) AND insert into the "Private Skills" table in README. Show diff. Ask confirm.
If user rejects the diff ā still copy files but skip the README update (with a printed reminder).
Step 5 ā Copy files
public:
rsync -a --delete <source>/ ${SKILL_MANAGER_REPO}/skills/<name>/
private:
rsync -a --delete <source>/ ${PRIVATE_SKILLS_REPO}/skills/<name>/
# If leak removal confirmed:
rm -rf ${SKILL_MANAGER_REPO}/skills/<name>
--delete ensures removed source files are also removed from target (avoids stale files lingering across syncs).
Do NOT copy: .git/, node_modules/, .checkpoints/, .DS_Store. Use --exclude flags.
Step 6 ā Build skill-manager
cd ${SKILL_MANAGER_REPO}
npm run build
If build fails ā abort. Do NOT commit broken state.
Step 7 ā Bump skill-manager package version
Always patch-bump ${SKILL_MANAGER_REPO}/package.json. Use:
cd ${SKILL_MANAGER_REPO}
npm version patch --no-git-tag-version
Capture new version ā $NEW_MANAGER_VERSION.
Step 8 ā Commit
Use scripts/commit.sh which runs (per repo) with conventional-commit messages:
skill-manager commit message templates:
- New public skill:
feat(<name>): add v<X.Y.Z>
- Updated public skill:
feat(<name>): sync v<old> ā v<new> (or chore(<name>): sync v<X> if patch only)
- New private skill (catalog only):
feat(<name>): add to private catalog v<X.Y.Z>
- Updated private (catalog version bump only):
chore(<name>): bump catalog to v<X.Y.Z>
- Privacy leak removal:
fix: remove leaked private skill <name> from public bundle
- Manager version bump:
chore: bump to v<NEW_MANAGER_VERSION>
These can be combined into a single commit when they belong together.
private-skills commit message:
- New:
feat(<name>): add v<X.Y.Z>
- Update:
feat(<name>): sync v<old> ā v<new> (or chore if patch)
NEVER add Co-authored-by, Signed-off-by, or AI attribution trailers (per workspace AGENTS.md).
Step 9 ā Push
If --no-push not set:
git -C ${SKILL_MANAGER_REPO} push
git -C ${PRIVATE_SKILLS_REPO} push
The skill-manager remote is HTTPS+token. The private-skills remote uses both SSH (fetch) and HTTPS+token (push) ā git push will use the push URL. Both rely on the embedded token or gh auth setup-git. If push fails on auth ā STOP and surface the error verbatim.
Step 10 ā npm publish
If --no-publish not set:
š Publish @nano-step/skill-manager v<NEW_MANAGER_VERSION> to npm? (yes/no)
Even with auto-push enabled, always ask before npm publish ā npm publishes are immutable.
cd ${SKILL_MANAGER_REPO}
npm publish --access public
The prepublishOnly script in package.json runs npm run build automatically (already done in step 6, but re-runs for safety).
Step 11 ā Summary
Print:
ā
sync complete for '<name>'
Classification: <public|private>
Source version: v<X.Y.Z>
skill-manager: v<old> ā v<NEW_MANAGER_VERSION>
Commits:
⢠skill-manager: <sha-short> <subject>
⢠private-skills: <sha-short> <subject> (if applicable)
Pushed:
⢠https://github.com/nano-step/skill-manager/commit/<sha>
⢠https://github.com/nano-step/private-skills/commit/<sha> (if applicable)
Published:
⢠https://www.npmjs.com/package/@nano-step/skill-manager/v/<NEW_MANAGER_VERSION>
⢠Install: npx @nano-step/skill-manager update <name>
Flags
| Flag | Effect |
|---|
--dry-run | Print the full plan and exit. No writes, no git, no npm. |
--no-push | Commit locally but don't push. Skips npm publish (would publish stale state). |
--no-publish | Commit + push, but skip npm publish. |
--force | Skip dirty-state check. Still requires explicit user yes confirmation. |
--source <path> | Override source skill location (skip resolution). |
Error messages (be specific)
| Failure | Message |
|---|
| Source not found | Source skill '<name>' not found in .opencode/skills or ~/.config/opencode/skills |
| Both sources exist | (interactive prompt) |
| Source version behind | Source v<X> is behind remote v<Y>. Pull latest or set source version manually. |
| Token leak | Token-like pattern detected in <file>:<line>. Refusing to sync. Remove the secret first. |
| Dirty repo | <repo> has uncommitted changes:\n<status output>\nCommit, stash, or pass --force. |
| gh auth wrong account | Expected gh active account 'kokorolx', got '<user>'. Run: gh auth switch -u kokorolx |
| npm wrong account | Expected npm whoami 'nano-step001' or 'nhonh', got '<user>'. Run: npm login |
| Build failed | npm run build failed in skill-manager. Output:\n<output>\nNot committing. |
| Push failed | git push to <remote> failed:\n<output>\nManual intervention required. |
| Privacy leak detected | (interactive prompt) |
Anti-patterns (this skill MUST NOT do)
- Push without explicit user invocation of the skill (running the skill IS the consent ā but
--no-push overrides)
npm publish without explicit yes from the user
- Add AI attribution /
Co-authored-by to commits
- Commit private skill files into
${SKILL_MANAGER_REPO}/skills/
- Bump major or minor version automatically ā only patch
- Skip the build step before committing
- Use
git push --force ever
- Edit unrelated files in either repo
Scripts
The orchestrator delegates the mechanical work to small bash scripts shipped alongside this skill:
scripts/sync.sh <name> [flags] ā main entry point; coordinates the others
scripts/preflight.sh <name> ā all pre-flight checks (exits non-zero on failure)
scripts/version-bump.sh <type> <skill-json-path> ā patch-only semver bump
scripts/commit.sh <repo> <message> ā conventional commit with no AI trailers
These are kept short and composable so the orchestrator can call them step-by-step and intercept on failure. See scripts/README.md for invocation details.