9 Commits
Author SHA1 Message Date
siujamo ba1a0c6feb chore: expand gitignore to cross-platform template
- ignore OS and editor noise for Linux, macOS, and Windows (.DS_Store, Thumbs.db, *~, etc.)
- ignore the entire .idea/ IDE folder instead of per-file rules
- keep Rust build output (target/, debug) ignored
2026-09-04 10:27:04 +08:00
siujamo 68c7f1b3fc ci: migrate release build to GitHub Actions
- add .github/workflows/build.yml: native per-OS runners (ubuntu/macos/windows) build and attach archives to the GitHub Release via gh CLI with GITHUB_TOKEN
- add workflow_dispatch to smoke-test builds without publishing a release
- remove .gitea/workflows/build.yml (cargo-zigbuild + Gitea API upload no longer used)
- docs: point clone and release URLs at github.com and update the Building section
2026-09-04 10:16:41 +08:00
siujamo 4041a76cd4 chore: bump version to 0.3.0 2026-06-22 14:15:29 +08:00
siujamo f63687c43d docs: add v0.3.0 changelog entries 2026-06-22 13:55:10 +08:00
siujamo e5d8693cb2 docs: add marketplace creation guide to README 2026-06-22 13:44:43 +08:00
siujamo d817e51836 docs: update changelog with detailed --version feature 2026-06-22 13:43:56 +08:00
siujamo 0a138f6f11 feat: add detailed --version output with build-time version injection 2026-06-22 13:42:52 +08:00
siujamo eb05e27655 feat: support monorepo skills via path field
- Add optional path field to SkillEntry for subdirectory skills
- Deploy from subdirectory when path is specified in install and update
2026-06-22 11:46:16 +08:00
siujamo cdb593bca9 ci: upload build artifacts directly to release
- Replace upload-artifact with direct release upload via Gitea API
2026-06-22 11:13:54 +08:00
11 changed files with 380 additions and 78 deletions

No files matched your search

-68
View File
@@ -1,68 +0,0 @@
name: Build
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
binary: ccsm
archive: ccsm-linux-amd64.tar.gz
- target: x86_64-apple-darwin
binary: ccsm
archive: ccsm-darwin-amd64.tar.gz
- target: x86_64-pc-windows-gnu
binary: ccsm.exe
archive: ccsm-windows-amd64.zip
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Install Rust targets
run: |
rustup target add ${{ matrix.target }}
- name: Build (Linux)
if: matrix.target == 'x86_64-unknown-linux-gnu'
run: cargo build --release --target ${{ matrix.target }}
- name: Install Zig
if: matrix.target != 'x86_64-unknown-linux-gnu'
run: |
curl -L -o zig.tar.xz https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz
tar xf zig.tar.xz
echo "$PWD/zig-linux-x86_64-0.13.0" >> "$GITHUB_PATH"
- name: Install cargo-zigbuild
if: matrix.target != 'x86_64-unknown-linux-gnu'
run: cargo install cargo-zigbuild
- name: Build with zigbuild
if: matrix.target != 'x86_64-unknown-linux-gnu'
run: cargo zigbuild --release --target ${{ matrix.target }}
- name: Package
run: |
cd "target/${{ matrix.target }}/release"
if [[ "${{ matrix.target }}" == *-windows-* ]]; then
zip "${{ matrix.archive }}" "${{ matrix.binary }}"
else
tar czf "${{ matrix.archive }}" "${{ matrix.binary }}"
fi
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.archive }}
path: target/${{ matrix.target }}/release/${{ matrix.archive }}
+64
View File
@@ -0,0 +1,64 @@
name: Build
on:
release:
types: [published]
workflow_dispatch: # manual: build + package only (no version override, no upload)
permissions:
contents: write # so GITHUB_TOKEN can upload assets to the release
jobs:
build:
name: ${{ matrix.archive }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: ccsm
archive: ccsm-linux-amd64.tar.gz
- os: macos-latest
target: x86_64-apple-darwin
binary: ccsm
archive: ccsm-darwin-amd64.tar.gz
- os: windows-latest
target: x86_64-pc-windows-msvc
binary: ccsm.exe
archive: ccsm-windows-amd64.zip
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set version from release tag
if: github.event_name == 'release'
run: |
VERSION="${GITHUB_REF_NAME#v}"
echo "CCSM_VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Install Rust toolchain (+ target)
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Build release binary
run: cargo build --release --target ${{ matrix.target }}
- name: Package archive
shell: bash
run: |
cd "target/${{ matrix.target }}/release"
if [[ "${{ runner.os }}" == 'Windows' ]]; then
powershell -NoProfile -Command "Compress-Archive -Path '${{ matrix.binary }}' -DestinationPath '${{ matrix.archive }}' -Force"
else
tar czf "${{ matrix.archive }}" "${{ matrix.binary }}"
fi
- name: Upload to GitHub release
if: github.event_name == 'release'
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "${{ github.event.release.tag_name }}" "target/${{ matrix.target }}/release/${{ matrix.archive }}" --clobber
+134 -1
View File
@@ -1 +1,134 @@
/target ### Linux
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# Metadata left by Dolphin file manager, which comes with KDE Plasma
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# Log files created by default by the nohup command
nohup.out
### macOS
# General
.DS_Store
.localized
__MACOSX/
.AppleDouble
.LSOverride
Icon[
]
# Resource forks
._*
# Files and directories that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
.com.apple.timemachine.supported
.PKInstallSandboxManager
.PKInstallSandboxManager-SystemSoftware
.hotfiles.btree
.vol
.file
.disk_label*
lost+found
.HFS+ Private Directory Data[
]
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Mac OS 6 to 9
Desktop DB
Desktop DF
TheFindByContentFolder
TheVolumeSettingsFolder
.FBCIndex
.FBCSemaphoreFile
.FBCLockFolder
# Quota system
.quota.group
.quota.user
.quota.ops.group
.quota.ops.user
# TimeMachine
Backups.backupdb
.MobileBackups
.MobileBackups.trash
MobileBackups.trash
tmbootpicker.efi
### Windows
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
### Rust
# Generated by Cargo
# will have compiled files and executables
debug
target
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Generated by cargo mutants
# Contains mutation testing data
**/mutants.out*/
# rustc will dump stack traces when hitting an internal compiler error to PWD
rustc-ice-*.txt
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
+56
View File
@@ -0,0 +1,56 @@
# Changelog
## v0.3.0
### Added
- **Detailed `--version` output** — shows version, commit SHA, project name, and attribution (`by OnixByte`); version can be injected at build time via `CCSM_VERSION` env var
- **Marketplace creation guide** — README now documents how to create and publish a skill marketplace, including the `skills.json` format, skill entry fields, monorepo `path` support, and skill repository structure
## v0.2.0
### Added
- **Monorepo skill support** — skills can now live in subdirectories of a single repository via the `path` field in `skills.json`
- **Release-triggered CI** — workflow runs on `release: published` and uploads built artifacts directly to the release
- **Project documentation** — README, CONTRIBUTING, and LICENCE (MIT) files
### Fixed
- CI artifact upload compatibility with self-hosted Gitea (downgrade `upload-artifact` to v3)
- Cross-compilation for macOS and Windows targets via `cargo-zigbuild`
## v0.1.0
Initial release of Claude Code Skill Manager (CCSM) — a CLI tool for discovering, installing, and managing Claude Code skills from Git-based marketplace repositories.
### Features
- **Marketplace management** — register, list, update, and remove skill marketplaces from any Git repository exposing a `skills.json` manifest
- **Skill search** — browse and search available skills by name or description across all registered marketplaces
- **Skill installation** — download and deploy skills to `~/.claude/skills/` with copy or symlink modes
- **Skill lifecycle** — update installed skills to latest versions, remove unwanted skills, and inspect skill details
- **Cross-platform** — pre-built binaries for Linux, macOS, and Windows (amd64)
### Commands
| Command | Description |
|---|---|
| `ccsm marketplace add <url>` | Register a skill marketplace |
| `ccsm marketplace remove <name>` | Unregister a marketplace |
| `ccsm marketplace list` | List registered marketplaces |
| `ccsm marketplace update [name]` | Refresh marketplace manifests |
| `ccsm search [query]` | Search available skills |
| `ccsm install <name>` | Install a skill |
| `ccsm list` | Show installed skills |
| `ccsm info <name>` | Show skill details |
| `ccsm update [name]` | Update installed skills |
| `ccsm remove <name>` | Uninstall a skill |
### Installation
```bash
cargo install --path .
```
Or download a pre-built binary from the [Releases](https://onixbyte.dev/onixbyte/claude-code-skill-manager/releases) page.
Generated
+1 -1
View File
@@ -218,7 +218,7 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]] [[package]]
name = "claude-code-skill-manager" name = "claude-code-skill-manager"
version = "0.1.0" version = "0.3.0"
dependencies = [ dependencies = [
"clap", "clap",
"dirs", "dirs",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "claude-code-skill-manager" name = "claude-code-skill-manager"
version = "0.1.0" version = "0.3.0"
edition = "2021" edition = "2021"
[[bin]] [[bin]]
+77 -3
View File
@@ -7,14 +7,14 @@ A CLI tool for discovering, installing, and managing [Claude Code](https://claud
### From source ### From source
```bash ```bash
git clone https://onixbyte.dev/onixbyte/claude-code-skill-manager.git git clone https://github.com/onixbyte/claude-code-skill-manager.git
cd claude-code-skill-manager cd claude-code-skill-manager
cargo install --path . cargo install --path .
``` ```
### Pre-built binaries ### Pre-built binaries
Download the latest binary for your platform from the [Releases](https://onixbyte.dev/onixbyte/claude-code-skill-manager/releases) page. Download the latest binary for your platform from the [Releases](https://github.com/onixbyte/claude-code-skill-manager/releases) page.
## Quick start ## Quick start
@@ -81,6 +81,80 @@ Skills are directories placed under `~/.claude/skills/` that Claude Code reads a
} }
``` ```
## Creating a skill marketplace
A marketplace is simply a public Git repository that contains a `skills.json` manifest at its root. CCSM reads this file to discover what skills are available.
### Repository structure
```
your-marketplace-repo/
skills.json ← manifest (required)
README.md ← optional landing page
```
### skills.json format
```json
{
"name": "My Marketplace",
"version": "1.0.0",
"skills": [
{
"name": "my-skill",
"description": "A short description shown in search results",
"repository": "https://github.com/user/skill-repo",
"branch": "main"
},
{
"name": "another-skill",
"description": "A skill that lives in a monorepo subdirectory",
"repository": "https://github.com/user/monorepo",
"branch": "main",
"path": "skills/another-skill"
}
]
}
```
| Field | Required | Description |
|---|---|---|
| `name` | Yes | Marketplace display name |
| `version` | No | Arbitrary version string for your own tracking |
| `skills` | No | Array of skill entries (defaults to `[]`) |
**Skill entry fields:**
| Field | Required | Description |
|---|---|---|
| `name` | Yes | Unique skill identifier (used with `ccsm install <name>`) |
| `description` | Yes | One-line summary shown in `ccsm search` output |
| `repository` | Yes | Public Git repository URL for the skill |
| `branch` | No | Git branch to install from (defaults to `main` or `master`, auto-detected by the marketplace host) |
| `path` | No | Subdirectory within the repository where the skill files live — use for monorepos hosting multiple skills |
### What goes in a skill repository
Each skill repository (the one referenced by `repository`) is a standard Claude Code skill directory. At minimum it should contain one of:
- `SKILL.md` — the skill manifest (preferred)
- `CLAUDE.md` — alternative manifest filename
- Supporting files referenced by the manifest (scripts, templates, etc.)
See the [Claude Code skills documentation](https://docs.anthropic.com/en/docs/claude-code/skills) for details on writing skill manifests.
### Publishing your marketplace
1. Create a public Git repository with a `skills.json` following the format above.
2. Push it — make sure `skills.json` is at the repository root.
3. Anyone can register it with:
```bash
ccsm marketplace add https://github.com/your-username/your-marketplace
```
CCSM fetches `skills.json` from the raw `main` or `master` branch automatically, so both of those branch names work out of the box.
## Deploy modes ## Deploy modes
- **Copy** (default): Skill files are physically copied to `~/.claude/skills/<name>/`. - **Copy** (default): Skill files are physically copied to `~/.claude/skills/<name>/`.
@@ -92,7 +166,7 @@ Skills are directories placed under `~/.claude/skills/` that Claude Code reads a
cargo build --release cargo build --release
``` ```
Cross-compilation targets: Linux (amd64), macOS (amd64), and Windows (amd64) via `cargo-zigbuild`. Release binaries for Linux, macOS, and Windows (amd64) are built natively per platform by GitHub Actions and attached to each GitHub Release.
## Contributing ## Contributing
+19
View File
@@ -0,0 +1,19 @@
use std::process::Command;
fn main() {
let version = std::env::var("CCSM_VERSION")
.unwrap_or_else(|_| env!("CARGO_PKG_VERSION").to_string());
let commit = Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=CCSM_VERSION={version}");
println!("cargo:rustc-env=GIT_COMMIT_SHORT={commit}");
println!("cargo:rerun-if-changed=.git/HEAD");
println!("cargo:rerun-if-env-changed=CCSM_VERSION");
}
+9 -1
View File
@@ -1,10 +1,18 @@
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
const VERSION_TEXT: &str = concat!(
env!("CCSM_VERSION"),
" -- Claude Code Skill Manager",
" -- ",
env!("GIT_COMMIT_SHORT"),
"\nby OnixByte"
);
#[derive(Parser)] #[derive(Parser)]
#[command( #[command(
name = "ccsm", name = "ccsm",
about = "Claude Code Skill Marketplace — discover and install skills from Git marketplaces", about = "Claude Code Skill Marketplace — discover and install skills from Git marketplaces",
version version = VERSION_TEXT,
)] )]
pub struct Cli { pub struct Cli {
#[command(subcommand)] #[command(subcommand)]
+2
View File
@@ -19,6 +19,8 @@ pub struct SkillEntry {
pub repository: String, pub repository: String,
#[serde(default)] #[serde(default)]
pub branch: Option<String>, pub branch: Option<String>,
#[serde(default)]
pub path: Option<String>,
} }
pub fn add(url: &str, name: Option<&str>) -> Result<MarketplaceEntry, String> { pub fn add(url: &str, name: Option<&str>) -> Result<MarketplaceEntry, String> {
+17 -3
View File
@@ -38,10 +38,20 @@ pub fn install(
let branch = entry.branch.as_deref().unwrap_or("main"); let branch = entry.branch.as_deref().unwrap_or("main");
download::download_skill(&entry.repository, branch, &target)?; download::download_skill(&entry.repository, branch, &target)?;
// Deploy to Claude skills directory (copy or link) let deploy_source = match &entry.path {
Some(subdir) => {
let p = target.join(subdir);
if !p.exists() {
return Err(format!("Path '{}' not found in skill repository", subdir));
}
p
}
None => target.clone(),
};
let config = Config::load().map_err(|e| format!("Cannot read config: {e}"))?; let config = Config::load().map_err(|e| format!("Cannot read config: {e}"))?;
let deploy_mode = mode.unwrap_or_else(|| config.default_mode.clone()); let deploy_mode = mode.unwrap_or_else(|| config.default_mode.clone());
deploy_skill(name, &target, &deploy_mode)?; deploy_skill(name, &deploy_source, &deploy_mode)?;
Ok(Skill { Ok(Skill {
name: name.to_string(), name: name.to_string(),
@@ -233,12 +243,16 @@ pub fn update(name: Option<&str>) -> Result<Vec<(String, String)>, String> {
match download::download_skill(&skill_entry.repository, branch, &old_path) { match download::download_skill(&skill_entry.repository, branch, &old_path) {
Ok(()) => { Ok(()) => {
let deploy_source = match &skill_entry.path {
Some(subdir) => old_path.join(subdir),
None => old_path.clone(),
};
// Redeploy if previously deployed // Redeploy if previously deployed
if deploy_mode.is_some() || config.default_mode != InstallMode::Copy { if deploy_mode.is_some() || config.default_mode != InstallMode::Copy {
let mode = deploy_mode let mode = deploy_mode
.clone() .clone()
.unwrap_or_else(|| config.default_mode.clone()); .unwrap_or_else(|| config.default_mode.clone());
match deploy_skill(skill_name, &old_path, &mode) { match deploy_skill(skill_name, &deploy_source, &mode) {
Ok(()) => results.push((skill_name.clone(), "Updated".to_string())), Ok(()) => results.push((skill_name.clone(), "Updated".to_string())),
Err(e) => results.push((skill_name.clone(), format!("Downloaded but deploy failed: {e}"))), Err(e) => results.push((skill_name.clone(), format!("Downloaded but deploy failed: {e}"))),
} }