Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 43 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ CapSync solves this by creating a single source of truth for your skills and com

**What CapSync Is Not:**

- A skill or command discovery tool. CapSync does not search registries or catalogs for skills/commands on your behalf
- A skill or command installer. You must already have skills/commands in your source directories
- A skill or command creator. CapSync only syncs what you already have
- A registry browser or catalog search tool. CapSync does not rank, search, or recommend skills for you
- A general-purpose package manager for commands. Commands still need to come from your own source directory
- A skill or command creator. CapSync only syncs and installs content you explicitly point it at

**Prerequisites:**
You need to have skills (and optionally commands) already installed in local directories before using CapSync. CapSync assumes you have:
Expand All @@ -42,7 +42,7 @@ You need to have skills (and optionally commands) already installed in local dir
- Optionally, a directory containing your commands (e.g., `~/dev/scripts/commands`)
- Skills and commands formatted for your AI tools

If you already know which Git repository you want, `capsync clone` can download it into your configured skills source. CapSync still does not browse, rank, or discover skills for you.
If you already know which Git repository or skill reference you want, `capsync clone` and `capsync install` can materialize it into your configured skills source. CapSync still does not browse, rank, or discover skills for you.

## Installation

Expand Down Expand Up @@ -311,7 +311,7 @@ Create or update symlinks for all enabled tools.

### `capsync clone <repo>`

Clone a remote Git repository into your configured skills source.
Clone a whole remote Git repository into your configured skills source.

Supported inputs:

Expand All @@ -325,7 +325,44 @@ Options:
- `--branch <name>`: Clone a specific branch instead of auto-detecting the remote default branch
- `--no-sync`: Skip running `capsync sync` after the clone finishes

If the skills source already exists, CapSync prompts before replacing it. During override, it offers a backup when local changes would otherwise be lost.
Behavior:

- Treats `skills_source` as the checkout for one whole repository
- If the same repo already exists, prompts to update in place or override with a fresh clone
- Update fetches remote changes and hard-resets the local branch to its upstream
- If the requested branch differs from the current local branch, CapSync asks for explicit confirmation before re-cloning instead of silently replacing the checkout
- If the existing source is a different repo, a git repo without `origin`, or a plain directory, CapSync asks before replacing it
- During override, it offers a backup when local changes would otherwise be lost

### `capsync install <reference>`

Install a single skill into your configured skills source from an explicit reference.

Supported inputs in v1:

- `https://skills.sh/owner/repo/skill-slug`
- `https://github.com/owner/repo/tree/<branch>/path/to/skill`
- `owner/repo/skill-slug`
- `owner/repo/path/to/skill`

Options:

- `--no-sync`: Skip running `capsync sync` after the install finishes

Behavior:

- Installs exactly one skill into `skills_source/<slug>`
- Uses a temporary git checkout to resolve and copy the skill directory
- Rejects `http://skills.sh/...`; use HTTPS only
- For GitHub tree URLs, branch names containing `/` must be URL-encoded in the branch segment (for example `feature%2Fmy-branch`)
- Refuses to install into a `skills_source` that is itself a git repository managed by `capsync clone`
- Prompts before replacing an already-installed skill with the same slug
- Leaves `commands_source` unchanged in v1

Mental model:

- `capsync clone ...` makes `skills_source` be a checkout of one whole repository
- `capsync install ...` copies one selected skill into `skills_source/<slug>`

### `capsync add <tool>`

Expand Down
55 changes: 52 additions & 3 deletions documentation/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ A target tool's skills/commands location. Each destination has:
A symbolic link. CapSync doesn't copy files—it creates symlinks from destination directories pointing to your source directory.

### Clone
The `clone` command fetches a remote Git repository into your skills source, enabling CapSync to sync remote skills to local tools.
The `clone` command makes your `skills_source` a checkout of one whole remote Git repository.

### Install
The `install` command copies one explicit skill into `skills_source/<slug>` without turning the whole source directory into a repo checkout.

---

Expand Down Expand Up @@ -154,12 +157,42 @@ capsync clone <repo> --no-sync # Clone without syncing
- If `skills_source` exists but is not a git repository: Prompt before overriding
- If `skills_source` is a git repository without an `origin` remote: Prompt before overriding
- If override would discard local changes: Warn and offer to back up first
- If update is selected while the current branch differs from `--branch`: Re-clone the requested branch instead of updating in place
- If update is selected while the current branch differs from `--branch`: Explain the mismatch and require explicit confirmation before re-cloning the requested branch

**Branch Detection:**
- Auto-detects the remote's default branch from remote HEAD
- Falls back to fetched branch names only if the remote does not report a default branch

### `capsync install <reference>`

Install one skill into your skills source from an explicit reference.

```bash
capsync install <reference> # Install and sync
capsync install <reference> --no-sync # Install without syncing
```

**Arguments:**
- `<reference>`: Explicit skill reference in one of these forms:
- `https://skills.sh/owner/repo/skill-slug`
- `https://github.com/owner/repo/tree/<branch>/path/to/skill`
- `owner/repo/skill-slug`
- `owner/repo/path/to/skill`

**Behavior:**
- Resolves exactly one skill from the provided reference
- Uses a temporary git checkout and copies the selected skill into `skills_source/<slug>`
- Rejects `http://skills.sh/...`; HTTPS is required for `skills.sh` references
- For GitHub tree URLs, branch names containing `/` must be URL-encoded in the branch segment (for example `feature%2Fmy-branch`)
- Prompts before replacing an already-installed skill with the same slug
- Runs `capsync sync` automatically unless `--no-sync` is passed
- Leaves `commands_source` unchanged in v1

**Constraints:**
- v1 does not browse or search the public skills catalog
- v1 does not install commands
- v1 refuses to install into a `skills_source` that is itself a git repository managed by `capsync clone`

---

## Configuration
Expand Down Expand Up @@ -299,13 +332,25 @@ After (with CapSync):
1. Parse repo URL (`owner/repo` or full URL)
2. Determine default branch (fetch remote refs)
3. Handle existing source:
- Same repo → offer update (git pull) or override
- Same repo → offer update (fetch + hard reset to upstream) or override
- Different repo → offer override
- Non-git directory or no `origin` remote → require explicit confirmation before override
- Requested branch differs from current local branch during update → require explicit confirmation before re-cloning
- Local changes during override → warn and offer backup
4. Clone to `skills_source`
5. Optionally sync to all enabled tools

### Install Workflow

1. Parse an explicit skill reference
2. Resolve it to a repo URL plus a concrete skill selector
3. Clone the repo to a temporary checkout
4. Find exactly one skill directory containing `SKILL.md`
5. Copy that skill into `skills_source/<slug>`
6. Optionally sync to all enabled tools

Unlike `clone`, this does not make `skills_source` a repo checkout. It only installs one selected skill into the managed source directory.

---

## Common Use Cases
Expand Down Expand Up @@ -378,6 +423,7 @@ capsync/
│ ├── config.rs # Config loading/saving
│ ├── clone.rs # Git clone functionality
│ ├── detect.rs # Tool detection
│ ├── install.rs # Explicit skill install service
│ ├── sync.rs # Symlink management
│ └── tools.rs # Supported tools list
├── Cargo.toml
Expand All @@ -397,6 +443,9 @@ capsync/
| "Skills source not found" | Source path doesn't exist | Check config or run `capsync clone` |
| "Repository not found" | Invalid repo URL | Check URL format: `owner/repo` |
| "Failed to clone" | Network/auth error | Check internet connection |
| "Install requires a concrete skill reference" | Repo-only ref provided | Use `owner/repo/skill-slug` or a GitHub tree URL |
| "HTTP skills.sh references are not supported" | Tried to use `http://skills.sh/...` | Switch to `https://skills.sh/...` |
| "Skills source is currently a git repository" | Tried to install into clone-managed source | Use a different `skills_source` or continue using `capsync clone` |

---

Expand Down
72 changes: 65 additions & 7 deletions documentation/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ Reads and writes your settings to `~/.config/capsync/config.toml`. It's just a T
**`detect.rs`** - The Finder
Scans your computer for installed AI tools. Just checks if directories exist. Fast, simple, non-invasive.

**`clone.rs`** - The Repo Materializer
Handles whole-repository cloning into `skills_source`, including update vs override prompts, branch selection, and safety checks around replacing an existing checkout.

**`install.rs`** - The Skill Materializer
Handles installing one explicit skill reference into `skills_source/<slug>` by cloning to a temporary checkout, selecting a skill directory, and copying it into the managed source tree.

**`sync.rs`** - The Worker
Actually creates and removes symlinks. Handles the messy platform differences (Unix vs Windows). Reports what worked and what didn't.

Expand All @@ -54,14 +60,13 @@ A big list of all supported tools and where they keep their stuff. Currently 40+

CapSync doesn't:

- Download skills from the internet
- Create skills for you
- Validate your skill format
- Have a GUI
- Run as a daemon
- Do anything fancy

It just syncs. That's it. That's the feature.
It does sync, and it can materialize remote content when you explicitly point it at a repository or a concrete skill reference. It still does not browse, rank, or discover skills for you.

### Why Symlinks?

Expand Down Expand Up @@ -123,6 +128,54 @@ Synced successfully:

If something fails, it tells you. But keeps going with the others.

### `capsync clone <repo>` - Make `skills_source` a Repo Checkout

Use clone when your `skills_source` should be one whole Git repository.

```bash
$ capsync clone vercel-labs/skills
Fetching remote branch info...
Using branch: main
Cloning into /Users/you/my-skills...
Successfully cloned vercel-labs/skills (branch: main)
Running sync...
```

Key behavior:

- Supports `owner/repo`, `owner/repo.git`, `https://...`, `http://...`, and `git@...`
- Auto-detects the remote default branch unless `--branch` is provided
- If the same repo is already present, offers update or override
- Update is implemented as fetch + hard reset to upstream, not a merge-based pull
- If the requested branch differs from the current local branch, it requires explicit confirmation before re-cloning

### `capsync install <reference>` - Copy One Skill Into `skills_source`

Use install when you want one skill from a repo, not the whole repo.

```bash
$ capsync install vercel-labs/skills/find-skills
Fetching skill source...
Installed skill 'find-skills' to /Users/you/my-skills/find-skills
Running sync...
```

Supported explicit references in v1:

- `https://skills.sh/owner/repo/skill-slug`
- `https://github.com/owner/repo/tree/<branch>/path/to/skill`
- `owner/repo/skill-slug`
- `owner/repo/path/to/skill`

Key behavior:

- Rejects repo-only refs like `owner/repo`
- Rejects `http://skills.sh/...`; HTTPS is required for `skills.sh` references
- For GitHub tree URLs, branch names containing `/` must be URL-encoded in the branch segment (for example `feature%2Fmy-branch`)
- Clones to a temporary checkout, finds exactly one skill directory, then copies it into `skills_source/<slug>`
- Refuses to install into a `skills_source` that is itself a git repo managed by `capsync clone`
- Leaves `commands_source` untouched in v1

### `capsync add <tool>` - Add New Tools

Got a new AI tool? Add it anytime.
Expand Down Expand Up @@ -219,17 +272,22 @@ $ capsync remove claude
Located at `~/.config/capsync/config.toml`. Looks like this:

```toml
source = "/Users/you/my-skills"
skills_source = "/Users/you/my-skills"
commands_source = "/Users/you/my-skills/commands"

[destinations.claude]
enabled = true
path = "/Users/you/.claude/skills"
skills_path = "/Users/you/.claude/skills"
commands_path = "/Users/you/.claude/commands"

[destinations.opencode]
enabled = true
path = "/Users/you/.config/opencode/skill"
skills_path = "/Users/you/.config/opencode/skill"
commands_path = "/Users/you/.config/opencode/commands"
```

The legacy keys `source` and destination `path` are still accepted for backward compatibility, but the canonical config fields are `skills_source`, `commands_source`, `skills_path`, and `commands_path`.

You can edit this by hand. It's just TOML. Add tools, remove them, change paths. CapSync will respect whatever's there.

## Design Decisions (The "Why")
Expand All @@ -248,7 +306,7 @@ They break in some terminals. They're distracting. Plain text works everywhere.

### Why No Skill Discovery?

CapSync doesn't download skills from the internet. That's a different problem. Maybe someday, but for now we solve the sync problem really well.
CapSync still does not browse or rank a public catalog for you. But it can now materialize remote content when you provide an explicit repo (`capsync clone`) or an explicit skill reference (`capsync install`).

### Why TOML for Config?

Expand Down Expand Up @@ -308,7 +366,7 @@ Simple. Fast. Works.

**What It Won't Do:**

- Download skills from a registry
- Browse or search a public skills registry for you
- Validate your skill format
- Sync to remote machines (SSH, etc.)
- Run as a background service
Expand Down
57 changes: 57 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::clone::{CloneAction, CloneOptions, clone_skills};
use crate::config::{self, Config, DestinationConfig};
use crate::detect::ToolDetector;
use crate::install::{InstallOptions, install_skill};
use crate::sync::SyncManager;
use crate::tools::{all_tools, get_tool};
use anyhow::{Context, Result, anyhow};
Expand Down Expand Up @@ -56,6 +57,16 @@ pub enum Commands {
#[arg(long)]
no_sync: bool,
},
#[command(about = "Install a skill from an explicit reference")]
Install {
#[arg(
help = "Skill reference (HTTPS skills.sh URL, GitHub tree URL, or owner/repo/skill)"
)]
reference: String,
#[arg(long)]
#[arg(help = "Skip syncing after install")]
no_sync: bool,
},
}

pub fn run() -> Result<()> {
Expand All @@ -81,6 +92,7 @@ pub fn run() -> Result<()> {
branch,
no_sync,
} => clone_repo(&repo, branch, no_sync),
Commands::Install { reference, no_sync } => install_from_reference(&reference, no_sync),
Commands::Status => show_status(),
}
}
Expand Down Expand Up @@ -374,6 +386,51 @@ fn clone_repo(repo: &str, branch: Option<String>, no_sync: bool) -> Result<()> {
Ok(())
}

fn install_from_reference(reference: &str, no_sync: bool) -> Result<()> {
let config = match config::load_config() {
Ok(c) => c,
Err(e) => {
let config_path = config::get_config_path();
if !config_path.exists() {
println!("No configuration found. Running init first...");
init_config()?;
config::load_config()?
} else {
return Err(e).context("Failed to load config");
}
}
};

let options = InstallOptions {
reference: reference.to_string(),
};

let result = install_skill(&options, &config)?;

if result.replaced_existing {
println!(
"\nReplaced installed skill '{}' at {}",
result.skill_slug,
result.installed_path.display()
);
} else {
println!(
"\nInstalled skill '{}' to {}",
result.skill_slug,
result.installed_path.display()
);
}

if !no_sync {
println!("\nRunning sync...");
sync_all()?;
} else {
println!("\nSkipped sync (--no-sync passed). Run 'capsync sync' manually to sync.");
}

Ok(())
}

fn show_status() -> Result<()> {
let config = config::load_config()?;

Expand Down
Loading
Loading