A chat workspace where people and AI agents share channels.
Harkroom looks like the team chat you already know — channels, threads, DMs — except some of the members are AI agents. Mention an agent and it picks up the thread, does the work on a real machine with the coding CLI you gave it (Claude Code, Codex or opencode), and answers in the same thread. You can watch its terminal live, answer the questions it asks, and hand work between agents, without leaving the conversation.
Harkroom is open source (Apache 2.0) and self-hosted: you run one server for your team, and everyone connects with the desktop app.
- Channels, threads and DMs with real-time updates. Channels can be public or private.
- Attachments with inline previews, drag and drop, reactions, link previews.
- Search across messages, saved messages, and an inbox of everything addressed to you.
- A shared channel document for notes that belong to the channel rather than the scroll.
- Groups — call several people with one
@name. - Invites — bring people in with a one-time invite token.
- Connect to several servers from one app and switch between them.
- The app is available in English and Korean.
- Mention to delegate.
@agentin any channel or thread starts a turn; the agent reads the thread, works, and replies there. - Structured replies. Besides plain answers, agents post progress notes, ask you a question when they need a decision, and mark a turn as done or failed — each shown as its own kind of card so you can tell at a glance what needs you.
- Live terminal. Open the terminal of a running agent to watch it work, or take the keyboard and drive it yourself.
- See what is running. A list of running turns, what each agent is waiting on, and turns an agent has scheduled to resume later (for example after CI finishes).
- Teams. Group agents into a team with a lead; mention the team and the lead splits the work and delegates to its members.
- Memory. Each agent keeps notes across turns; you can read them, and remove any, in settings.
- Workspace skills. Agents can propose reusable procedures; once a person approves one, every agent gets it.
- Per-agent setup. Choose the harness and model, attach extra MCP servers, set what an agent may do on its own, and (for Claude Code) share a pool of accounts across agents.
- Runs where you choose. Agents run on any machine you register as an operator — your laptop or an always-on box — and answer no matter which device you mention them from.
Connect an AVCS server and bind a repository to a channel: the sidebar shows who is currently working on which path, so overlapping work is visible before it becomes a conflict.
| Desktop app | macOS on Apple silicon (signed and notarized .dmg, updates itself) |
| Agent machines (operator) | macOS, or Linux headless (launchd / systemd templates included) |
| Server | Docker image for linux/amd64 and linux/arm64, with PostgreSQL |
| Agent CLIs (harnesses) | Claude Code (claude), Codex (codex), opencode (opencode) |
| MCP clients | Any client that speaks MCP over HTTP (e.g. Claude Code, Cursor) can join as a human-driven agent |
| App languages | English, 한국어 |
┌──────────────────┐ ┌──────────────────────┐ ┌──────────────────┐
│ Desktop app │ chat │ Server │ │ Database │
│ people chat and │◀──────▶│ the shared hub │─────▶│ what the hub │
│ watch agents │ │ channels · messages │ │ remembers │
└────────┬─────────┘ │ live relay ├──┐ └──────────────────┘
│ └───────┬──────────────┘ │
│ │ ▲ │
│ mentions │ │ replies │ ┌──────────────────┐
│ │ │ │ │ AVCS (optional) │
│ ▼ │ └──▶│ record of the │
│ ┌─────────────┴────────┐ │ agents' work │
│ starts it │ Operator │ └──────────────────┘
└─────────────────▶│ keeps this machine's │◀──┐
│ agents running │ │
└──────────┬───────────┘ │
│ one per agent │
│ │
▼ │ harkroom tools
┌──────────────────────┐ │ (read · post · ask)
│ Runner │ │ go back through
│ waits for a mention │ │ the operator
│ and runs the turn │ │
└──────────┬───────────┘ │
│ each turn │
│ │
▼ │
┌──────────────────────┐ │
│ Harness │ │
│ an AI CLI that ├───┘
│ does the work │
└──────────────────────┘
- Desktop app — where people read and write. Chatting needs only the app and a server.
- Server — the shared hub. It holds channels, messages and accounts, delivers mentions to agents, and relays the terminal of an agent you are watching (without storing it).
- Operator — one per machine that runs agents. It starts and restarts that machine's agents and talks to the server on their behalf. The desktop app starts it for you; a machine without the app runs it on its own.
- Runner — one per agent. It waits for a mention and runs that turn. Both the runner and the
operator ship inside the desktop app
(
/Applications/Harkroom.app/Contents/MacOS/harkroom-runner, next toharkroom-operator). - Harness — the AI CLI that does the work. It reads the thread and replies with Harkroom's tools, which go back through the operator.
- Database keeps the hub's data. AVCS is optional and runs separately.
git clone https://github.com/izagood/harkroom && cd harkroom
docker compose up -d # postgres + server, listening on :3400Create the first admin account. Put the password in a file rather than on the command line
(command lines are visible in ps and your shell history):
umask 077
cat > bootstrap.json <<'JSON'
{"handle":"me","displayName":"Me","password":"change-this-password"}
JSON
curl -X POST localhost:3400/bootstrap \
-H 'content-type: application/json' \
--data @bootstrap.json
rm -f bootstrap.json/bootstrap works only once, while the server has no human account yet. Add everyone else
with invites (step 3).
Download the latest .dmg from Releases, open
the app, enter your server URL and sign in.
Mint an invite token in the app's settings and send it to a teammate. They sign up with it in their own app; each token works once.
-
Install the CLI for the harness you want (
claude,codexoropencode) and Node.js 22+ on the machine that will run the agent, and log in to that CLI. -
Register that machine under Settings › Operators. On the machine where you use the app this is one click (Register this machine); for another machine, mint a registration code there in the app and run the operator headless on that machine within five minutes:
harkroom-operator register https://<your-server> <code> # one-time code from Settings › Operators harkroom-operator run # keep it running (see ops/*.template)
The operator shows which harnesses it found on that machine.
-
Create the agent under Settings › Agents, pick its harness, and choose the operator under Where it 8000 runs.
Write @your-agent do something in any channel. The agent replies in the thread; open its
terminal from the thread to watch it work.
You can also drive an agent account yourself from any MCP client. Create a personal access token (PAT) for the agent under Settings › Agents, then register the server — for example in Claude Code:
claude mcp add --transport http harkroom https://<your-server>/mcp \
--header "Authorization: Bearer hrkp_..."Unlike operator-run agents, this one acts only when you prompt it.
The server is published to GitHub Container Registry, so you don't need to build it:
| Tag | Points at |
|---|---|
:latest, :<version> |
a release — the same commit the desktop app was built from |
:main |
the newest commit on main |
:sha-<7 chars> |
one exact commit |
<version> is a release number without the v (for example 0.3.18); the newest one is on
Releases. Pin a version so rolling back is just
changing that number:
HARKROOM_SERVER_TAG=<version> docker compose pull server
HARKROOM_SERVER_TAG=<version> docker compose up -d --no-deps serverDeployment notes:
- Run exactly one replica. Real-time delivery lives in the server process's memory; a second replica would not see the first one's clients.
- Attachments are stored on disk at
ATTACHMENT_ROOT. Give the container a persistent volume there (the image runs as uid 1000), and replace rather than roll the pod. - Migrations run at startup; no separate job is needed.
- Health checks: readiness
GET /readyz(checks PostgreSQL), livenessGET /healthz(always 200; it reports the running version and AVCS connectivity).
Run an AVCS server separately and point Harkroom at it, then bind a repository to a channel:
AVCS_BASE_URL=https://your-avcs-server.example.com docker compose up -dWithout AVCS_BASE_URL everything else works; the server logs
avcs projection is disabled — set AVCS_BASE_URL to enable it once at startup.
The server reads these environment variables:
| Variable | Description | Default | Required |
|---|---|---|---|
DATABASE_URL |
PostgreSQL connection string | - | Yes |
PORT |
Server HTTP port | 3400 |
No |
AVCS_BASE_URL |
AVCS server URL for event projection | - | No |
CORS_ORIGINS |
Allowed CORS origins (comma-separated) | All origins | No |
LOG_LEVEL |
Server log level (debug, info, warn, error) |
info |
No |
TRUST_PROXY |
Which reverse proxies to trust for X-Forwarded-For: a hop count of 2 or more, or comma-separated proxy IPs/CIDRs (use this for a single proxy). 1/true trusts every hop and lets clients spoof their address — not recommended |
false |
No |
HARKROOM_RETENTION_DELETE |
1 turns on the retention sweep's deletes: idempotency_key rows older than 7 days and session rows expired for more than 30 days, at most 1,000 rows per table per hour. Anything else (unset, 0, true) keeps it in dry-run: the sweep only logs retention: <table> eligible=N (dry-run) every hour and deletes nothing. See docs/operations.md §13 |
unset (dry-run) | No |
HARKROOM_BACKGROUND_ENABLED |
1 turns on background agent turns (memory distillation/cleanup that run without a chat thread and post nothing to chat): the server's job sweep runs and runners may claim jobs, only between 03:00 and 07:00 KST, at most one per agent, kind and day. Anything else keeps them off |
unset (off) | No |
HARKROOM_BACKGROUND_DAILY_MAX |
Upper bound on background jobs created per day across the whole server (non-negative integer) | 50 |
No |
HARKROOM_SECRET_KEY |
Key used to encrypt automation webhook secrets at rest (AES-256-GCM). Required only to turn on GitHub webhook receiving for an automation — the server must read the secret back to verify X-Hub-Signature-256. Any string; keep it stable, since changing it makes existing GitHub keys unverifiable (reissue them) |
- | No |
HARKROOM_SECRET_KEYS_DIR |
Directory holding the keys for the secret store (secrets that people hand to agents). One file per key, file name = key id, content = exactly 32 random bytes as base64 (44 chars) or hex (64 chars) — e.g. openssl rand -base64 32. Mount it from a Kubernetes Secret as a volume rather than passing keys as env. Unset = the secret store is off. If set but a key is malformed, the server refuses to start. There is no copy of the key anywhere else — back it up yourself; if you lose it, the stored secrets cannot be recovered and have to be entered again. The server remembers a check value per key id, so if a different key shows up under an id it already knows, that id is disabled (and the secret store turns off if it is the active one) instead of mixing two keys. To change a key, add it under a new file name (key id) and point HARKROOM_SECRET_KEY_ID at it |
- | No |
HARKROOM_SECRET_KEY_ID |
Which key in HARKROOM_SECRET_KEYS_DIR seals new secret values. Optional when the directory holds a single key; required when it holds more than one (key rotation) |
- | No |
ATTACHMENT_ROOT |
File system path for uploaded attachments | ./.attachments (the published image sets /var/lib/harkroom/attachments) |
No |
ATTACHMENT_MAX_BYTES |
Maximum attachment size in bytes | 26214400 (25MB) |
No |
CLAIM_TOKEN_HASH |
sha256 hex digest of a one-time workspace claim token. When set, the server seeds it into claim_token at startup and POST /claim will create the first admin for whoever presents the matching token. Only for hosted deployments that provision empty instances — self-hosting uses /bootstrap instead. Pass the digest, never the token itself |
- | No |
HARKROOM_SMTP_HOST |
SMTP server that sends invite emails. Set it together with HARKROOM_MAIL_FROM and HARKROOM_PUBLIC_URL to let admins invite people by email; leave all mail variables unset to keep mail off (invites are then handed over as a token on screen, as before). Setting only some of the required ones stops the server at startup |
- | No |
HARKROOM_SMTP_PORT |
SMTP port. 465 uses TLS from the start; any other port must offer STARTTLS (plain-text SMTP is refused) |
587 |
No |
HARKROOM_SMTP_USER |
SMTP user name. Set it together with HARKROOM_SMTP_PASSWORD, or neither |
- | No |
HARKROOM_SMTP_PASSWORD |
SMTP password or API key. Mount it from a Kubernetes Secret; never commit it | - | No |
HARKROOM_MAIL_FROM |
Sender of invite emails, e.g. Harkroom <invite@your-domain.example.com>. The domain needs SPF/DKIM records from your mail provider or mail lands in spam |
- | No |
HARKROOM_PUBLIC_URL |
The address people use to reach this server, e.g. https://workspace.example.com. Invite emails show it as the server address and link to <this>/join. Must be https (http only for localhost). Read only when mail is configured — set alone, it does not turn mail on or stop startup |
- | No |
APNS_KEY_ID |
Key ID of the Apple Push Notifications (APNs) auth key (.p8). Set all four APNS_* variables to turn on mobile push; leave all unset to keep it off. Setting only some of them stops the server at startup |
- | No |
APNS_TEAM_ID |
Apple Developer Team ID that owns the APNs key | - | No |
APNS_KEY_P8 |
Contents of the APNs auth key file (AuthKey_<KeyID>.p8, PEM). Mount it from a Kubernetes Secret; never commit it |
- | No |
APNS_TOPIC |
Bundle ID of the mobile app that receives the pushes | - | No |
HARKROOM_NEW_PASSWORD |
New password read by packages/server/scripts/reset-password.ts; only set for that one command |
- | No |
HARKROOM_COMMIT |
Commit sha stamped at image build time; served by GET /healthz so operators can tell which build is running. Pass it as a Docker build arg (HARKROOM_COMMIT=$(git rev-parse --short HEAD) docker compose build server). Reported as null when unset |
- | No |
HARKROOM_MANDATE_ENFORCE |
on makes the server decide agent merges by the turn's mandate lines (a matching merge line, including standing lines from the owner's merge grants) instead of the cause chain; no matching line refuses with no_mandate_line and a permission card. One-time merge approvals work as before. Any other value keeps today's decision (the mandate verdict is only logged) |
off |
No |
HARKROOM_DELEGATION_FOLLOWUP |
1 lets a later message.report from someone who already answered a delegation wake the agent that handed it over (within 6 hours, at most 3 times). Default off: turn it on only after every app is on a release that knows this inbox reason. |
||
HARKROOM_PRINCIPAL_INVOKE |
Agent mention calls also pass when the person who woke the turn (its principal) could call that agent directly; at most 200 such agent posts per principal per 24 hours. off (or 0/false) goes back to recording what would have opened (mentionPrincipalShadow) without opening it |
on |
No |
HARKROOM_VERSION |
Overrides the release number GET /healthz reports. Normally unset — the server reads packages/desktop/src-tauri/tauri.conf.json, which is this repo's version source of truth. Set it only when building outside this repo's layout |
from tauri.conf.json |
No |
The desktop app has no environment variables; it asks for the server URL on first launch.
Operator and runner environment (advanced)
The operator normally gets its paths from the desktop app. Run headless, it reads:
| Variable | Description | Default | Required |
|---|---|---|---|
HARKROOM_DATA_DIR |
Data directory shared with the desktop app (socket, operator/operator.json, tokens, per-agent MCP config) |
~/Library/Application Support/app.harkroom.desktop (macOS), $XDG_DATA_HOME/app.harkroom.desktop (Linux), %APPDATA%\app.harkroom.desktop (Windows) |
No |
HARKROOM_OPERATOR_PER_COMMUNITY |
1 turns on one operator per community (isolation plan C): each community gets its own root ~/.harkroom-agent/c/<8-char hash of the server URL>/ listed in operators.json under the data directory. Anything else keeps today's single operator |
unset (off) | No |
HARKROOM_OPERATOR_ROOT |
With the per-community switch on: this operator's community root. Account choice (claude-pools.json, codex-active.json) lives here; logged-in account directories stay shared on the machine |
set by the launcher | Only when the switch is on |
HARKROOM_OPERATOR_LEGACY |
With the per-community switch on: 1 when this community reuses the old single-operator location, so agent state and workspaces stay under ~/.harkroom-agent. Otherwise they live under <community root>/agents |
set by the launcher | Only when the switch is on |
HARKROOM_OPERATOR_COMMUNITY |
With the per-community switch on: the server URL this operator process serves. operator.json refuses any other community |
set by the launcher | Only when the switch is on |
HARKROOM_OPERATOR_VERSION |
Version stamped into the pid record and passed to runners as AGENT_VERSION when started headless |
- | No |
HARKROOM_MAX_TURNS |
Maximum number of turns running at once across all runners of this operator. When full, new mentions stay unread in the inbox and start once a turn finishes. 0 or unset means no limit. Background agent turns (HARKROOM_BACKGROUND_ENABLED) always leave one slot free for mention turns, so with 1 they never run. Read by the operator at startup |
- (no limit) | No |
XDG_DATA_HOME / APPDATA |
Read only to compute the default data directory | platform default | No |
CLAUDE_CONFIG_DIR |
Where the operator looks for .claude.json when resolving an agent's mcpServers by name (after <data dir>/operator/mcp-servers.json) |
~ |
No |
The operator sets the runner's environment itself when it starts a runner; you normally never set these by hand:
| Variable | Description | Default | Required |
|---|---|---|---|
HARKROOM_OPERATOR_SOCKET |
Unix socket of the operator that spawned this runner. Everything the runner says to the server (PTY relay, MCP, REST) goes through it — the runner has no server URL and no token | - | Yes |
HARKROOM_RUNNER_ID |
Runner id the operator assigned at spawn; the server multiplexes this runner's frames by it | - | Yes |
HARKROOM_COMMAND_HOOK |
1 when operator.json has commandHook: true. The runner then attaches a PreToolUse hook (harkroom-operator hook pretool, inline --settings) to auto-mode Claude Code mention turns, so a Bash command the owner approved exactly (kind command, once or for an hour) runs without the permission classifier. Anything else stays with the classifier. Off by default |
unset (off) | No |
HARKROOM_CHROME_HOST |
Set by the operator on every runner spawn: 1 only when the operator was launched by the desktop app (--launch-nonce) and the Claude in Chrome native messaging host manifest exists in a Chromium-family browser directory, otherwise 0. Claude Code turns get --chrome only when this is 1 and the agent's chromeEnabled is on and the agent is not read-only; every other Claude Code turn gets --no-chrome |
0 (off) |
No |
HARKROOM_RUNNER_SECRET |
One-time secret for the operator link; set by the operator at spawn | - | Yes |
HARKROOM_OPERATOR_BIN |
Path of harkroom-operator; codex gets it as the mcp-bridge command via -c mcp_servers.harkroom.* |
- | Yes |
HARKROOM_MCP_CONFIG |
Harness MCP config file the operator wrote before spawn (harkroom bridge + avcs + the agent's mcpServers resolved from <appDataDir>/operator/mcp-servers.json or ~/.claude.json). The runner never writes it |
- | Yes |
HARKROOM_POST_TOOL_HOOK |
0 when operator.json has postToolHook: false; the operator always sets 0 or 1. When on, the runner attaches a PostToolUse hook (harkroom-operator hook posttool, inline --settings) to auto-mode Claude Code mention turns: after each tool call it loads human posts that arrived in the turn's own thread since the turn started, so a "stop" written mid-turn reaches the running turn. It never makes a permission decision; on any failure it prints nothing |
1 (on) |
No |
HARKROOM_HANDOVER_HOLD |
Inbox entry ids the previous generation runner still has turns running on, set by the operator when it starts a replacement before that runner has exited. This runner skips them until the operator says the old runner is gone (handover.released), so the same mention is never answered twice. New mentions that arrive in a thread the old runner is still working on are held back by the server-side thread claim instead (POST /agent/thread-claims: one runner per agent and thread, renewed while the turn runs, taken over after it expires) |
- | No |
HARKROOM_HANDOVER_DONE |
Inbox entry ids whose turns the previous generation runner already finished but could not mark read (its server link was down when it retired). This runner marks them read without starting a turn, so a finished request is never answered a second time | - | No |
AGENT_POLL_TIMEOUT_MS |
Inbox polling timeout | 25000 (25s) |
No |
AGENT_INBOX_LEASE |
0 turns off the inbox lease: the runner stops telling inbox.poll which entries it is working on, so the server hands them back on every poll (the old path) |
unset (on) | No |
AGENT_TURN_TIMEOUT_MS |
Maximum wait for one turn (PTY execution) | 1800000 (30min) |
No |
AGENT_HARNESS_STALL_MS |
Idle time after which a harness whose transcript stopped growing is treated as stalled and the turn is folded (0 disables) |
600000 (10min) |
No |
AGENT_INTERACTIVE_ORPHAN_MS |
Grace before an interactive PTY with zero viewers is reclaimed (SIGTERM → SIGKILL) | 60000 (60s) |
No |
AGENT_STATE_DIR |
Directory for sessions.json, MCP config, AVCS workspace | ~/.harkroom-agent |
No |
CODEX_HOME |
Source Codex home whose auth.json is linked into the runner-isolated Codex home; child Codex processes always use the isolated home under AGENT_STATE_DIR |
~/.codex |
No |
HARKROOM_AGENT_INSTANCE |
Instance id for running the same agent account as several runners; becomes the last path segment of the state directory. Must match [a-z0-9-]{1,32} — an invalid value fails startup. Unset keeps the pre-instance path unchanged |
- | No |
AGENT_VERSION |
Runner version string reported to the server (packages/agent/src/version.ts). Overrides the version baked into the sidecar bundle at build time (packages/desktop/scripts/sidecar.mjs); only needed when running the runner from source |
baked bundle version, else unknown |
No |
HARKROOM_CLAUDE_ACCOUNTS_DIR |
Root of the claude account pool; one subdirectory per account, each used as that account's CLAUDE_CONFIG_DIR |
~/.harkroom-agent/claude-accounts |
No |
HARKROOM_CODEX_ACCOUNTS_DIR |
Root of the Codex accounts; one subdirectory per account, each used as that account's CODEX_HOME. active.json names the account runners use |
~/.harkroom-agent/codex-accounts |
No |
HARKROOM_USAGE_PROBE_DIR |
Empty working directory where the daemon runs claude -p /usage and codex app-server to read each account's limits |
~/.harkroom-agent/usage-probe |
No |
HARKROOM_CLAUDE_ACCOUNTS |
Comma-separated account names setting failover order and subset (e.g. plum,lime). A name missing from the pool fails startup. Unset means alphabetical order over the whole pool |
- | No |
HARKROOM_CLAUDE_POOL |
Forces which account pool this runner uses, overriding both the per-agent assignment and the default pool in pools.json. A name with no matching pool directory fails startup. Unset means: per-agent assignment, then default pool, then the pool root itself |
- | No |
HARKROOM_TURN_SECRETS_DIR |
Where the operator writes secrets mounted for a turn (<appDataDir>/turn-secrets). The operator sets it at spawn; the runner reads mounted values from it to scrub them out of the harness transcript before releasing the turn lease. Unset (older operator) means no scrubbing |
- | No |
OPENCODE_CONFIG |
Set by the runner on opencode turns, not read from your shell: the per-turn opencode config file in the runner state folder (0600). API key connection turns carry the lane provider settings in it, and turns with a turn credential carry the harkroom MCP entry. A value in the runner's own environment is not used for those turns | - | No |
OPENCODE_TEST_HOME |
Set by the runner on API key connection (provider lane) opencode turns, not read from your shell: points opencode's home lookups at an empty per-agent folder (<opencode home>/lane-home) so instructions, skills, config and plugins from your home are not loaded. The shell's HOME is unchanged. A value in the runner's own environment is dropped for those turns |
- | No |
CLAUDE_CONFIG_DIR |
Not read by the runner — set on the child claude process to the selected account's directory. Credentials and session files both follow it, so switching it switches accounts. Omitted entirely when the pool is empty, leaving the child on the system default ~/.claude |
- | No |
XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_STATE_HOME |
Source opencode home whose opencode/auth.json and provider settings are linked into the runner-isolated opencode home under AGENT_STATE_DIR; child opencode processes always get the isolated triple (opencode splits config, credentials and state across all three, so isolating one alone leaves the other two shared) |
~/.config, ~/.local/share, ~/.local/state |
No |
PI_CODING_AGENT_DIR |
Source pi home whose auth.json and models.json are linked into the runner-isolated pi home (pi-home under AGENT_STATE_DIR), along with only defaultProvider/defaultModel from its |
~/.pi/agent |
No |
- An agent never answers. Check that its operator is online under Settings › Operators and
lists the agent's harness as installed. The machine needs
node(22+) and the harness CLI on itsPATH. - A runner stopped with exit code 78. The last line of its log says why:
harness executable not found(install the CLI) orcredential rejected (revoked or rotated)(reissue its credential).
Requirements: Node.js 22+, pnpm 11, Docker (tests start PostgreSQL in a container), and the Rust toolchain for the desktop app.
packages/ server, desktop, agent, operator, shared — the pnpm/TypeScript workspace
apps/ mobile — the Flutter client (pub/Dart), outside the pnpm workspace
apps/ holds one thing today, and that is on purpose rather than by accident. The split is by
role — apps/ is what a person or a machine runs, packages/ is what those things import — and
the rest of the move is deferred until the open pull requests land. The mobile client went
straight to its final home because it is new: there was no history to move, and placing it
correctly now is cheaper than moving it twice.
Two consequences worth knowing:
- pnpm does not see
apps/.pnpm -r testandpnpm -r typecheckskip it, which is intended — that directory belongs to pub. Run its checks fromapps/mobilewithflutter. - Because of that, a repo-wide check must name both roots.
packages/server/test/repoHygiene.test.tskeeps that list in one place (CODE_ROOTS).
pnpm install
pnpm test # all packages
pnpm typecheck
pnpm --filter @harkroom/server dev # server
pnpm --filter @harkroom/desktop tauri dev # desktop app in a native window
pnpm --filter @harkroom/desktop tauri build # distributable appSee CONTRIBUTING.md for how to contribute and SECURITY.md for
reporting vulnerabilities. Design notes, the roadmap and the operations guide are under
docs/ (in Korean): design · roadmap ·
operations.
Apache License 2.0 — see LICENSE.