@adpharm/bit
v0.1.19
Published
A lightweight CLI for pulling shared files from a bit-compatible host directly into your project — and pushing local files back up. Tracks what you've synced so drift checks, re-pushes, and renames don't need you to re-type slugs.
Readme
@adpharm/bit
A lightweight CLI for pulling shared files from a bit-compatible host directly into your project — and pushing local files back up. Tracks what you've synced so drift checks, re-pushes, and renames don't need you to re-type slugs.
Installation
npm install -g @adpharm/bit
# or, one-off:
bunx @adpharm/bit@latest <command>Quick start
bit init # one-time per repo
bit login # browser-based auth
bit pull my-project/src --to ./src # pulls + auto-tracks
bit push ./src/utils/foo.ts # tracked → slug resolved from manifest
bit status # drift-check everything tracked
bit ls my-project --verbose # who pushed what, when
bit log my-project/foo # recent push history for a fileAfter bit init, --host is optional on every subsequent command — it's read from bit.config.json.
Configuration
bit.config.json
Written by bit init at the repo root. Extended by tracked-path commands as you use them.
{
"host": "https://bit.adpharm.digital",
"tracked": [
{ "path": ".claude/skills/my-skill", "slug": "skills/my-skill", "kind": "folder" },
{ "path": "src/components/Button.tsx", "slug": "my-project/ui/button", "kind": "file" }
]
}Never edit tracked by hand — use bit track, bit untrack, bit mv, bit retarget, or let bit push / pull / import auto-manage it. Entries are sorted on every write to keep git diffs clean.
.bit/state.json
Written under a .bit/ directory next to bit.config.json. The whole .bit/ directory is gitignored automatically by bit init. Records the version_number and content hash the CLI last successfully synced for each remote slug. This is what makes bit status distinguish "you edited locally" from "the server moved" — and what powers bit push's --force-with-lease precondition (server refuses if a teammate pushed since you last synced).
Note: this is not a lockfile in the
package-lock.json/bun.locksense — those are committed reproducibility manifests. This is machine-local sync state, more like.git/index. Hidden directory mirrors the.git//.vscode//.claude/convention.
{
"entries": {
"my-project/ui-button": { "version": 7, "hash": "<sha256>", "syncedAt": "2026-04-29T..." }
}
}Don't commit it, don't share it. Delete the .bit/ directory any time to reset baselines (next push or pull will re-establish them; bit status falls back to a two-way diff in the meantime).
Host resolution
| Source | Where | When to use |
|---|---|---|
| --host <url> flag | CLI arg | Per-invocation override |
| BIT_HOST env var | Shell env | CI, one-off scripts |
| bit.config.json → host | Repo root (walks up) | Normal per-project config |
| Built-in default | Hardcoded | https://bit.adpharm.digital if nothing else is set |
Auth
| Source | Where |
|---|---|
| BIT_TOKEN env var | Shell env — overrides the file |
| ~/.bit/credentials | Written by bit login, keyed by host, 0600 perms |
Tokens expire after 90 days of inactivity (sliding — every successful call resets the clock) or 1 year absolute, whichever comes first. When a token dies, requests return 401 and bit login re-auths.
Commands
bit init
Set up the current repo for use with bit.
bit initPrompts for host URL, whether to install the bundled Claude Code skill, and whether to run bit login. Safe to re-run.
| | Description |
|---|---|
| --host <url> | Skip the host prompt |
| --yes | Accept all defaults, no prompts |
| --skill-only | Skip config + login; just (re)install the skill |
bit login / bit logout
Browser-based device auth (like Vercel or GitHub CLI). Token stored in ~/.bit/credentials.
bit logout revokes the token server-side before clearing the local file, so the token is dead everywhere. If the server is unreachable it warns and still clears the local copy — the token will then expire via the idle window. A BIT_TOKEN set in the environment is left untouched (CI tokens aren't killed by an interactive logout).
bit login
bit logout| | Description |
|---|---|
| --host <url> | Override the host from bit.config.json |
bit skill install
Copy the bundled Claude Code agent skill into .claude/skills/bit-cli/SKILL.md. Use after upgrading @adpharm/bit.
bit skill install # prompts before overwriting
bit skill install --force # overwrite silentlybit push
Push a file or folder to a project. Auto-adds a manifest entry on success.
bit push <localPath> [slug]Requires a token (run bit login first). The target project must already exist.
| | Description |
|---|---|
| <localPath> | Local file or folder to upload |
| [slug] | Remote slug; omit to use the tracked slug for <localPath> |
| --host <url> | Override the host |
| --no-track | Don't add or update a manifest entry (one-off push). Also skips the .bit/state.json baseline write. |
| --force | Skip the --force-with-lease precondition — overwrite even if the remote moved since your last sync. |
Behaviour
- Auto-tracks the path on success unless
--no-trackis passed. - Slug optional when tracked —
bit push ./foo.tsre-pushes using the stored slug. - Slug mismatch — if
<localPath>is already tracked with a different slug and you pass a new one, you'll be prompted to confirm the retarget (skipped with--no-track). --force-with-leaseprecondition (default): if.bit/state.jsonhas a baseline for a slug, thatversion_numberis sent asexpectedVersion. The server refuses the whole batch with409if any slug has moved since — you'll see(you: vN, server: vM)per stale slug. Runbit pullto update, or pass--forceto overwrite. Slugs without a baseline (fresh pushes) skip the check.- Existing remote files are overwritten (upsert by slug) once the precondition passes.
- Versioned: each push creates a restorable snapshot, visible in the file's history view in the web UI (tagged
cli). Squashes with other saves from the same user inside a 60s window. The new version_number is recorded into.bit/state.jsonso the next push knows its baseline. - Ignored locally:
node_modules,.git,dist,build,.next,.turbo,.cache,.DS_Store. - Cap: 500 files per push.
Examples
bit push ./Button.tsx my-project/ui/button # first push → tracks
bit push ./Button.tsx # subsequent → slug from manifest
bit push ./Button.tsx my-project/other/slug # prompts to retarget
bit push ./snapshot.tsx project/snapshot --no-track
bit push ./src my-project/src # folderbit pull
Pull every file under a folder (or an entire project). Preserves structure under --to. Auto-adds a folder-kind manifest entry.
bit pull <slug> --to <dir>| | Description |
|---|---|
| <slug> | <project> or <project>/<folder> |
| --to <dir> | Local directory (created if missing) |
| --host <url> | Override the host |
| --force | Overwrite existing files without prompting |
bit pull skills/landing-page-spec --to ./.claude/skills/landing-page-specbit import
Pull a single file. Auto-adds a file-kind manifest entry.
bit import <slug> --to <path>| | Description |
|---|---|
| <slug> | The file's slug |
| --to <path> | Output directory or explicit file path (has an extension) |
| --host <url> | Override the host |
--to behaviour
| Value | Result |
|---|---|
| ./src/components (directory) | Writes to ./src/components/<folders-from-slug>/<filename> |
| ./src/components/MyButton.tsx (has extension) | Writes directly to that exact path, ignoring slug folders |
bit status
Check whether tracked paths match the registry. Exits 1 when anything is out of sync.
bit status # walk the whole manifest (CI-friendly)
bit status <path> # check one tracked file or folder| | Description |
|---|---|
| [path] | A tracked file or folder; omit to check everything |
| --host <url> | Override the host |
| --check | Silent mode — exit 0 (in sync) or 1 (drift), no output |
Output
When .bit/state.json has a baseline for a slug, status reports a three-way state with a (vN → vM) arrow:
| State | Marker | Meaning | Safe action |
|---|---|---|---|
| local-ahead | → | You edited locally; remote unchanged. | bit push |
| remote-ahead | ← | Remote moved; your local is unchanged. | bit pull |
| diverged | ✗ | Both sides moved. | Manual reconcile or bit pull --force / bit push --force |
| in-sync | ✓ | Local matches current remote. | — |
Without a baseline (fresh clone, never pushed/pulled), it falls back to a two-way content-diff with a unified patch — same as before. Run bit pull or bit push once to establish baselines.
Other outcomes:
missing locally— tracked path not on disk.remote slug gone— tracked slug 404s.local-only— file present locally but not on remote (folder entries only).
Hash-based hints: when a tracked path is missing locally but another file in the repo has matching content, status suggests bit mv. When a slug 404s but a different slug in the same project has matching content, status suggests bit retarget. Suggestions are never applied automatically.
bit status
bit status ./src/components/Button.tsx
bit status ./src/components/Button.tsx --checkbit ls
List remote slugs under a project or prefix. No download, no side effects — useful for confirming a slug exists before pulling.
bit ls <prefix>
bit ls <prefix> --verbose| | Description |
|---|---|
| <prefix> | Project slug or projectSlug/sub/path |
| --host <url> | Override the host |
| --verbose | Show version, last-modified time, and last pusher's display name. Requires login — pusher names aren't returned to unauthenticated callers. |
bit ls my-project
bit ls my-project/components
bit ls my-project --verbose
# my-project/components/button-tsx v5 2 hours ago Ben Honda
# my-project/components/card-tsx v2 yesterday Alice Reyesbit log
Show recent push history for a single file. Auth-required — version history exposes pusher names. Output is newest-first.
bit log <slug>
bit log <slug> --limit 50| | Description |
|---|---|
| <slug> | Full file slug (e.g. my-project/skills/skill-auditor) |
| --host <url> | Override the host |
| --limit <n> | Number of versions to show (default 20, max 100) |
bit log my-project/components/button-tsx
# v5 2 hours ago Ben Honda bit push
# v4 yesterday Alice Reyes
# v3 3 days ago Ben Honda (restore) Restored from version 1Same-author pushes within 60 seconds squash into one
version_number. The history is therefore at-checkpoint granularity, not at-keystroke. Surfaced inbit log's footer too.
bit track / bit untrack
Retrofit or remove a manifest entry without pushing or pulling.
bit track <path> <slug> # default kind inferred from the filesystem
bit track <path> <slug> --kind folder
bit untrack <path> # removes entry only; does not delete filesbit mv
Update the manifest after a local rename. Does not touch disk — run git mv (or rename the file) first.
bit mv <old> <new>bit retarget
Change the remote slug a tracked path points at. Use when a slug was renamed on the server.
bit retarget <path> <new-slug>API
The CLI expects the host to expose:
GET /api/bit/:projectSlug/* → single file
GET /api/bits/:projectSlug?prefix=… → list every file under a folder prefix
GET /api/bits/:projectSlug?verbose=1 → list with version + last_pusher (requires Bearer token)
POST /api/bits/:projectSlug → push (upsert) a batch of files (requires Bearer token)
GET /api/log/:projectSlug/*?limit=N → version history for a single file (requires Bearer token)
POST /api/cli/auth/start → begin device-auth flow
POST /api/cli/auth/poll → poll for approval, receive token once on success
GET /api/cli/auth/whoami → identify the current Bearer token
POST /api/cli/auth/logout → revoke the Bearer token used to call itSingle-file response
{
"name": "Button.tsx",
"slug": "my-project/ui-button",
"content": "...",
"updated_at": "2026-04-29T12:34:56Z",
"version_number": 5
}version_number is the latest file_versions row's number. null if the file has no version history yet.
List response
{
"projectSlug": "my-project",
"prefix": "components",
"files": [
{
"name": "Button.tsx",
"slug": "my-project/components/button-tsx",
"content": "...",
"updated_at": "2026-04-29T12:34:56Z",
"version_number": 5
}
]
}When called with ?verbose=1 and a valid Bearer token, each file additionally carries last_pusher: { display_name } | null.
Push request
POST /api/bits/:projectSlug
Authorization: Bearer <token>
Content-Type: application/json
{
"files": [
{ "path": "src/utils/foo.ts", "content": "…", "expectedVersion": 3 }
]
}expectedVersion is optional — when present, the server pre-checks every guarded slug against its current version_number in a single query and refuses the whole batch with 409 if any have moved. This is --force-with-lease semantics.
Success:
{ "written": 1, "files": [ { "slug": "my-project/src/utils/foo-ts", "version_number": 4 } ] }Conflict (409):
{
"error": "Push refused — slugs moved since baseline",
"conflicts": [
{ "slug": "my-project/components/button-tsx", "yourBaseline": 3, "currentVersion": 5 }
]
}currentVersion: null means the file no longer exists (or never did) on the server.
File-log response
{
"slug": "my-project/components/button-tsx",
"versions": [
{
"version_number": 5,
"created_at": "2026-04-29T12:34:56Z",
"kind": "edit",
"message": "bit push",
"user": { "display_name": "Ben Honda" }
}
]
}Newest first. kind is "edit" (normal save) or "restore" (produced by a UI restore action). user is null for entries with no recorded author.
Status codes
401 missing/invalid token · 404 project or file not found · 409 expectedVersion mismatch (push) · 413 payload too large (>500 files) · 429 rate-limited.
