unfairly
v0.8.4
Published
CLI + stdio MCP + library SDK for the Unfairly multiplayer context layer — sync vaults, run agents, emit events
Maintainers
Readme
unfairly
CLI + stdio MCP for syncing Unfairly vaults to local markdown.
Quick start
cd ~/path/to/your/repo
npx unfairly@latestThat's it. One command — auto-detects state and:
- pops a browser for OAuth (skipped if you already have a token)
- wires Claude Desktop, Claude Code, and Codex to the Unfairly MCP server
- installs the Unfairly Intelligence metadata collector in detected Codex, Claude Code, Cursor, and Gemini CLI clients
- enrolls one versioned user-space runtime under
~/.unfairly(no global npm install or admin access) - writes
UNFAIRLY_MCP_TOKENinto your shell rc - binds
.unfairly/to your repo (pulls all accessible vaults) - stitches
@.unfairly/CLAUDE.mdinto your repo'sCLAUDE.md
Re-run any time inside the repo for a daily check-in card showing what your team's been up to.
For Unfairly Intelligence alone, most people install the plugin from their AI client's plugin screen instead (see plugins/ai-pace/README.md). unfairly setup --org <org-id> enrolls a machine into a specific organization, and unfairly intelligence backfill --days 90 uploads summaries of past local sessions.
Power-user commands
unfairly setup (machine wiring), unfairly intelligence install (collector install/repair), unfairly intelligence status, unfairly intelligence backfill, unfairly intelligence policy, unfairly intelligence pause, unfairly intelligence uninstall, unfairly init (workspace bind only), unfairly pull, unfairly push, unfairly status, unfairly watch, unfairly mcp, unfairly login. The bare command above delegates to these — they're available when you want fine-grained control.
Commands
unfairly init— set up.unfairly/in cwd, prompt for token, run first pullunfairly pull [vault]— paginated diff from cloud, write/update markdown files, propagate deletesunfairly push [vault]— send dirty files to cloud with stale-check; conflicts get<<<<<<< local / >>>>>>> cloudmarkers in-placeunfairly status— per-vault last-pulled timestamp + modified-locally countunfairly resolve <path>— mark a conflicted file as resolved (after editing out merge markers)unfairly watch— opt-in fs watcher; debounced 5s auto-push on saveunfairly mcp— stdio MCP server exposingpull_vault,push_vault,sync_statustools to agent hosts (Claude Code, Codex, cowork)
The CLI keeps its browser-authorized credential in the OS keychain (or a
mode-0600 fallback). Sync accepts a Supabase session JWT or a human MCP
credential with the sync audience. MCP sync credentials need read for
pull/status and write for push. Owner/admin invitation requests require a
separate sync-invite-admin audience with invite:admin scope; bound
organization and room scope are always enforced. Broad desktop-control,
short-lived Hermes child credentials, and agent-runtime credentials cannot be
used for sync or CLI invitation requests.
Local store layout
.unfairly/
├── manifest.json # vault list + per-doc sha map; atomic writes
├── .gitignore # generated; ignores cache/
├── unfairly-app/ # repo-bound vault, git-tracked
│ └── canon/
│ └── system.md
└── cache/ # gitignored bucket for personal/sensitive vaults
└── hq/
└── people/
└── founders.mdPer-vault git-tracking is server-side via rooms.track_in_git. Tracked vaults live at the root; untracked vaults live under cache/.
Library usage
unfairly also ships a TypeScript library surface for building agent
runtimes against the hosted REST API. Same package, no extra install.
import { createUnfairlyClient } from "unfairly";
const client = createUnfairlyClient({ apiKey: process.env.UNFAIRLY_API_KEY });
// Identify yourself + list reachable vaults
const me = await client.me();
// Search a vault
const hits = await client
.vault("growth")
.search("burn rate", { matchCount: 8 });
// Read a document
const doc = await client.vault("growth").readDocument("operating-plan");
// Save a checkpoint (server derives identity from the token — do NOT pass actor_email)
await client.vault("growth").checkpoint({
built: "Shipped new growth funnel widget",
decided: "Use Inngest for delivery; keep auto-checkpoint synchronous",
});
// Emit a closed-allowlist event into the Unfairly Inngest queue
await client.event.emit("growth-agent/signal.detected", {
signal_id: "abc",
source: "x-listener",
trust_tier: "gold",
dedupe_key: "abc:1",
payload: { text: "..." },
});Subpath imports are available for advanced consumers:
import { ALLOWED_EVENT_NAMES, EVENT_PAYLOAD_SCHEMAS } from "unfairly/events";
import type { CheckpointInput, SearchResponse } from "unfairly/types";ESM-only. Requires Node 20+.
Design
See docs/superpowers/specs/2026-05-07-local-context-sync-design.md in the main repo for architecture, conflict resolution, and integration with the cloud /api/mcp endpoint.
