Accounts & Profiles
If you only ever work with one Tissue account, ribo login is all you need and you can skip this page.
If you work with more than one account — a personal account and a client's, a company account and a demo account — or if you run AI agents that deploy on your behalf, read on. A single global login is a shared mutable variable, and this page is about not tripping over it.
The problem
ribo login stores one token in ~/.config/ribo/token.json, and the account you are acting as lives inside that token. ribo account switch re-mints it in place. That means account selection is global:
# terminal 1 # terminal 2
ribo account switch "Acme"
ribo account switch "Personal"
ribo deploy # ← deploys as Personal, not Acme
Nothing errors. The deploy succeeds — into the wrong account. And because ribo deploy looks a Cell up by name within the token's account, deploying to the wrong account doesn't update your existing Cell: it creates a brand-new one there, with a live URL under the wrong subdomain and bindings pointing at the wrong account's databases and buckets.
Two shells is the mild version. Two coding agents on the same machine, working in different projects, is the version that bites.
Tissue gives you two independent defences. Use both:
- Profiles bind a credential to a directory tree, so concurrent work stops fighting over one file.
- The account pin records the account a project belongs to and aborts any deploy that would land elsewhere.
Profiles: one credential per project tree
A profile is a named login bound to a directory. Any command run in that directory — or any subdirectory — authenticates as that profile.
Create profiles
If you're already logged in, capture the current login under a name:
ribo account switch "Acme Corp"
ribo profile create acme --from-current
ribo account switch "Personal"
ribo profile create personal --from-current
Or log in fresh, straight into a profile:
ribo profile create acme # opens the browser, stores under "acme"
ribo profile create ci --key tok_a1b2c3... # or store an API token
Bind them to directories
cd ~/work/acme-site && ribo profile activate acme .
cd ~/personal/my-blog && ribo profile activate personal .
That's it. From now on, anything you run under ~/work/acme-site is Acme, and anything under ~/personal/my-blog is you — at the same time, in as many terminals or agents as you like. Neither can clobber the other, because they no longer share a credential.
Check before you act
$ cd ~/work/acme-site
$ ribo profile which
directory /Users/you/work/acme-site
credential profile "acme" (bound at /Users/you/work/acme-site)
server https://api.tissue.systems
account Acme Corp (acct_3f8c1a2b4d6e8f00)
ribo profile which has no side effects and answers the one question that is otherwise invisible: who am I about to be? It's the right thing to put at the top of a deploy script, and the right thing to teach an agent to run before it touches production.
ribo deploy reports the same thing on every deploy, as a via line:
account Acme Corp (acct_3f8c1a2b4d6e8f00) [pinned]
via profile "acme" (bound at /Users/you/work/acme-site)
server https://api.tissue.systems
deployed acme-site
See everything at once
$ ribo profile list
* acme
account Acme Corp (acct_3f8c1a2b4d6e8f00)
servers https://auth.tissue.systems
bound /Users/you/work/acme-site
personal
account Personal (acct_9zp5k1m3n5p7r9t1)
servers https://auth.tissue.systems
bound /Users/you/personal/my-blog
Here (/Users/you/work/acme-site): profile "acme" (bound at /Users/you/work/acme-site)
Which credential wins
ribo picks a credential in this order — the first one that applies:
| # | Source | Scope |
|---|---|---|
| 1 | RIBO_TOKEN environment variable |
the process |
| 2 | --profile <name> |
one command |
| 3 | the nearest bound ancestor directory | that directory tree |
| 4 | the default profile (ribo login) |
everywhere else |
Nearest wins. If ~/work is bound to outer and ~/work/acme is bound to acme, then ~/work/acme/site uses acme — the most specific binding, not the first one found.
--profile works on any command, and it redirects writes as well as reads. ribo --profile acme account switch "Acme Staging" re-mints the credential inside the acme profile and leaves everything else alone.
A stale binding is an error, not a fallback. If a directory is bound to a profile you deleted:
Error: /Users/you/work/acme-site is bound to profile "acme", which no longer exists.
Re-create it: ribo profile create acme
Or unbind it: ribo profile deactivate /Users/you/work/acme-site
ribo will not quietly fall back to your default login, because quietly retargeting a deploy at a different account is precisely the failure profiles exist to prevent.
Pin the account in ribo.toml
Profiles make the right credential ambient. The pin makes a wrong one fatal.
cd ~/work/acme-site
ribo account pin
# Pinned Acme Corp (acct_3f8c1a2b4d6e8f00) in ribo.toml.
That writes an account_id into the [cell] block:
[cell]
name = "acme-site"
account_id = "acct_3f8c1a2b4d6e8f00"
The pin is an assertion, not a selector. It never picks an account for you — it only stops a deploy that would land in the wrong one, before anything is uploaded:
Error: Refusing to deploy: account mismatch.
ribo.toml pins Acme Corp (acct_3f8c1a2b4d6e8f00)
active account Personal (acct_9zp5k1m3n5p7r9t1)
Switch accounts: ribo account switch "Acme Corp"
Or use a profile for this tree: ribo profile activate <name> .
The check runs on both sides: ribo refuses locally, and the deploy request carries the expected account id so the server refuses too. An out-of-date CLI can't get around it.
Commit the pin. It contains no secret — an account id is not a credential — and it means anyone who clones the project, including an agent, gets the guardrail for free.
Don't pin public templates. If you publish a project for other people to clone and deploy into their accounts, leave it unpinned; a pin would fail every one of their deploys with an account id that means nothing to them.
Recipes
Two agents, two accounts, one machine
# one-time
ribo profile create acme --from-current # after: ribo account switch "Acme Corp"
ribo profile create personal --from-current # after: ribo account switch "Personal"
ribo profile activate acme ~/work/acme-site
ribo profile activate personal ~/personal/my-blog
(cd ~/work/acme-site && ribo account pin)
(cd ~/personal/my-blog && ribo account pin)
Then both agents can run ribo profile which and ribo deploy concurrently, in any order, with no coordination.
CI
Nothing to change. Set RIBO_TOKEN to a scoped API token and profiles never come into play — an environment variable is already per-process, so parallel jobs on one runner are isolated by construction:
- name: Deploy Cell
env:
RIBO_TOKEN: ${{ secrets.RIBO_TOKEN }}
run: ribo deploy
ribo profile which tells you when RIBO_TOKEN is in play, so an environment credential shadowing a profile is never a silent surprise.
Retiring a profile
ribo profile deactivate ~/work/acme-site # unbind one directory
ribo profile delete acme # remove the profile and unbind everything
Where credentials live
~/.config/ribo/
├── token.json ← your default login (ribo login)
└── profiles.json ← named profiles and their directory bindings
Same path on every platform, macOS included. Both files hold bearer tokens, so ribo writes the directory as 0700 and the files as 0600, and re-applies those permissions on every write — including to files created by older versions of ribo. Treat them like an SSH private key: don't commit them, don't sync them into a shared volume.
Deleting profiles.json removes every profile and binding but leaves your default login intact.
A note on --from-current
ribo profile create <name> --from-current copies the credentials in your default login for every server it holds — production, staging, a local dev server. ribo account switch only re-mints the credential for the server it targeted, so the others are whatever they already were, and are not guaranteed to belong to the account you just captured.
When more than one server is copied, ribo says so and tells you how to check:
ribo --profile acme account whoami --server https://api.staging.tissue.systems
ribo --profile acme account switch "Acme Corp" --server https://api.staging.tissue.systems
If you only use production, there is nothing to do.
See also
- ribo CLI Reference — every command and flag
- ribo.toml Reference — the
account_idfield - REST API — scoped API tokens for CI and agents