Open source Granola AI Alternative
# Overview
For Anarlog work, read and follow [.agents/skills/anarlog-workflow/SKILL.md](.agents/skills/anarlog-workflow/SKILL.md). Start requested work immediately; record useful decisions and non-obvious lessons in Linear, not routine execution logs.
Anarlog is a pnpm and Rust workspace. Read the nearest `AGENTS.md` before changing a component.
- `apps/desktop/`: Tauri 2 with React, TypeScript, Vite, and Tailwind. Zustand owns UI state; TanStack Query/Form own queries, mutations, and forms.
- `apps/web/`: React with TanStack Start/Router, Vite, and Tailwind; deployed through Vercel.
- `apps/mobile/` and `apps/watch/apple/`: Expo/React Native with a Rust UniFFI bridge, plus a native watchOS app. Use the versions in `apps/mobile/package.json` and the mobile instructions.
- `apps/api/`: Rust/Axum API. `apps/stripe/`: Bun/Hono webhook and seat-worker component packaged with the Rust billing API. Deploy both through `api_cd.yaml` with `service=billing`.
- `apps/cli/`: Rust CLI/TUI and MCP entry point (`anarlog-cli` package, `anarlog` binary).
- `crates/db-app/`: canonical SQLite schema and migrations shared by desktop and mobile. `db-core` owns runtime initialization, `db-migrate` owns migration execution, and `plugins/db/` / `crates/mobile-bridge/` expose transports. React consumes these through `packages/db-runtime`, `packages/db-react`, and `packages/db-tauri`; `packages/db` is the Drizzle adapter.
- `supabase/`: hosted Postgres migrations and database tests, separate from local SQLite migrations.
- `enterprise/`: a separate Cargo workspace with a commercial license. Community packages must not depend on it.
Sessions are the core entity: all notes are backed by sessions. ProseMirror powers the editor (`packages/editor`, via `@handlewithcare/react-prosemirror`); documents use TipTap-dialect ProseMirror JSON, with converters/validation in `crates/tiptap`.
<!-- gitbutler-agent-setup:start -->
## Version control on `gitbutler/workspace`
Check the active checkout's branch with `git symbolic-ref --quiet --short HEAD` before applying this section or any GitButler skill. This read-only branch check is allowed before selecting the version-control interface.
Only when the result is exactly `gitbutler/workspace`, use the repository-local [but](.agents/skills/gitbutler/SKILL.md), [create-prs](.agents/skills/create-prs/SKILL.md), [fix-prs](.agents/skills/fix-prs/SKILL.md), [update-prs](.agents/skills/update-prs/SKILL.md), and [sweep-workspace](.agents/skills/sweep-workspace/SKILL.md) skills as relevant. All rules below apply only on that branch. On any other branch or detached HEAD, use the normal Git workflow; do not activate these skills, initialize GitButler, or switch branches to satisfy this condition.
- For overlapping PR or CI repair requests on this branch, use [fix-prs](.agents/skills/fix-prs/SKILL.md) instead of [fix-ready-prs](.agents/skills/fix-ready-prs/SKILL.md); preserve any narrower user scope.
- Use GitButler (`but`) for version-control inspection and write operations, including status, diffs, branching, committing, pushing, and history edits.
- Assume multiple agents may be working in this repository. Do not move, amend, squash, discard, commit, push, or otherwise modify another agent's work unless the user asks.
- For commit just/only/specific changes on a new branch (selected-change requests), use the two-command fast path from the GitButler skill: `but diff`, then `but commit -b <branch> -m "message" <id> <id>`.
- For that fast path, after the commit succeeds, stop and summarize; do not run separate branch, staging, status, or diff commands unless the commit output is missing information you need.
- Use the repository-local GitButler skill for command recipes and syntax before guessing flags, using `--help`, or translating Git habits directly.
- Mutation commands report their result without appending workspace status. Add `--status-after` only when the next step needs resulting workspace IDs or details; otherwise do not rerun status or diff to verify success.
- Organize GitButler branches and PRs around coherent areas of work, such as sync, Charlie, or agent runtime. Reuse the relevant existing branch for related changes and follow-ups across sessions. Keep commits focused within that branch. Do not create a separate branch or stacked PR for every small task; split only when work has an independent purpose or needs to ship separately. Preserve other agentsβ uncommitted work and avoid rewriting shared history.
- Do not push or open pull requests unless the user asks.
- Keep commit messages and pull request descriptions succinct: explain what changed, why it changed, and any important decision.
### Amend local fixes into the right commits
- For small cleanup or follow-up fixes, amend an unpublished local commit when the change clearly belongs with that commit's intent.
- Do not create tiny fixup commits unless the user asks.
- Use GitButler to move the relevant changes into the commit where they belong.
- Ask before rewriting pushed, reviewed, shared, or ambiguous history.
### Split unrelated changes into separate commits
- If one file contains unrelated changes, split them by hunk instead of committing the whole file.
- Keep tests with the behavior they verify.
- Split generated output, docs-only edits, or mechanical cleanup into separate commits when each commit remains coherent on its own.
- If the split is ambiguous, summarize the options before committing.
### Update from the target branch automatically
- When GitButler status shows new changes on the target branch and the workspace holds only this session's branches, update with `but pull` directly β its output reports the result and `but undo` reverts it.
- If an update you started on your own initiative reports conflicted commits, stop and ask before resolving them (`but undo` reverts the pull if the user prefers).
- When other agents' branches are applied, run `but pull --check` first and ask before updating if it reports conflicts or their branches would move.
- If the user asks you to handle update conflicts, use GitButler's conflict tools. Ask before resolving semantic conflicts, dependency updates, generated files, or conflicts involving another person's work.
### Commit checkpoints after each turn
- Commit after a working checkpoint, when the requested change is complete and relevant checks have passed or been reported.
- Treat checkpoint commits as local savepoints, not final review history.
- When the user asks you to tidy the history, use GitButler to squash commits, reword commits, and move changes between commits where appropriate.
- Only tidy unpublished local history unless the user explicitly authorizes changing pushed or shared history.
<!-- gitbutler-agent-setup:end -->
## GitHub Enterprise API access
- For `fastrepl/char` and `fastrepl/anarlog`, prefer `~/.local/bin/gh-enterprise` over plain `gh` for GitHub API, PR, issue, and CI operations when the helper is installed. Examples: `gh-enterprise api repos/fastrepl/anarlog/pulls/123`, `gh-enterprise pr view 123 -R fastrepl/anarlog`, and `gh-enterprise api graphql -f query='...'`.
- The helper renews a GitHub App installation token automatically. Both repositories share its verified REST quota of 15,000 requests/hour and GraphQL quota of 10,000 points/hour; secondary limits still apply. Batch reads, paginate completely, and respect reset/retry headers instead of repeated retries.
- The app supports repository contents, PR/issue updates, and Actions writes. Writes appear as the app bot. Existing authorization, review, merge, and environment-selection requirements still apply; possessing the token is not permission to act.
- Git, GitButler, and connectors do not automatically use this helper. Use existing user authentication when an endpoint requires a user identity or unsupported permissions; do not bypass permission failures or broaden app access automatically.
- Credentials stay outside the repository. Never print tokens or keys, commit them, or put them in command arguments. If the helper is missing on another machine, report the setup gap and use existing authorized access; do not assume the higher quota applies to plain `gh`.
## Commands
- Install: `pnpm install --frozen-lockfile`. Match CI's Node 22, `package.json#packageManager`, and `rust-toolchain.toml`; keep lockfiles in sync with intentional dependency changes.
- Format: `pnpm exec dprint fmt --allow-no-files <changed-files>`; check: `pnpm exec dprint check --allow-no-files <changed-files>`. Whole-repository check: `pnpm fmt:check`.
- Typecheck (TS): `pnpm -F <package-name> typecheck`; use `pnpm -r typecheck` for changes spanning packages. Use actual names from package manifests, such as `@anlg/desktop`.
- Typecheck (Rust): `cargo check --locked -p <package>`; match the relevant workflow's features and target. A root `cargo check` does not cover the separate enterprise workspace or every platform.
- Build shared UI before desktop/web checks: `pnpm -F @anlg/ui build`.
- Desktop dev: `pnpm dev:desktop`.
- Web dev: `pnpm dev:web`.
- Combined desktop/web/API/local Supabase: `pnpm dev`; backend only: `pnpm dev:api`. Requires Docker, Task, and API configuration. Process Compose 1.122.0+ owns process lifecycle and logs; Turbo owns the shared UI build. See `CONTRIBUTING.md` for control ports and headless use.
- Dev docs: https://docs.anarlog.so
## Pre-commit verification
Treat `.github/workflows/` and their composite actions as the source of truth for commands, setup, features, targets, and exclusions. Re-read them when changing this guide or CI. Run locally reproducible checks before committing; include affected consumers of shared packages even when workflow path filters miss them. A skipped workflow/job is not passing coverage.
### Common checks
- Format the changed files, then check the same files. `fmt.yaml` checks added/copied/modified/renamed files against the PR base and falls back to `pnpm fmt:check` when no base is available. Use the complete branch diff for final verification, not only the last commit. Respect `dprint.json` associations/exclusions: `--allow-no-files` permits excluded paths but does not validate them. Avoid formatting unrelated work in a shared workspace.
- `lint.yaml`: `pnpm exec oxlint --quiet --format=github apps/desktop/src/`. The root `pnpm lint` also runs type-aware Oxlint and ESLint; it is broader than this CI job. Run configured ESLint checks for touched desktop/web query code with `pnpm exec eslint <changed-files>`.
- `ci.yaml`: run both license-boundary Python commands and the complete `node --test` command listed there. These run on every PR, including documentation-only changes.
- `zizmor.yaml` runs on every PR. Reproduce `uvx zizmor --format sarif .` (save output outside the repository) with read-only GitHub access; report unavailable authenticated checks. Inspect the SARIF findings: a successful scan/upload does not mean zero findings. Fix findings introduced by workflow/action changes and report existing findings separately.
- For TypeScript changes, run affected packages' typechecks and existing test scripts. For Rust changes, run affected crate checks/tests with the workflow's flags; retain `--locked`, feature selections, test filters, and Clippy's `-D warnings`. Add regression coverage for changed behavior, especially persistence, auth, billing, and recording lifecycles.
- Decide what to test, delete, or leave untested with `.agents/skills/testing/SKILL.md`: one regression test per fix, no tests for CSS, copy, mock call counts, private state, or file layout. Structural and import-boundary rules belong in lint rules, not tests.
### Checks by component
This map is a starting point; execute the relevant workflow's full locally reproducible job, including setup and generated-file checks.
| Changed component | Validation and source |
| --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Desktop frontend and shared UI/editor packages | Build `@anlg/ui`, then `pnpm -F @anlg/desktop typecheck`, `pnpm -F @anlg/desktop test`, lint, and i18n validation. Run changed packages' own scripts too. See `desktop_ci.yaml`. |
| Desktop Rust, plugins, CloudSync | See `desktop_ci.yaml`: macOS checks both `direct-distribution` and `app-store` features and runs desktop/workspace tests; Linux/Windows use their own targets and test selections. Preserve the workspace-test exclusions. CloudSync gates include `cargo test --locked -p cloudsync` and `cargo test --locked -p db-core 'cloudsync::' -- --test-threads=1`. Swift window changes also require `swift build --package-path plugins/windows/swift-lib` on macOS. |
| Web and its shared packages | Build `@anlg/ui`; run typecheck and test for both `@anlg/supabase` and `@anlg/web`, then `pnpm -F @anlg/web media:figures:check`. See `web_ci.yaml`. Routing, SSR, content-generation, dependency, and bundling changes also need `pnpm -F @anlg/web build` with the build environment from `web_cd.yaml`. |
| API and related crates | See `api_ci.yaml`: `cargo check -p api`; tests for `api-cloud`, `api-auth`, `mcp`, `supabase-auth --features server`, `api-sync`, `transcribe-proxy --lib`, the API drain test, and the deployment/E2EE Python tests. Regenerate and verify OpenAPI as described below. |
| CLI, agent access, local SQLite schema/migrations, TipTap | See `cli_ci.yaml`: locked tests for `agent-access`, `anarlog-cli`, `db-app`, `db-core`, `db-migrate`, and `tiptap`; Clippy with `--all-targets --no-deps -- -D warnings`; release CLI build and isolated smoke tests. Preserve the Linux/Windows smoke checks and skill/docs validation. |
| Mobile, watch, and native bridge | `pnpm -F @anlg/mobile typecheck` and `pnpm -F @anlg/mobile test`. See `mobile_ci.yaml` for `APP_VARIANT=stable`, bridge builds (`cargo xtask mobile-bridge ios` / `android`), iOS Release, Android `assembleRelease`, and watchOS simulator builds. |
| Hosted database | In an isolated local Supabase stack with Docker, run `supabase db start`, then `supabase test db` as in `db_ci.yaml`. Include migration and RLS regression tests; never point these checks at a hosted database. |
| Stripe billing service | Run `pnpm -F @anlg/stripe typecheck` and `pnpm -F @anlg/stripe test` (requires Bun). `stripe_ci.yaml` runs the billing component checks; `api_cd.yaml` with `service=billing` deploys the combined service. Dependency/container changes also require a local image build using the repository root as context: `docker build --target billing-runtime -f apps/api/Dockerfile .`. |
| Enterprise capture | Use `--manifest-path enterprise/Cargo.toml`; the root workspace does not include these checks. Run the reliability/fixture-replay, worker library, control-plane upgrade, and Clippy commands in `enterprise_ci.yaml`. |
### Generated files and data compatibility
- Desktop i18n has its own `desktop_ci.yaml` job. When translated copy, extraction, or catalogs change, run `pnpm -F @anlg/desktop exec lingui extract --clean --workers 1` and `pnpm -F @anlg/desktop exec lingui compile --strict --workers 1`; include the resulting `apps/desktop/src/i18n/locales` changes and rerun until stable. `pnpm -F @anlg/desktop i18n:check` includes a diff against HEAD, so an intentional uncommitted catalog change will fail that final diff. Run it when catalogs are committed/clean; do not discard intended translations to make it pass.
- API contract changes: run `cargo test -p api gen_openapi_json`, then `pnpm -F @anlg/api-client openapi` when the contract changes. Commit `apps/api/openapi.gen.json` and corresponding generated client changes. CI checks the OpenAPI file against HEAD; verify generation is stable before committing and the diff is clean afterward.
- Regenerate native bindings and permissions using their owning Rust build/generator; do not hand-edit generated output. The macOS desktop job checks `plugins/db/js/bindings.gen.ts`, `plugins/db/permissions/autogenerated`, and `plugins/db/permissions/schemas/schema.json` for drift. Mobile bridge generation belongs to `cargo xtask mobile-bridge`.
- Keep shipped SQLite migration IDs/SQL append-only, maintain desktop/mobile schema parity, and update the Drizzle adapter when schema changes affect it. Follow `crates/db-app/AGENTS.md` for explicit CloudSync migration scope and table policy. Test upgrades from supported older schemas as well as fresh initialization.
Fix failures caused by the change before committing. If an existing unrelated failure or unavailable platform, secrets, hardware, or infrastructure prevents a check, record the exact command, result, and reason in the handoff. Keep focused passes separate from full-suite failures; do not report full validation or release readiness with unresolved gates.
## Release verification
- For an explicitly requested stable desktop release, follow `.agents/skills/release-new-version/SKILL.md` and inspect the current workflows. The explicit version must have an accurate, validated changelog merged into `main`; record that exact candidate SHA.
- Before freezing each release candidate, complete the release skill's surface review: check product changes against CLI, local and hosted MCP, API/generated clients, agent skills/plugins, and documentation. Merge required updates and verify their publication through each surface's own channel. Record reasons for unchanged surfaces and explicit deferrals; a desktop build or changelog alone does not prove these surfaces are current.
- Desktop native CI (macOS, Windows, Linux x86_64/ARM64, and Swift) runs only on the daily schedule and `workflow_dispatch`, including release-candidate verification. PRs and pushes to `main` run desktop JS and i18n checks. Mobile iOS, Android, and watchOS native jobs also skip PRs. Check each required job and SHA, not only the aggregate green result.
- Follow the release skill's candidate `desktop_ci.yaml` dispatch: CloudSync source rebuilds run on `workflow_dispatch` or a reusable caller with `rebuild_cloudsync=true`. Verify the platform artifacts and tests from that candidate before approving the desktop lanes. Mobile has its own native-build and release requirements.
- `desktop_cd.yaml` builds a stable draft and records artifact provenance; `desktop_publish.yaml` publishes those verified artifacts using the explicit version, candidate SHA, and dry-run ID. Require a successful first-attempt dry run, matching hashes, and a candidate merged into `main`. For new releases, dispatch from frozen `main` and verify each workflow SHA equals the candidate; if main advances, verify a fresh candidate before continuing. Historical immutable Nightly tags remain supported for existing candidates. Dispatch a fresh build after failure; do not mix evidence across rerun attempts or commits.
- Verify publish completion, immutable `desktop_v<version>` tag, GitHub/CrabNebula assets, signatures/hashes, and downstream store/package results. Report pending store submission or package-publication work separately.
- Web, API, and hosted database have separate `*_cd.yaml` workflows; the billing API and Stripe component share the API billing deployment. Check the relevant packaging/build path before deployment; filtered Docker builds must include every workspace dependency's manifest and source. Verify the deployed version/health and affected behavior after the workflow succeeds.
- Release and optional QA are separate requested workflows. Run the QA skills or hardware/provider E2E workflows when explicitly requested; do not add them as implicit publish gates. Report build, QA, publication, and live deployment results separately.
## Guidelines
- JavaScript/TypeScript formatting runs through `oxfmt` via dprint's exec plugin.
- Use `useForm` (tanstack-form) and `useQuery`/`useMutation` (tanstack-query) for form/mutation state. Avoid manual state management (e.g. `setError`).
- Keep schema creation, migrations, and DB initialization on the Rust side. TypeScript consumes the shared transport contracts; the Drizzle adapter uses `executeProxy` and must not parse SQL or remap named rows into positional rows.
- New SQLite migrations must be downgrade-safe (older builds tolerate newer schemas): additive only, new columns nullable or with a DEFAULT. If a migration can't be downgrade-safe, add a `-- breaking` line to the leading comment block of its `.sql` file so older builds refuse the database with an update prompt. Nightly and stable desktop builds share one database, so a breaking migration published in Nightly locks stable out until stable ships it.
- Branch naming: `fix/`, `chore/`, `refactor/` prefixes.
- PR titles and commit messages: the title summarizes intent, not the diff or a ticket number. The description is a short summary of what changed, not a generated commit log or file-by-file recap. See `.github/PULL_REQUEST_TEMPLATE.md` for the human-facing Intent/Demo structure.
## Code Style
- Avoid creating types/interfaces unless shared. Inline function props.
- Do not write comments unless code is non-obvious. Comments should explain "why", not "what".
- Use `cn` from `@anlg/utils` for conditional classNames. Always pass an array, split by logical grouping.
- Use `motion/react` instead of `framer-motion`.
- Prefer DOM order for local overlap and portals for cross-tree floating UI. Use `z-index` only inside a bounded stacking context with explicit sibling ordering; do not add arbitrary global or escalating values.
## CLI TUI Command Architecture
Choose the lightest command structure that fits the workflow.
Use the full reducer/effect/runtime split only when the command has async orchestration, a multi-step workflow, or substantial state transitions that benefit from reducer-style tests.
```
commands/<name>/
mod.rs -- Screen impl, Args, run() [glue]
app.rs -- App or screen-local state [optional]
action.rs -- Action enum [optional]
effect.rs -- Effect enum [optional]
runtime.rs -- Runtime, RuntimeEvent [async I/O]
ui.rs -- draw(frame, app) [rendering]
```
Naming rules:
- Types drop the command prefix: `App`, `Action`, `Effect`, `Runtime`, `RuntimeEvent`
- `app.rs` β `app/mod.rs` with private submodules when state is complex
- `ui.rs` β `ui/mod.rs` with sub-files when rendering is complex
- `action.rs`/`effect.rs` are siblings of `mod.rs` when they exist; do not create them by default for simple list/detail screens
- `app.rs` contains no rendering logic, no API calls, no async code when using the reducer pattern
- Prefer `screen.rs` plus a small local state struct for simple browse/select flows
- Do not add parent-level action/effect translation layers that proxy child workflows through another command's reducer
## Misc
- Do not create summary docs or example code files unless requested.
## Cursor Cloud specific instructions
The cloud image is Ubuntu 24.04 x86_64 with Node, pnpm, Rust, and the Linux libraries needed for Tauri (see `scripts/setup-linux-tauri.sh` and `scripts/setup-linux-others.sh`), plus `xvfb`/`dbus-x11`. Verify installed tool versions against the repository/CI pins above; an existing image may lag a toolchain update. The startup update script installs workspace dependencies and builds `@anlg/ui`; it does not re-install system packages.
- Use `pnpm dev:desktop` / `pnpm dev:web`: Process Compose builds `@anlg/ui` through Turbo before starting the app. The older `pnpm exec turbo dev:*` entrypoints also work.
- No `start`/`terminals` are configured in the environment (only the `install`/update script). Dev servers are started on demand, not auto-launched on boot.
- A real XFCE desktop runs on the VNC display `:1` (this is what computer-use sees). Run the desktop app there so it is visible: `DISPLAY=:1 LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe WEBKIT_DISABLE_COMPOSITING_MODE=1 WEBKIT_DISABLE_DMABUF_RENDERER=1 pnpm dev:desktop`. The first native build is slow; the desktop Vite frontend serves on `:1422`.
- `dotenvx` loads `.env.supabase`/`.env` with `--ignore MISSING_ENV_FILE`. Desktop local notes and public web pages can run without cloud credentials. Auth, CloudSync, hosted STT/LLM, and billing require the relevant services/configuration; local development uses `task supabase-start` (Docker) and `cargo run -p api`. Provider features also need provider credentials.
- Web dev serves on `:3000`; auth/DB-backed routes require the Supabase stack.
- Swift formatting requires an available `swift format` executable. If missing, check changed non-Swift files and report the exact unchecked Swift paths; verify them on a host with the formatter. Do not treat a formatter startup error as proof that Swift files are formatted, or waive other formatting failures.
- No audio input device exists in the headless pod; use `crates/audio-mock` for automated recording flows. Mobile TypeScript checks run on Linux; Android native builds require Java/Android SDK/NDK setup from `mobile_ci.yaml`. iOS and watchOS builds require macOS/Xcode.
- The default `cc`/`c++` are clang. The native C++ dependencies need a matching `libstdc++` development package (the image provides `libstdc++-14-dev`). The default dev database is `~/.local/share/com.hyprnote.dev/app.db`, with notes in `sessions`/`session_documents`; confirm the running app's channel and configured storage location before inspecting data.
- Changelog, release, and QA skills are exposed to Cursor Cloud via `.cursor/skills/` shims (`new-changelog`, `release-new-version`, `qa-critical-ux`, `qa-cli-mcp-api`). Canonical copies live in `.agents/skills/`; edit those only. `qa-critical-ux` is macOS-native and is not a cloud-environment gate.