sb put
Upload a local item to SkillsBin. Accepts a directory or a single file.
Inside a git repository, sb put automatically files the item under a project named after the repo. Items uploaded outside a repository land in your inbox.
Usage
sb put <path>Arguments
| Argument | Description |
|---|---|
path | Path to a directory or file to upload. Required. |
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--org | "" | Target organization slug. Auto-detected from the git remote owner, falling back to your personal bin. | |
--project | "" | Target project slug. Auto-detected from the git repo name if omitted. | |
--type | "" | Force item type (skill, command, prompt, spec, plugin, agent, statusline, hook, mcp, rule, keybinding, script, secret). Auto-detected if omitted. | |
--message | -m | "" | Commit message for this change. |
--description | "" | Description for the item (updates on upsert). | |
--title | "" | Display name for the item; also sets the slug on create. | |
--new | false | Always create a new item (slug is suffixed if taken). | |
--update | false | Always update the existing item (fails if it doesn’t exist). |
--new and --update are mutually exclusive.
Project detection
When --project is omitted, sb put looks at the git repository containing path:
- Inside a repo — the project slug is the repository name (from the
originremote). The project is auto-created if it doesn’t exist yet. - Outside a repo (or no remote) — the item is stored in your inbox (
_inbox).
Provenance is recorded alongside the upload: the source repo, branch, and file path within the repo appear in the item’s sb.json manifest.
Item identity and updates
An item’s identity is (org, project, type, slug). sb put updates an existing item only when all four match. A slug that already exists in a different project, or with a different type, is a different item — the upload creates a new one (the slug gets a -2, -3, … suffix if it’s taken in the target folder).
Revision conflicts
If the identity matches an existing item but the new content looks like an unrelated document (both versions are substantial and share almost no lines), the server refuses the update with REVISION_CONFLICT instead of silently burying the old document. The CLI then prompts:
sb put ./notes.md# Existing command 'notes' in project 'webapp' looks like a different document (4% similar).# Select an action:# 1) update the existing item# 2) create a new item# 3) abort# Enter number [1-3]:When running non-interactively (piped stdin or Claude mode), the CLI resolves the conflict by creating a sibling item and says so:
Existing command 'notes' in project 'webapp' looked like a different document (4% similar); created new item 'notes-2' instead. Use --update to revise the existing item.To skip the prompt entirely, force the choice up front with --update (revise the existing item) or --new (always create).
Examples
Upload a skill from inside a repo
sb put .claude/skills/deploy-helper # in a checkout of the "webapp" repo# Created Deploy Helper (skill) in webapp at webapp/skills/deploy-helperUpdate an existing skill with a message
sb put .claude/skills/deploy-helper -m "Add error handling section"# Updated Deploy Helper (skill) in webapp at webapp/skills/deploy-helperUpload outside a repo (lands in the inbox)
sb put ~/notes/my-command.md# Created My Command (command) in personal at _inbox/commands/my-commandUpload to an organization
sb put ~/skills/deploy-helper --org my-team# Created Deploy Helper (skill) in my-team at _inbox/skills/deploy-helperThe item is stored in the my-team org’s directory repo. Combine with --project to file it under one of the org’s projects.
Store a secret
sb put .env# Stored encrypted (secret) .env in webapp at webapp/secrets/env.env-style files (.env, .env.production, local.env, …) are stored as secrets automatically; any single file can be forced with --type secret. Secrets are encrypted before they reach GitHub — see Secrets.
Force a specific type
sb put ./notes.md --type prompt# Created Notes (prompt) in personal at _inbox/prompts/notesAuto-detection
From a directory
When uploading a directory, sb put looks for known files in this order:
| File | Detected type |
|---|---|
SKILL.md | skill |
spec.md | spec |
plugin.json | plugin |
<slug>.md | command |
<slug>.sh | statusline |
Any *.md (fallback) | skill |
The slug is the directory name.
From a file
For single files, detection runs in this order:
- Secret filenames —
.env,.env.*, or*.envbecomesecret. - Well-known paths — e.g. files under
.claude/skills/areskill,.claude/commands/arecommand,.claude/agents/areagent,.claude-plugin/areplugin, aspecs/directory isspec,CLAUDE.mdisrule,*.mcp.jsonismcp. - Interactive prompt — if the type is still ambiguous,
sbshows a numbered type menu. - Extension fallback (non-interactive) —
.md→command,.json→plugin,.sh/.py→script, otherwiseskill.
The slug is the filename without extension (scripts keep their extension; secrets derive the slug from the full filename, e.g. .env.production → env-production).
The --type flag overrides all auto-detection.
Errors
| Code | Cause |
|---|---|
LOCAL_NOT_FOUND | Path does not exist or contains no recognized files. |
AUTH_INVALID | Token missing or expired. |
PERMISSION_DENIED | Not authorized to write to the target org. |
NOT_FOUND | --update was passed but no item with that identity exists. |
VALIDATION_ERROR | --new and --update passed together, or a directory/multi-file upload with --type secret. |
SECRETS_NOT_CONFIGURED | The server has no SB_SECRETS_KEY configured (self-hosted). |
ABORTED | You chose “abort” at the revision-conflict prompt. |