@super-repo/pluto
v0.2.6
Published
Self-hosted, multi-tenant file server for syncing runtime-scoped repository files.
Readme
@super-repo/pluto
Self-hosted, multi-tenant file-sync server for organization/repository runtime files.
Pluto stores and serves bytes exactly as provided. It does not know or care whether files are plaintext, encrypted, JSON, env files, certificates, manifests, or any other format. If encryption is needed, encrypt before pluto push and decrypt after pluto pull in your own application/runtime layer.
Files are addressed by:
<org>/<repo>/<runtime>/<relative-file-path>The client syncs a local target directory, defaulting to .pluto:
pluto pushuploads files for the selected runtime from the target directory.pluto pulldownloads every file for the resolvedorg/repo/runtimeand writes them into the target directory without runtime prefixes.
Install
pnpm add -D @super-repo/plutoThe same pluto binary runs as the server and as the workspace client.
Server quickstart
# Persist /data in a volume.
docker run -d --name pluto -p 3000:3000 -v pluto-data:/data ghcr.io/super-repo/pluto:latest
# Create an org.
docker exec -it pluto pluto org create acme
# Mint a repo/runtime scoped token.
docker exec -it pluto pluto token create \
--org acme \
--scope 'acme/portal/**:read' \
--scope 'acme/portal/**:write' \
--label 'portal-sync' \
--expires 30dThe plaintext token is printed once. Store it securely.
Workspace quickstart
export PLUTO_TOKEN=plt_xxxx
# pluto.config.json, pluto.config.ts, etc.
pluto init
# Upload all files from .pluto to acme/portal/<runtime>/...
pluto push
# Pull all files for acme/portal/<runtime>/... into .pluto, replacing the directory.
pluto pull
# Sync every runtime file this token can read into .pluto/<runtime>/...
pluto sync
# Pull, then run a process.
pluto exec -- node app.js
pluto statusConfiguration
Committed config files must not contain tokens.
import { defineConfig } from '@super-repo/pluto/client'
export default defineConfig({
server: 'https://pluto.example.com',
org: 'acme',
repo: 'portal',
targetDir: '.pluto', // optional; default is .pluto
autoDetect: true,
runtimeMap: {
development: 'dev',
production: 'prod',
preview: 'preview',
},
})Resolution order:
| Field | Sources |
|---|---|
| server | --server, PLUTO_SERVER, config, local .pluto, prompt |
| token | --token, PLUTO_TOKEN, local .pluto.local.json or legacy file .pluto, prompt |
| org | --org, PLUTO_ORG, config, local .pluto, prompt |
| repo | --repo, PLUTO_REPO, config, local .pluto, prompt |
| runtime | --runtime, PLUTO_RUNTIME, config, runtimeMap[NODE_ENV], default default |
| targetDir | --target-dir, PLUTO_TARGET_DIR, config, local .pluto, default .pluto |
A machine-local .pluto.local.json file may contain secrets and must be gitignored. Legacy file .pluto is still read only when it is a file; the default .pluto/ target directory takes precedence for file sync.
{
"server": "https://pluto.example.com",
"org": "acme",
"repo": "portal",
"runtime": "preview",
"targetDir": ".pluto",
"token": "plt_..."
}Sync model
Given:
.pluto/
config/app.json
secrets.enc
certs/tls.crtWith org=acme, repo=portal, runtime=preview, pluto push uploads:
acme/portal/preview/config/app.json
acme/portal/preview/secrets.enc
acme/portal/preview/certs/tls.crtRuntime selection rules for pluto push:
- Files inside
<targetDir>/<runtime>/...are uploaded with the runtime directory stripped. - Root files named
<runtime>.<name>are uploaded with the runtime prefix stripped. - Root files named
<name>.<runtime>are uploaded with the runtime suffix stripped. This supports env-style files such as.env.preview -> .env. - Unprefixed files are also uploaded for the selected runtime.
- Known non-selected runtime directories/prefixes/suffixes such as
dev/,prod.,.preview, and values fromruntimeMapare ignored. - If an unprefixed base file and a runtime-specific file map to the same output, the runtime-specific file wins. If two equally-specific runtime files collide, Pluto errors.
Example for runtime=preview:
.pluto/config/app.json -> acme/portal/preview/config/app.json
.pluto/preview/secrets.enc -> acme/portal/preview/secrets.enc
.pluto/preview.flags.json -> acme/portal/preview/flags.json
.pluto/.env.preview -> acme/portal/preview/.env
.pluto/prod/secrets.enc -> ignored
.pluto/.env.prod -> ignoredpluto pull lists the remote runtime prefix and writes files back without runtime prefixes:
acme/portal/preview/config/app.json -> .pluto/config/app.json
acme/portal/preview/secrets.enc -> .pluto/secrets.encPulls overwrite files they download, but they do not delete unrelated files in the target directory. After each pull, Pluto writes .pluto/pluto-details.json with metadata including mode, cloudGenerated, updatedAt, pulledAt, username, org, repo, runtime, fileCount, totalBytes, and the pulled file list. username is only included for cloud-generated pull/sync details. pluto-details.json is local metadata and is excluded from pluto push.
Cloud sync and runtime gating
pluto sync pulls every file under <org>/<repo>/... that the token can read and preserves the cloud runtime directory layout locally:
acme/portal/dev/.env -> .pluto/dev/.env
acme/portal/preview/.env -> .pluto/preview/.env
acme/portal/prod/.env -> .pluto/prod/.envAccess is controlled by token scopes:
# Organization admin: all repos and all runtimes in the org.
pluto token create --org acme --scope 'acme/**:read' --scope 'acme/**:write' --label org-admin
# Repo admin: all runtimes for one repo.
pluto token create --org acme --scope 'acme/portal/**:read' --scope 'acme/portal/**:write' --label portal-admin
# Runtime-limited user: preview only for one repo.
pluto token create --org acme --scope 'acme/portal/preview/**:read' --label portal-preview-readerTokens may also be associated with a verified user via --user <email> for operator/audit metadata. Admins can grant access either with the operator CLI or over the API using an org-admin authenticated token.
Operator CLI:
pluto grant org-admin [email protected] --org acme
pluto grant repo [email protected] --org acme --repo portal --access read
pluto grant runtime [email protected] --org acme --repo portal --runtime preview --access read
pluto grant list [email protected] --org acmeFramework-only mode
You can use Pluto without the cloud server as a local runtime-file staging framework:
pluto stage --runtime previewstage applies the same runtime selection rules as push, but writes the selected files back to unprefixed root paths locally and never contacts the server. This lets a repo organize files by runtime while applications consume stable paths.
.pluto/config/app.json -> .pluto/config/app.json
.pluto/preview/secrets.enc -> .pluto/secrets.enc
.pluto/preview.flags.json -> .pluto/flags.json
.pluto/prod/secrets.enc -> ignoredstage also writes .pluto/pluto-details.json with mode: "stage", cloudGenerated: false, and the same non-cloud metrics as cloud pulls.
CLI
Server-side
| Command | Purpose |
|---|---|
| pluto serve | Start the HTTP server |
| pluto org create <name> | Create an organization namespace |
| pluto org list | List organizations |
| pluto token create --org --scope --label --expires | Mint a scoped bearer token |
| pluto token list --org <org> | List tokens |
| pluto token revoke <id> | Revoke a token |
| pluto user ..., pluto service ... | Optional operator metadata/source-warning helpers |
Workspace-side
| Command | Purpose |
|---|---|
| pluto init | Write pluto.config.json |
| pluto push [dir] | Upload runtime-selected files in dir or targetDir |
| pluto pull [--out <dir>] | Cloud pull one runtime into targetDir or --out as unprefixed files |
| pluto sync [--out <dir>] | Cloud sync all readable repo/runtime files into runtime directories |
| pluto stage [dir] [--out <dir>] | Framework-only local runtime staging; reads from dir or targetDir, writes to targetDir unless --out is provided; no server required |
| pluto exec -- <cmd> | Pull then execute a command |
| pluto status | Print resolved config and server health |
All client commands accept --server, --token, --org, --repo, --runtime, --target-dir, and --config.
Web UI
Pluto serves a built-in lightweight Web UI at /. It is intentionally API-first and uses the same endpoints an autonomous agent would use. It supports email/password login, identity/grant inspection, file metadata browsing, user creation, TOTP setup, and org-admin grant actions.
The UI is designed to sit behind Tailscale or another private network boundary. Tailscale identity headers can be added later for SSO-style login, but bearer/session API auth remains the source of authorization.
HTTP API
| Route | Purpose |
|---|---|
| GET /v1/health | Health check, no auth |
| POST /v1/auth/login | Exchange verified email, password, and org for a scoped session token based on DB grants |
| POST /v1/auth/tailscale | Optional trusted-header login when PLUTO_TRUST_TAILSCALE_HEADERS=true |
| GET /v1/me | Inspect token identity, scopes, and user grants |
| GET /v1/tokens | List own tokens, or org tokens for org admins |
| POST /v1/tokens | Mint a scoped API token limited to the caller's grants |
| DELETE /v1/tokens/<id> | Revoke own token, or any org token for org admins |
| POST /v1/users | Org admin creates a user |
| POST /v1/users/verify | Org admin verifies a user email |
| POST /v1/mfa/totp/setup | Authenticated user starts TOTP setup |
| POST /v1/mfa/totp/enable | Authenticated user enables TOTP |
| GET /v1/grants/<org> | Org admin lists grants |
| DELETE /v1/grants | Org admin revokes a grant |
| POST /v1/grants/org-admin | Org admin grants org admin access |
| POST /v1/grants/repo | Org admin grants repo access |
| POST /v1/grants/runtime | Org admin grants runtime access |
| GET /v1/files/<org>/<repo>/<runtime>/<path> | Download raw bytes |
| PUT /v1/files/<org>/<repo>/<runtime>/<path> | Upload raw bytes, application/octet-stream; updates file metadata |
| DELETE /v1/files/<org>/<repo>/<runtime>/<path> | Tombstone file metadata; blob removal is planned |
| GET /v1/files-meta/<org>/<repo>/<runtime> | List DB metadata visible to this token |
| GET /v1/audit/<org> | Org admin reads recent audit events |
| GET /v1/resolve/<org>/<repo>/<runtime> | List visible files under the runtime prefix |
| GET /v1/resolve/<org>/<repo> | List visible files across all readable runtimes for pluto sync |
Scope grammar
Scopes match full storage paths:
*matches exactly one path segment.**matches one or more trailing segments and must be the final token.- Scopes must start with the token org.
- Traversal, absolute paths, empty path segments, unsupported glob syntax, and non-normalized unicode are rejected.
Examples:
--scope 'acme/portal/prod/**:read'
--scope 'acme/portal/preview/**:write'
--scope 'acme/portal/*/config.json:read'Security notes
- Pluto stores bytes verbatim; encryption is caller-owned.
- Use TLS at the reverse proxy/load balancer.
- Keep tokens short-lived and scope them to the narrowest repo/runtime prefix possible.
- The server enforces org isolation and path normalization before touching disk.
PUTrequiresapplication/octet-stream; responses are markedCache-Control: no-store.
License
MIT
