docs: add Claude Code skills for sfs (mount, remote, status)

Three SKILL.md files under .claude/skills/ so Claude Code users get
agent-aware guidance for sfs:

- sfs-mount: mnt/umnt/sync flow, flags, multi-device setup
- sfs-remote: s3/gs/file URLs, AWS/GCS/MinIO/R2 credential chains
- sfs-status: status/log/whoami, daemon logs, diagnostic flow

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Snow Lee
2026-06-17 08:15:57 -07:00
co-authored by Claude Opus 4.7
parent cd1834f917
commit db0ff4a279
3 changed files with 419 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
---
name: sfs-mount
description: Mount, unmount, and sync folders with sfs. Use when the user wants to "mount a folder", "unmount", "stop syncing", "sync now", or set up a shared agent workspace across devices with sfs. Covers `sfs mnt`, `sfs umnt`, and `sfs sync` including foreground daemon mode and scan intervals.
---
# sfs — mount, unmount, sync
`sfs` mounts any folder as a synced volume backed by a cloud object store (or a shared directory). Each mount runs a per-mount background daemon that scans the folder for changes and exchanges with the remote. Files on disk are always real files — every tool works on them with no integration.
Use this skill whenever the user wants to:
- Mount a folder and start syncing it.
- Stop syncing (with or without forgetting the mount).
- Run an on-demand sync cycle.
For remote URL setup and credentials, see the `sfs-remote` skill. For inspecting state, see the `sfs-status` skill.
## Commands at a glance
| Action | Command |
|---|---|
| Mount a folder, no remote yet | `sfs mnt <folder>` |
| Mount with a remote in one shot | `sfs mnt <folder> --remote <url>` |
| Mount in foreground (no background daemon) | `sfs mnt <folder> -f` |
| Stop the sync daemon | `sfs umnt <folder>` |
| Stop and forget the mount entirely | `sfs umnt <folder> --forget` |
| Run one sync cycle now | `sfs sync [<folder>]` |
`<folder>` is created if it doesn't exist. Omitting `<folder>` on `sync`/`status`/`log` defaults to the current working directory.
## Mount flow
1. **Pick a folder.** A new empty folder works; an existing folder with files works too — existing files are imported into the volume on the first cycle.
2. **Decide on a remote.** Optional at mount time. If skipped, the volume is local-only until `sfs remote set <folder> <url>` (see `sfs-remote`).
3. **Run `sfs mnt`.** sfs:
- registers the folder in `~/.sfs/mounts.json`,
- opens (or creates) the volume under `~/.sfs/volumes/<volume>/`,
- runs an initial cycle (import local files; pull remote state if a remote is set),
- starts a background daemon (unless `-f`).
4. **Verify** with `sfs status <folder>` (see `sfs-status`).
### Important `sfs mnt` flags
- `--remote, -r <url>``s3://bucket/prefix`, `gs://bucket/prefix`, or `file:///abs/path`. Can be set later via `sfs remote set`.
- `--volume, -v <name>` — override volume name. Default: folder basename. The same volume name on another device + same remote means they sync.
- `--foreground, -f` — run the daemon in the foreground. Useful for systemd / launchd / containers where you want sfs to be PID 1 of its own service. Do **not** combine with starting a second background daemon.
- `--scan-interval` (default `3s`) — how often to scan the folder for local changes.
- `--remote-interval` (default `10s`) — how often to push/pull with the remote.
### Reusing the same volume on multiple machines
```sh
# Machine A
sfs mnt ~/agent-workspace --remote s3://my-bucket/agent-workspace
# Machine B (and C, …)
sfs mnt ~/agent-workspace --remote s3://my-bucket/agent-workspace
```
The basename `agent-workspace` becomes the volume name on each machine, and they converge through the same remote prefix.
## Unmount flow
`sfs umnt <folder>` stops the per-mount daemon. Files stay on disk, the local volume store under `~/.sfs/volumes/<volume>/` is kept, and you can `sfs mnt` the folder again later to resume.
`sfs umnt <folder> --forget` additionally removes the entry from the mount registry. The local volume data is still preserved.
To fully reclaim disk space, the user must manually delete `~/.sfs/volumes/<volume>/` — sfs does not do this automatically.
## On-demand sync
`sfs sync [<folder>]` runs a single cycle: scan local changes, upload new blobs and the local journal, pull remote journals, materialize the result. This is what the daemon does on its interval; running it manually is useful when:
- The user just made changes and wants to see them on another device immediately.
- Verifying that credentials and the remote URL work end-to-end.
- The daemon was stopped but a one-shot sync is still wanted.
## Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| `mnt` says `already mounted as volume "X"` | Folder is in the registry under a different volume name | Drop the `--volume` flag or pass the existing one |
| `mnt` succeeds but no daemon | `-f` was used, or `daemon.Start` failed | Re-run without `-f`, or check `sfs status` and the foreground output |
| Files don't appear on the other device | Remote not set, or credentials missing | `sfs remote <folder>` to check; run `sfs sync` to surface the error |
| Conflict files appear (`*.sfs-conflict-*`) | Two devices edited the same path concurrently | Pick a winner manually; sfs preserves the loser intentionally |
## Examples to walk a user through
```sh
# Brand-new shared workspace
sfs mnt ~/agent-workspace --remote s3://acme-sfs/agent-workspace
# Local-only first, add a remote later
sfs mnt ./notes
sfs remote set ./notes file:///Volumes/nas/sfs/notes
sfs sync ./notes
# Pause syncing for the day
sfs umnt ~/agent-workspace
# Drop a folder entirely from sfs (keeps local volume history)
sfs umnt ./notes --forget
```
## What sfs does not sync
`.git` directories, `.DS_Store`, sfs's own temp files, and empty directories. Don't suggest mounting a folder where `.git` is the primary content the user expects synced — they want git, not sfs.
+169
View File
@@ -0,0 +1,169 @@
---
name: sfs-remote
description: Configure cloud storage remotes for sfs — S3, Google Cloud Storage, S3-compatible (MinIO, R2), or a shared directory. Use when the user wants to "set up sfs cloud storage", "connect sfs to S3/GCS/R2/MinIO/NAS", change a remote URL, or troubleshoot credentials. Covers `--remote`, `sfs remote`, `sfs remote set`, and the per-provider credential chain.
---
# sfs — remote (cloud storage) setup
sfs syncs through any object store that supports basic PUT/GET/LIST. The remote URL determines both the protocol and the credential chain — sfs uses each provider's standard credentials, nothing sfs-specific.
Use this skill whenever the user wants to:
- Pick a backend (S3 / GCS / S3-compatible / shared directory).
- Set or change the remote URL of an existing mount.
- Debug "files aren't appearing on the other device" caused by remote/auth issues.
For mounting itself, see `sfs-mount`. For checking sync state, see `sfs-status`.
## Supported remote URL schemes
| Scheme | Backend | Example |
|---|---|---|
| `s3://bucket/prefix` | Amazon S3, or any S3-compatible store via `AWS_ENDPOINT_URL` | `s3://acme-sfs/agent-workspace` |
| `gs://bucket/prefix` | Google Cloud Storage | `gs://acme-sfs/agent-workspace` |
| `file:///abs/path` | Plain directory (local, NAS, Dropbox folder, …) | `file:///Volumes/nas/sfs/notes` |
`sfs remote set` validates the scheme and rejects anything else. The prefix can be a multi-segment path (`s3://bucket/team/agent/workspace`); sfs writes `blobs/` and `journal/` underneath it.
## Setting the remote
Two ways:
```sh
# At mount time
sfs mnt ./workspace --remote s3://acme-sfs/workspace
# After mounting (mount must already exist)
sfs remote set ./workspace s3://acme-sfs/workspace
# Inspect current remote
sfs remote ./workspace
```
After `remote set`, run `sfs sync ./workspace` to push immediately. A running daemon will pick up the change on its next interval automatically.
## Credentials by provider
### Amazon S3 (`s3://`)
sfs uses the standard AWS Go SDK v2 credential chain, in order:
1. `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` env vars
2. `AWS_PROFILE` env var → `~/.aws/credentials` + `~/.aws/config`
3. EC2 / ECS / EKS IAM roles
4. SSO sessions (`aws sso login`)
Region resolution:
- `AWS_REGION` env var, or the profile's `region`, or the bucket's discovered region.
Minimum IAM policy (one prefix):
```json
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::acme-sfs",
"arn:aws:s3:::acme-sfs/agent-workspace/*"
]
}]
}
```
### S3-compatible stores (MinIO, Cloudflare R2, Backblaze B2, Wasabi…)
Set `AWS_ENDPOINT_URL` (or `AWS_ENDPOINT_URL_S3`) before running sfs, and use the `s3://` scheme:
```sh
# Cloudflare R2
export AWS_ENDPOINT_URL=https://<accountid>.r2.cloudflarestorage.com
export AWS_REGION=auto
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
sfs mnt ./workspace --remote s3://my-r2-bucket/workspace
# MinIO
export AWS_ENDPOINT_URL=http://minio.local:9000
export AWS_REGION=us-east-1
export AWS_ACCESS_KEY_ID=minioadmin
export AWS_SECRET_ACCESS_KEY=minioadmin
sfs mnt ./workspace --remote s3://sfs/workspace
```
Persist these in the user's shell rc (`~/.zshrc` / `~/.bashrc`) or a systemd/launchd unit so the daemon also has them.
### Google Cloud Storage (`gs://`)
sfs uses Application Default Credentials (ADC):
```sh
# Interactive workstation
gcloud auth application-default login
# Service account
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json
sfs mnt ./workspace --remote gs://acme-sfs/workspace
```
The service account needs `storage.objects.{get,list,create,delete}` on the bucket (e.g. `roles/storage.objectAdmin` on the bucket, scoped tighter if needed).
### Shared directory (`file://`)
No credentials. Anything readable+writable by the user works: a NAS mount, an SMB share, a Dropbox/iCloud folder, an external drive. Path must be absolute (`file:///Volumes/nas/sfs/notes`, not `file://./notes`).
Caveats:
- The shared directory becomes the single source of truth, just like a bucket. Don't put files into it directly — let sfs manage it.
- iCloud/Dropbox throttling can slow sync but doesn't break it; conflicts still resolve deterministically.
## Picking a backend
Recommend based on the user's situation:
- **Already on AWS** → `s3://` (cheapest at low volume; native IAM).
- **Already on GCP** → `gs://`.
- **Privacy / no cloud account** → Cloudflare R2 (`s3://` + `AWS_ENDPOINT_URL`); zero egress fees.
- **Self-hosted / homelab** → MinIO (`s3://` + endpoint), or `file://` over a NAS mount.
- **Single laptop + external drive / iCloud** → `file://`.
Bucket-per-team or prefix-per-volume both work. A common layout:
```
s3://acme-sfs/
├── agent-workspace/ # one volume
├── design-notes/ # another volume
└── research/ # another volume
```
## Verifying a remote actually works
```sh
sfs sync ./workspace
```
A clean exit with `synced /path (volume "workspace")` plus a non-error cycle summary means the credential chain, endpoint, and permissions all work. Failures usually surface as one of:
| Error | Cause | Fix |
|---|---|---|
| `NoCredentialProviders` / `could not load credentials` | No AWS creds in any chain step | Set `AWS_PROFILE` or env vars; for daemons, set them in the launch unit |
| `403 Forbidden` / `AccessDenied` | Credentials work but lack `s3:Put/Get/List` on the prefix | Update IAM policy to include the bucket and the prefix `/*` |
| `404 NoSuchBucket` | Wrong bucket name or wrong endpoint | Verify; for R2/MinIO ensure `AWS_ENDPOINT_URL` matches the bucket's region/account |
| `dial tcp: ... no such host` | Endpoint URL wrong or DNS broken | Recheck `AWS_ENDPOINT_URL` |
| `permission denied` on `file://` | OS-level permissions on the shared dir | `chmod`/`chown` so the user can read+write+list |
## Changing the remote later
Safe to change:
- Same bucket, different prefix → effectively starts a new volume at the new prefix. Old prefix is not touched.
- Switching providers (e.g. `file://``s3://`) → next sync pushes everything to the new remote. Other devices need their remote pointed at the new URL too, or they'll diverge.
Recommend telling all devices that share a volume at once, then `sfs sync` on each.
## Credentials and the background daemon
The daemon inherits the environment of the `sfs mnt` invocation. If you set `AWS_PROFILE` in a one-off shell, mounted, and then opened a new shell without it — the daemon is fine because it already has the env, but `sfs sync` from a fresh shell may fail credential lookup. For long-lived setups, put credentials in the user's shell rc or in the launchd/systemd unit that supervises sfs.
+143
View File
@@ -0,0 +1,143 @@
---
name: sfs-status
description: Inspect sfs mounts, the sync daemon, change history, and identity. Use when the user wants to "check sfs status", "see sfs logs", "see what changed", "is sfs syncing?", "is the daemon running?", "who changed this file?", or troubleshoot a stuck sync. Covers `sfs status`, `sfs log`, `sfs whoami`, and reading the per-mount daemon log.
---
# sfs — status, logs, and identity
sfs writes everything observable to three places:
1. **`sfs status`** — registry, daemon liveness, file count, pending push.
2. **`sfs log`** — per-file change history from the journals.
3. **The daemon log** — what the background syncer actually did and any errors.
Use this skill when the user wants to inspect any of those, or diagnose "it doesn't seem to be syncing".
For mounting itself, see `sfs-mount`. For remote setup, see `sfs-remote`.
## `sfs status [<folder>]` — what's mounted, is it running?
With no argument, lists every registered mount. With a folder, narrows to that mount.
```sh
sfs status
```
Output anatomy:
```
device: macbook (d380dea58598) as snow@runbear.io
/Users/snow/agent-workspace
volume: agent-workspace
remote: s3://acme-sfs/agent-workspace
daemon: running (pid 55434)
files: 142 (3.4 MiB)
pending: 0 local change(s) not yet pushed
```
Interpret each line:
- **`device`** — this machine's identity. Same as `sfs whoami`. This is what appears in `sfs log` attribution.
- **`volume`** — the volume name. Folders on other devices that mount the same volume name through the same remote will converge.
- **`remote`** — `(none — local only)` means changes are journaled locally but never leave the device. See `sfs-remote`.
- **`daemon`** — `running (pid N)` means the per-mount background syncer is alive. `stopped` means the folder is registered but nothing is watching it; run `sfs mnt <folder>` to start a daemon, or `sfs sync <folder>` for a one-shot.
- **`files`** — number of tracked files and total bytes. Mismatch with the working folder usually means a scan hasn't run yet (wait a scan interval or `sfs sync`).
- **`pending`** — local journal ops not yet pushed to the remote. Should be 0 shortly after a successful sync. If it stays > 0:
- the remote URL/credentials might be broken → check `sfs-remote`,
- the daemon might be stopped → status will show `stopped`,
- the remote-interval might be long → default is 10s, but anything custom is shown via `--remote-interval` at mount time.
Read the whole block before claiming "it's healthy".
## `sfs log [<folder>]` — change history
```sh
sfs log ./workspace # last 50 ops
sfs log ./workspace -n 0 # all ops
sfs log ./workspace -p notes/ # only paths under notes/
sfs log ./workspace -p notes/x.md # one file
```
Each line shows `time kind path author on device-name (size) [note]`:
```
2026-06-17 09:14:02 put notes/memory.md snow@runbear.io on macbook (412 B)
2026-06-17 09:14:55 put notes/memory.md agent@runbear.io on linux-vm (501 B)
2026-06-17 09:15:11 delete notes/draft.md snow@runbear.io on macbook
```
Use it to answer:
- "Who last changed file X?" → `sfs log <folder> -p <path> -n 1`.
- "What did device Y do?" → `sfs log <folder> -n 0` and grep by device-name.
- "Was my edit from machine A picked up on machine B?" → run `sfs log` on B; the op should appear once B has pulled A's journal.
History is content-addressed — overwritten and deleted files are still in the log, with the blob retained in `~/.sfs/volumes/<volume>/blobs/`.
## `sfs whoami` — device identity
```
device id: d380dea58598
device name: macbook
author: snow@runbear.io
sfs home: /Users/snow/.sfs
```
- **device id** — random 12 hex chars, generated on first sfs run, persisted to `~/.sfs/device.json`.
- **device name** — hostname (without `.local`).
- **author** — `git config user.email` if present, else `$USER@<hostname>`.
To change name/author, edit `~/.sfs/device.json` and restart the daemon (`sfs umnt <folder>` then `sfs mnt <folder>`).
## The per-mount daemon log
The daemon writes its activity to a log file inside the volume's daemon dir. Find it via:
```sh
# Volume dir
ls ~/.sfs/volumes/<volume>/
# Daemon state and log for this mount (one mount per file)
ls ~/.sfs/volumes/<volume>/daemons/
```
Tail the most recent log to see scan/sync cycles and errors:
```sh
tail -F ~/.sfs/volumes/<volume>/daemons/*.log
```
Useful when:
- `pending` stays > 0 and you suspect a remote error.
- The daemon shows `stopped` but you just started it (the log will show the startup failure).
- You changed credentials and want to confirm the daemon picked them up.
## Common diagnostic flow
User says "sfs doesn't seem to be working":
1. `sfs status` — does the folder appear? Is the daemon `running`? Is `pending` stuck > 0?
2. If `daemon: stopped` — restart with `sfs mnt <folder>`.
3. If `pending` is stuck — run `sfs sync <folder>` and read the cycle output. Errors here point at the remote (see `sfs-remote` troubleshooting).
4. If sync succeeds but the other device still doesn't see changes — run `sfs sync` on the other device too and then `sfs log` to confirm the op crossed over.
5. If the daemon keeps dying — tail `~/.sfs/volumes/<volume>/daemons/*.log` for the cause.
## What's on disk
```
~/.sfs/
├── device.json # identity (sfs whoami)
├── mounts.json # mount registry (sfs status)
└── volumes/<volume>/
├── blobs/ # content-addressed file content
├── journal/ # per-device append-only op logs
├── state.json # what's currently materialized
├── sync.json # lamport clock + push cursor
└── daemons/ # one pid+log file per mount of this volume
```
Don't suggest editing files under `volumes/` directly — sfs owns them. `device.json` and `mounts.json` are safe to inspect; `mounts.json` is safe to hand-edit if a mount entry needs surgery, but prefer `sfs umnt --forget` then `sfs mnt`.
Override the whole tree with `SFS_HOME=/path` (useful for tests and ephemeral environments).