Skip to content

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

Terminal window
sb put <path>

Arguments

ArgumentDescription
pathPath to a directory or file to upload. Required.

Flags

FlagShortDefaultDescription
--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.
--newfalseAlways create a new item (slug is suffixed if taken).
--updatefalseAlways 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 origin remote). 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:

Terminal window
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

Terminal window
sb put .claude/skills/deploy-helper # in a checkout of the "webapp" repo
# Created Deploy Helper (skill) in webapp at webapp/skills/deploy-helper

Update an existing skill with a message

Terminal window
sb put .claude/skills/deploy-helper -m "Add error handling section"
# Updated Deploy Helper (skill) in webapp at webapp/skills/deploy-helper

Upload outside a repo (lands in the inbox)

Terminal window
sb put ~/notes/my-command.md
# Created My Command (command) in personal at _inbox/commands/my-command

Upload to an organization

Terminal window
sb put ~/skills/deploy-helper --org my-team
# Created Deploy Helper (skill) in my-team at _inbox/skills/deploy-helper

The 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

Terminal window
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

Terminal window
sb put ./notes.md --type prompt
# Created Notes (prompt) in personal at _inbox/prompts/notes

Auto-detection

From a directory

When uploading a directory, sb put looks for known files in this order:

FileDetected type
SKILL.mdskill
spec.mdspec
plugin.jsonplugin
<slug>.mdcommand
<slug>.shstatusline
Any *.md (fallback)skill

The slug is the directory name.

From a file

For single files, detection runs in this order:

  1. Secret filenames.env, .env.*, or *.env become secret.
  2. Well-known paths — e.g. files under .claude/skills/ are skill, .claude/commands/ are command, .claude/agents/ are agent, .claude-plugin/ are plugin, a specs/ directory is spec, CLAUDE.md is rule, *.mcp.json is mcp.
  3. Interactive prompt — if the type is still ambiguous, sb shows a numbered type menu.
  4. Extension fallback (non-interactive) — .mdcommand, .jsonplugin, .sh/.pyscript, otherwise skill.

The slug is the filename without extension (scripts keep their extension; secrets derive the slug from the full filename, e.g. .env.productionenv-production).

The --type flag overrides all auto-detection.

Errors

CodeCause
LOCAL_NOT_FOUNDPath does not exist or contains no recognized files.
AUTH_INVALIDToken missing or expired.
PERMISSION_DENIEDNot 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_CONFIGUREDThe server has no SB_SECRETS_KEY configured (self-hosted).
ABORTEDYou chose “abort” at the revision-conflict prompt.