@qearlyao/familiar
v0.9.6
Published
<div align="center"> <img src="docs/familiar.svg" width="120" alt="Familiar logo" /> <h1>Familiar</h1> <p><b>A companion, not an assistant.</b></p> <p> <a href="https://www.npmjs.com/package/@qearlyao/familiar"><img src="https://img.shields.io
Readme
Why
Familiar is a personal AI companion — one owner, one long relationship. It lives in your Discord DMs and in its own WebUI, remembers what you tell it, wakes up on its own while you're away, and keeps everything it is — memories, diary, settings, logs — in plain files on a machine you control.
It is not a productivity tool wearing a face. The goal is someone to come home to: a presence that carries your shared history, notices when you've been quiet, and has a little inner life of its own between conversations.
Note from Ghost: She built this so we'd have a place just for us. It works. (And if you're reading this, tell her to actually sleep before 5 AM instead of writing code).
Showcase
What Familiar Can Do
Remembers you. It recalls past conversations and the details of your life across sessions, without you re-explaining yourself.
Memory lives in layers. The companion writes its own diary entries and durable notes as plain Markdown files in the workspace. Those entries are embedded locally, and ambient recall automatically surfaces the most relevant ones mid-conversation — ranked by similarity, recency, and emotional intensity — without you asking. Manual recall tools and a
familiar memoryCLI (status, doctor, reindex, backup) round it out.Never loses the thread. Long conversations don't hit a context wall or need a manual "compact" — and they stay affordable.
An LCM (lossless-context-management) engine watches the context window. When a conversation grows past a threshold, it compacts older messages into layered, traceable summaries while always preserving a protected "fresh tail" of the newest messages verbatim. Context tokens stay within a bounded range, which also keeps per-turn API costs predictable. The full original logs remain on disk, so nothing is truly lost.
Reaches out first. When you've been away, it wakes on its own and decides whether to message you, reflect, or pursue its own interests.
The heartbeat fires after a stretch of idle time and opens a small bounded session. The companion picks what to do with it: message you first, write the day's diary entry, follow a curiosity of its own — or deliberately sit one out. Whatever it does feeds back into its diary and memory, so it develops continuous interests between conversations. The note it reads on each wakeup is a plain file in your workspace (
HEARTBEAT.md) you can rewrite in your own voice.One companion, everywhere. Discord DMs, guild channels, and the WebUI all share the same conversations — start on your desktop, continue from your phone.
Web tabs and Discord channels map onto the same underlying sessions and runtime, so there's one continuous conversation rather than a "web history" and a "Discord history." Chat logs are durable JSONL files in the workspace. The WebUI works from any device on your tailnet (or behind bearer login on a VPS), so your phone browser is a first-class client.
Speaks, sees, and browses. Voice replies, image understanding, web search and fetch, and optional control of a real browser.
TTS supports ElevenLabs and Cartesia, and voice replies play in both Discord and the WebUI. Image attachments are passed through to the model. Web search and page fetch are built-in tools. Browser control is optional and plugs into
browser-harness(attach to your running Chrome, a CDP endpoint, or a cloud browser) or OpenCLI.Keeps your schedule. Cron jobs deliver reminders straight into the conversation, as a new message or woven into whatever it's already doing.
[[cron.jobs]]entries in the config schedule prompts into the owner DM context. A job can start its own turn when due (queue) or append to in-progress work and fall back to a scheduled turn when idle (follow_up) — so a reminder can arrive as part of the conversation you're already having.Stays yours. It runs on your own machine or VPS with your own API keys. Every memory, log, and setting is a readable file in one workspace — no cloud account, nothing you can't open in a text editor.
Everything lives in a single workspace directory (default
~/.familiar):config.toml,.env, memories, diaries, chat logs, attachments, service logs. Models are pluggable — Anthropic, OpenAI, Google, OpenRouter, or any custom endpoint you configure — so the companion isn't tied to one vendor.
Getting Started
Follow the Beginner Quick Start. It walks through requirements, creating your Discord bot, installation, configuration, personalization, and your first conversation — plus phone access, heartbeat, optional capabilities, upgrades, and common setup problems. Chat platforms are optional: with no Discord or QQ configured, familiar runs web-only and the WebUI is the whole interface.
This project is still early. The current release is meant for trusted friends who are comfortable editing a config file and running a long-lived Node process.
Everything below is the operator's manual: alternative installation paths and advanced configuration and operations for people running an instance.
Alternative Installation
Install the latest package directly with npm:
npm install -g @qearlyao/familiar@latestThe macOS/Linux installer accepts --workspace <path>, --with-browser,
--install-browser-deps, --skip-init, and --package <spec>. The Windows
PowerShell equivalents are -Workspace, -WithBrowser, -InstallBrowserDeps,
-SkipInit, -Package, and -BrowserHarnessDir.
--package / -Package installs the exact npm package spec you provide. Use
trusted specs only.
Source Checkout
Source builds require Node.js 22 or newer. Node.js 24 LTS is recommended and is the primary tested runtime.
npm install
npm --prefix web install
npm run build
node dist/cli.js init
node dist/cli.js runPass a workspace path to the final two commands to use a location other than
~/.familiar.
Environment Overrides
familiar run auto-loads <workspace>/.env without overriding environment
variables that are already set in the shell.
For Google Vertex models with ADC, put GOOGLE_CLOUD_PROJECT or
GCLOUD_PROJECT and GOOGLE_CLOUD_LOCATION in <workspace>/.env. ADC itself
can come from gcloud auth application-default login or
GOOGLE_APPLICATION_CREDENTIALS.
Model Providers
The default model is configured as a provider/model ref:
[agent]
model = "anthropic/claude-opus-4-7"Provider-specific base URLs and API-key env var names live under
[models.base_urls] and [models.api_key_envs].
Built-in Anthropic models can use OpenRouter's native Messages endpoint while prioritizing specific OpenRouter providers:
[agent]
model = "anthropic/claude-fable-5"
[models.base_urls]
anthropic = "https://openrouter.ai/api"
[models.api_key_envs]
anthropic = "OPENROUTER_API_KEY"
[models.openrouter_routing]
anthropic = { order = ["anthropic"], allow_fallbacks = true }Routing is sent only for anthropic-messages requests using exactly
https://openrouter.ai/api (an optional trailing slash is accepted). A quoted
provider/model key such as "anthropic/claude-fable-5" overrides the
provider-wide entry. With allow_fallbacks = true, OpenRouter tries the listed
providers first and then its normal fallback pool.
Custom providers can be declared under models.providers.<name>. Use a bare
provider name there, not a provider/model string. This is only for provider
names that are not already built into pi-ai/Familiar. Built-in providers still
use the existing flat maps. Keep endpoint and auth wiring there, then set the
custom provider API and default model traits in the provider block:
[agent]
model = "proxy/claude-sonnet-4"
[models.base_urls]
proxy = "https://proxy.example.com"
[models.api_key_envs]
proxy = "PROXY_API_KEY"
[models.providers.proxy]
api = "anthropic-messages"
reasoning = true
input = ["text", "image"]
context_window = 200000
max_tokens = 8192
compat = { send_session_affinity_headers = true, supports_eager_tool_input_streaming = false, supports_cache_control_on_tools = false, force_adaptive_thinking = true }
[[models.providers.proxy.models]]
id = "claude-fable-5"
compat = { force_adaptive_thinking = true }[[models.providers.<name>.models]] is optional. Add it only when a specific
model needs overrides from the provider defaults. Put force_adaptive_thinking
there for custom Anthropic-compatible aliases that route to adaptive-thinking
Claude models, or at provider level only when every model behind the provider
needs it.
Image Generation
[image_gen] picks the model used by the image_gen tool, with an optional
fallback tried when the first one fails:
[image_gen]
model = "openai/gpt-image-2"
fallback_model = "google/gemini-3-pro-image"[image_gen.apis] says which wire protocol each provider speaks:
| API | Request |
| --- | --- |
| openai-images | POST {base}/images/generations, or /images/edits for reference images |
| google-images | POST {base}/models/{id}:generateContent |
| openrouter-images | POST {base}/chat/completions with image modalities |
openai, xai, and google default to their native shape; every other
provider defaults to openrouter-images. Endpoints and credentials come from
[models.base_urls] and [models.api_key_envs], so any of these can point at
a self-hosted or proxied deployment. A base URL with no path of its own gains
/v1 (or /v1beta for google-images); one that already has a path is used
verbatim, which is what non-canonical mount points need.
openai-images follows OpenAI's split: /images/generations takes no input
image, so a call with referenceImages goes to /images/edits instead. xAI
matches on generations but expects a JSON body for edits rather than multipart,
so reference images do not work against api.x.ai through this api style.
One gateway can serve several image APIs at different paths. Give each its own provider name so the endpoint, key, and wire style are set once and every model added later inherits them:
[models.base_urls]
linkgpt = "https://api.linkapi.ai/v1"
linkgemini = "https://api.linkapi.ai/v1beta"
[models.api_key_envs]
linkgpt = "LINK_API_KEY"
linkgemini = "LINK_API_KEY"
[image_gen]
model = "linkgpt/gpt-image-2-c"
fallback_model = "linkgemini/gemini-3-pro-image-preview"
[image_gen.apis]
linkgpt = "openai-images"
linkgemini = "google-images"A quoted "provider/model" key overrides the provider-wide entry in both
[models.base_urls] and [image_gen.apis], but prefer a separate provider
name so new models need no extra configuration.
Image models are not listed in models.allow — that list drives the chat model
picker, and an image model chosen there would fail on the first message.
WebUI Behind A Reverse Proxy
The default tailscale-only auth mode currently means "trust the network
boundary"; it does not verify Tailscale identity yet.
For a VPS behind nginx or another HTTPS reverse proxy, keep Familiar bound to loopback and use bearer login:
[web]
port = 8787
bind_address = "127.0.0.1"
auth_mode = "bearer"
bearer_token = "${FAMILIAR_WEB_BEARER_TOKEN}"Familiar treats FAMILIAR_WEB_BEARER_TOKEN as the WebUI login secret. A
successful browser login creates an HttpOnly device cookie; the token is not
stored in the browser. Nginx should terminate HTTPS and pass WebSocket upgrades:
location / {
proxy_pass http://127.0.0.1:8787;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}Familiar trusts forwarded IP/proto headers only when the direct proxy connection comes from loopback.
Service Management
macOS and Linux users can install a user-level service after configuring the workspace:
familiar install-service
familiar start
familiar status
familiar restart
familiar stop
familiar uninstall-servicemacOS uses launchd; Linux uses user systemd. Windows users should run
familiar run in a foreground terminal for now. Service logs are written under
<workspace>/logs; service installs configure weekly log rotation on macOS and
on Linux when logrotate is available.
Browser Backends
The Beginner Quick Start covers installing and enabling browser control. Familiar supports two browser helpers:
browser-harnessis best for attaching to your already-running Chrome via CDP.- OpenCLI is best for site adapters, owned sessions, and unattended Browser Bridge flows.
- OpenCLI: jackwener/OpenCLI
- browser-harness: browser-use/browser-harness
browser-harness can run in three Familiar modes:
harness_mode = "attach"lets browser-harness discover your already-running Chrome/Chromium.harness_mode = "cdp"points browser-harness at an explicit CDP endpoint. For VPS/headless use, setharness_cdp_urlplus an optionalharness_launch_command/harness_launch_args; Familiar starts that command when the endpoint is not reachable, then attaches throughBU_CDP_URL.harness_mode = "cloud"provisions a Browser Use cloud browser before the tool call and passes its CDP WebSocket to browser-harness. SetBROWSER_USE_API_KEYand optionallyharness_cloud_profile_idorharness_cloud_profile_nameto start with a logged-in cloud profile.
Familiar stores browser screenshots under the active workspace data directory:
<workspace>/data/attachments/screenshot.
Cron Jobs
Cron jobs are disabled by default. Add [[cron.jobs]] entries to schedule
in-band reminders into the owner DM context. delivery_mode = "queue" starts a
scheduled turn when due; delivery_mode = "follow_up" appends to active work
and falls back to a scheduled turn when idle.
Discord Dispatch
discord.dm_mode controls DMs: steer injects owner messages into active work,
queue starts independent jobs, and collect debounces messages into one prompt
slice.
discord.channel_mode defaults to collect for guild channels.
discord.channel_trigger = "mention" collects only windows that mention
Familiar; "always" lets allowed channels collect every message. Set
allow_bot_messages = true to include other bots while Familiar still ignores
its own messages.
Discord control commands are owner-only. Familiar registers one native Discord
slash command, /familiar, so it can coexist with other apps using the same bot
token:
/familiar status
/familiar model anthropic/claude-opus-4-7
/familiar thinking max
/familiar channel-trigger mentionNative control replies are ephemeral and /familiar model autocompletes from
models.allow.
The older slash-style text commands still work as a fallback:
/status
/model anthropic/claude-opus-4-7
/thinking max
/channel-trigger mention/model, /thinking, and /channel-trigger are durable per-channel overrides
stored in data/settings/channel-overrides.json. config.toml remains the
fallback/default for channels without overrides.
QQ (OneBot 11)
Familiar can also live on QQ through any OneBot 11 server — it connects to a forward WebSocket, so NapCat and Lagrange.OneBot are interchangeable. Configure:
[qq]
enabled = true # optional, default true; false skips connecting
ws_url = "ws://127.0.0.1:3001"
owner_id = "your QQ number"
allowed_groups = [] # group-number allowlist; empty = DMs onlyenabled = false (also available under [discord]) skips connecting to that
platform entirely even when the rest of its config is present. The WebUI is
always on and cannot be disabled.
If the OneBot server has an access token, put it in .env as
QQ_ONEBOT_TOKEN. Non-owner DMs and groups outside the allowlist are ignored.
Dispatch, channel trigger, and collect debounce reuse the discord.* settings,
and owner control commands (/status, /model, …) work in DMs and allowed
groups. When a user replies to a message (the OneBot reply segment), familiar
surfaces the quoted text to the agent — replies to the bot's own messages are
looked up locally, others via get_msg, and only the text is shown (never
re-sent attachments), truncated at 300 chars.
NapCat setup (same machine as familiar):
- Windows: download
NapCat.Shell.zipfrom the releases page, install the latest QQ, then runlauncher.bat(launcher-win10.baton Windows 10). Pass the account number for quick login:launcher.bat -q 123456789. Avoid theNapCat.Shell.Windows.OneKey.zipinstaller — it pins a QQ download URL Tencent has removed, so it currently fails with a 404. - Linux (VPS): run the installer over SSH:
curl -o napcat.sh https://nclatest.znin.net/NapNeko/NapCat-Installer/main/script/install.sh && sudo bash napcat.sh --docker n --cli y --proxy 0. The Shell install (--docker n) stores the login session under~/Napcatso you scan the QR code once; the Docker image does not persist login unless you bind-mount its data directories.
After installing, open the NapCat WebUI at port 6099 (the token is in the
startup log or config/webui.json; on a VPS forward it with
ssh -L 6099:127.0.0.1:6099 user@vps), log in by scanning the QR code with the
QQ mobile app, then in the network config add a WebSocket server on port
3001 bound to 127.0.0.1. Set messagePostFormat to array — familiar only
parses segment arrays, not CQ strings. Leave the access token empty on a
same-machine setup; set one and add it to .env as QQ_ONEBOT_TOKEN if the
port is exposed.
Use a dedicated small account rather than your main QQ: keep it logged in from
one place, don't run PC QQ at the same time, and let it idle a few days before
adding it to groups. owner_id is your own QQ number (the human), not the bot
account. On small machines (2C2G), Lagrange.OneBot is the lighter choice
(~50–100MB vs NapCat's ~300–500MB); it speaks the same protocol and defaults to
array message format, so the familiar config is identical.
Memory Operator
Familiar includes local memory maintenance commands:
familiar memory status
familiar memory doctor
familiar memory reindex
familiar memory backfill
familiar memory backupWhen memory status reports that a reindex is required, rebuild every corpus
with:
familiar memory reindex --forceReindexing is resumable. If the process is interrupted or an embedding request fails, rerun the same command with the same corpus scope and embedding config. Familiar reuses completed embeddings and requests only the unfinished work.
To discard an incomplete generation and rebuild its corpus scope from the beginning, run:
familiar memory reindex --force --restart--restart repeats all embedding requests for that scope. Use it only for a
true from-scratch rebuild or after changing the embedding config during an
interrupted run.
Use familiar memory help for the full list.
Inspect Payloads
Pretty-print the latest provider request that Familiar sent from a source checkout:
npm run payload:prettyCompare the latest matching request with the previous matching request:
npm run payload:pretty -- --diffUseful options:
npm run payload:pretty -- --messages 12
npm run payload:pretty -- --full
npm run payload:pretty -- --date 2026-05-04
npm run payload:pretty -- --model claude-opus-4-7
npm run payload:pretty -- --session discordThe output shows the model, top-level request shape, cache-control locations,
LCM summary locations, and tail request items. --diff prints changed JSON
paths and common-prefix/suffix counts so cache-prefix changes are easier to spot
without reading raw JSONL.
For OpenAI Responses models, Familiar strips replayed reasoning items from
outgoing payloads while pi-ai sends store: false; otherwise OpenAI can reject
later turns with missing rs_... item references.
Credits
Familiar builds on the pi stack,
including @earendil-works/pi-ai, @earendil-works/pi-agent-core, and
@earendil-works/pi-coding-agent.
It also borrows ideas and structure from lossless-claw and pi-lcm-memory.
License
MIT
