Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,33 @@ Guidance for Claude Code when working in this repository.

## Project Overview

**Agentage Sync — Obsidian plugin.** A configuration page + bidirectional **git** sync between an Obsidian vault and the user's agentage Memory, plus OAuth sign-in. AI clients reach the same memory over MCP at `memory.agentage.io`.
**Agentage Sync — Obsidian plugin.** A configuration page + live **CouchDB** sync between an Obsidian vault and the user's agentage Memory, plus OAuth sign-in. AI clients reach the same memory over MCP at `memory.agentage.io`.

- **Plugin id:** `agentage-memory` (locked — the install/auto-update key) · **Display name:** `Agentage Sync`
- **Repo:** `agentage/obsidian-sync` · **Default branch:** `master`
- The plugin is a **git client** (vendored `isomorphic-git` over Obsidian `requestUrl`) talking to the agentage git server at `sync.agentage.io`; the store is one bare git repo per vault, server-authoritative. Plaintext markdown end to end (no client E2E) so the cloud can `git grep` it.
- **Couch is the only device channel.** The plugin replicates a content-addressed doc model (leaf docs + a file doc) to a per-memory CouchDB over Obsidian `requestUrl`; a server bridge commits every couch edit to git (the store stays one bare git repo per memory, server-authoritative, `git grep`-able). Plaintext markdown end to end. The git device channel was removed (see git history before the couch-only cut) - a memory the server has **not** advertised on the couch channel is an explicit error (server flip pending), never a silent fallback.

## Architecture (`src/`)

- **Config** — `settings.ts` (pure model mirroring memory-core `vaults.json`), `settings-tab.ts` (the page: Connect · Setup sync · Expose local/remote MCP · MCP address · config file), `vaults-config.ts` (merge-preserving `~/.agentage/vaults.json` writer; desktop, atomic).
- **Auth** (`auth/`) — `pkce.ts` (S256), `oauth.ts` (DCR + token exchange/refresh/revoke; public PKCE client), `discovery.ts` (`/.well-known/oauth-authorization-server`), `token-store.ts` (`app.secretStorage` + the `auth.json` mirror), `auth-json.ts` (desktop `~/.agentage/auth.json`, atomic 0600, CLI shape), `auth-flow.ts` (DI orchestration: startSignIn/handleCallback/getValidToken-with-refresh/disconnect/isSignedIn). AS = Better Auth at `auth.agentage.io`; custom-scheme redirect `obsidian://agentage-memory-cb`.
- **Git** (`git/`) — `git-client.ts` (DI clone/fetch/pull/push/merge; full single-branch, token-in-header, **never force**), `merge-note.ts` (split-YAML field-LWW + diff3 body), `backup-ref.ts`, `http-requesturl.ts` (requestUrl `HttpClient`), `vault-fs.ts` (`vault.adapter` fs-shim — **wired as the git fs on every platform**; no `node:fs` on the sync path), `stream-utils.ts`, `git-test-server.ts` (test-only `git-http-backend`).
- **Sync** — `resolve-host.ts` (`/.well-known/agentage-sync` + 1h cache), `sync-controller.ts` (single-flight lifecycle: ensure repo → commit-before-pull → merge → conflict note → push).
- **Entry** — `main.ts` (wires Obsidian adapters: secretStorage, requestUrl, `VaultFs` for git, node fs only for the desktop `~/.agentage` config, the ribbon/status-bar/command). Obsidian-coupled files are coverage-excluded; the rest is unit/integration-tested.
- **Couch** (`couch/`) — `couch-sync.ts` (live replication: push-on-save + interval pull, echo-safe, resilient retry queues), `couch-doc.ts` (content-addressed leaf/file doc model + Web Crypto rev), `couch-state.ts` (persisted pull cursor + push-rev cache + pending push/delete queues, through `data.json`), `couch-channel.ts` (holds the single live controller per memory; a switch/sign-out tears it down), `couch-token.ts` (mints/caches the per-memory couch JWT from the resolved `couch_token_url`).
- **Sync** — `resolve-host.ts` (`/.well-known/agentage-sync` + 1h cache; parses the couch advert and routes a memory to `couch` or `error`).
- **Entry** — `main.ts` (wires Obsidian adapters: secretStorage, requestUrl, the couch channel + live vault-event handlers, node fs only for the desktop `~/.agentage` config, the ribbon/status-bar/command). Obsidian-coupled files are coverage-excluded; the rest is unit-tested.

## Key invariants
- **Client owns history.** The server is plain git (force-push allowed, no fast-forward-only), so the plugin **never force-pushes**; it commits-before-pull, 3-way merges, and surfaces conflicts (markers + a `conflict:true` note). Never silent-drop.
- **Tokens** live in `app.secretStorage` + `~/.agentage/auth.json` (0600) — **never** `vaults.json`/`data.json`.
- **isomorphic-git gotchas:** token via `onAuth` header only (never the URL, #1942); full single-branch clone (no `depth` — shallow breaks push, #682); no gc (re-clone on bloat — mobile, future).
- **Merge:** split YAML frontmatter before diff3 (markers inside `---` corrupt the note).
- **Git fs is `VaultFs` (vault adapter) on every platform** — the only `node:fs/os/path` use is the desktop `~/.agentage` config mirror (lazy, `isDesktop`-guarded). **Mobile is deferred** (`isDesktopOnly: true`): the git layer is mobile-safe, but sign-in (the `obsidian://` deep-link round-trip) and first-sync `.git` detection are not device-verified — re-enable only after a mobile smoke passes.
- **Server reconciles history.** The server bridge commits couch edits to git and reconciles concurrent edits; the plugin is echo-safe (a push/pull whose content already matches is skipped) so the vault↔couch↔git loop converges. Never silent-drop.
- **Couch is the only device channel.** A memory absent from the resolution's `couch_vaults` resolves to `channel: 'error'` - surfaced as a Notice + red status dot ("not on the new sync channel yet - server update pending"), never a git fallback or a no-op.
- **Tokens** live in `app.secretStorage` + `~/.agentage/auth.json` (0600) - **never** `vaults.json`/`data.json`. The per-memory couch JWT is minted on demand from `couch_token_url` (auth service is the sole minter), never persisted.
- **No node builtins on the sync path** — couch uses `requestUrl` + Web Crypto (mobile-safe). The only `node:fs/os/path` use is the desktop `~/.agentage` config mirror (lazy, `isDesktop`-guarded); `node:http` backs the desktop loopback OAuth listener. **Mobile is deferred** (`isDesktopOnly: true`): the sync layer is mobile-safe, but sign-in (the `obsidian://` deep-link round-trip) is not device-verified — re-enable only after a mobile smoke passes.

## Development

```bash
npm install
npm run dev # esbuild watch -> main.js
npm run build # production main.js
npm test # vitest (spawns git-http-backend; needs the git binary)
npm test # vitest
npm run verify # type-check + lint + format + test + build + check:hosts/docs/bundle
npm version <x.y.z> # bump manifest.json + versions.json (release prep)
```
Expand Down
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
[![Obsidian](https://img.shields.io/badge/Obsidian-1.11.4%2B-7c3aed.svg)](https://obsidian.md)

Stop copying notes into every chatbot. **Agentage Sync** makes your [Obsidian](https://obsidian.md) vault one shared [Agentage Memory](https://agentage.io) that every AI reads and writes over MCP - Claude, ChatGPT, Cursor, or any MCP client - so the same context follows you across apps. It works through two-way **Git** sync to a private hosted memory: notes stay plain Markdown you own, merges are safe with flagged conflicts, and you pick which memory to sync to. Desktop only, with up to 100 MB of storage included.
Stop copying notes into every chatbot. **Agentage Sync** makes your [Obsidian](https://obsidian.md) vault one shared [Agentage Memory](https://agentage.io) that every AI reads and writes over MCP - Claude, ChatGPT, Cursor, or any MCP client - so the same context follows you across apps. It syncs live to a private hosted memory: notes stay plain Markdown you own, edits replicate on save, and you pick which memory to sync to. Desktop only, with up to 100 MB of storage included.

> ⚠️ **Status:** **desktop only** for now - mobile is on the way.

## Features

- 🔄 **Two-way Git sync** - clone / pull / commit / push your vault to `sync.agentage.io`. The server is a bare git repo per memory that you can clone or export anytime.
- 🔄 **Two-way live sync** - edits replicate to your hosted memory as you save, and remote changes flow back in. On the server your notes are stored as a bare git repo per memory that you can clone or export anytime.
- 🧠 **Pick or create a memory** - choose which memory this vault syncs into, or create a new one, from a single dialog (search your memories, see file/folder counts).
- 🤝 **Shared with every AI over MCP** - the same memory is exposed at `memory.agentage.io`, so Claude / ChatGPT / Cursor read and write the same notes. [How to connect →](https://agentage.io/connect)
- 🔐 **Sign in once** - a secure browser sign-in; your access is kept in Obsidian's encrypted storage, never in your notes or config.
- 🧩 **Plain Markdown, safe merges** - notes stay `.md`; edits from different places merge automatically, and anything that can't be is flagged with a note - never a silent drop.
- 🧩 **Plain Markdown** - notes stay `.md`; concurrent edits reconcile server-side so nothing is silently dropped.
- 📊 **Status at a glance** - a status-bar dot (green / red / gray) with a click menu: Sync now, Open dashboard, Settings. The same actions are in the command palette and the ribbon.

## Installation
Expand All @@ -33,7 +33,7 @@ From the [latest release](https://github.com/agentage/obsidian-sync/releases/lat

1. **Settings → Agentage Sync → Sign in to Agentage** - a browser window opens once; no password is stored by the plugin.
2. **Choose a memory** - click the status-bar dot (or run **Agentage Sync: Choose memory** from the command palette) → **Choose memory…** → pick an existing memory or **Create a new** one. (A fresh memory makes the cleanest first sync.)
3. **Sync now** - from the dot menu or the command palette (**Agentage Sync: Sync now**). Your notes are committed and pushed.
3. **Sync now** - from the dot menu or the command palette (**Agentage Sync: Sync now**). Your notes replicate to the memory, and edits keep syncing live as you save.
4. **Connect your AI apps** - point Claude / ChatGPT / Cursor at your memory over MCP. [See how to connect →](https://agentage.io/connect)

## Settings
Expand All @@ -47,20 +47,19 @@ From the [latest release](https://github.com/agentage/obsidian-sync/releases/lat
- **Account required:** you need an agentage account to sign in and sync.
- **Storage:** each account includes up to 100 MB.
- **No calls until you act:** a fresh or signed-out install makes **no network requests at all** - the plugin only contacts the network once you Connect or Sync.
- **Network use:** when active, the plugin contacts `auth.agentage.io` (sign-in, OAuth 2.1 / PKCE) and **`sync.agentage.io`** (syncing your vault over Git). "Open dashboard" opens `dashboard.agentage.io` in your browser. AI access to the same memory happens server-side over MCP at `memory.agentage.io`.
- **Network use:** when active, the plugin contacts `auth.agentage.io` (sign-in, OAuth 2.1 / PKCE), **`sync.agentage.io`** (to discover your sync endpoint), and the per-memory sync host it hands back (for the live replication of your notes). "Open dashboard" opens `dashboard.agentage.io` in your browser. AI access to the same memory happens server-side over MCP at `memory.agentage.io`.
- **Privacy policy:** <https://agentage.io/privacy>. **Terms of Service:** <https://agentage.io/terms>.
- **No client-side telemetry.** Your notes live in your own per-tenant git repo (EU-hosted) and as plain Markdown on your machine; the OAuth token is kept in Obsidian's encrypted secret storage, never in `vaults.json` or `data.json`.
- **No client-side telemetry.** Your notes are stored server-side in your own per-tenant git repo (EU-hosted) and as plain Markdown on your machine; the OAuth token is kept in Obsidian's encrypted secret storage, never in `vaults.json` or `data.json`.

## Third-party

- **[isomorphic-git](https://github.com/isomorphic-git/isomorphic-git)** (MIT) - pure-JS git client.
- **js-yaml** (MIT) + **diff3** - frontmatter parsing and 3-way merge.
- **js-yaml** (MIT) - frontmatter parsing.

## FAQ

- **Why desktop only?** Mobile sign-in isn't fully tested on phones yet, so the plugin is desktop-only for now. Mobile is on the way.
- **Where is my sign-in token stored?** In Obsidian's encrypted storage - never in your notes or plugin settings.
- **What happens on a conflict?** Nothing is silently dropped. If the same note changed in two places, anything that can't be merged automatically is flagged in an **Agentage Sync Conflicts** note listing the files. Fix them, then sync again.
- **What happens on a conflict?** Nothing is silently dropped. Concurrent edits are reconciled server-side and flow back to your vault.
- **Can I leave or export?** Yes - you can export your memory anytime, and your notes stay as plain Markdown files on your computer.

## Support
Expand Down
5 changes: 3 additions & 2 deletions esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ const context = await esbuild.context({
// reaches them. Without the `node:`-prefixed entries esbuild leaves the raw
// specifier and the Obsidian renderer can't resolve it.
external: ['obsidian', 'electron', ...builtins, ...builtins.map((m) => `node:${m}`)],
// Browser platform so the bundle is mobile-safe; isomorphic-git + diff3 + js-yaml
// are pure-JS. A couple of Node globals are shimmed for browser resolution.
// Browser platform so the bundle is mobile-safe; the couch sync channel uses Obsidian
// requestUrl + Web Crypto (no node builtins), js-yaml is pure-JS. A couple of Node
// globals are shimmed for browser resolution.
platform: 'browser',
define: {
global: 'window',
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Agentage Sync",
"version": "0.2.3",
"minAppVersion": "1.11.4",
"description": "Let Claude, ChatGPT, Cursor, and any MCP client read and write your vault. Two-way Git sync to a private memory, safe merges with flagged conflicts, plain Markdown you own. Up to 100 MB included.",
"description": "Let Claude, ChatGPT, Cursor, and any MCP client read and write your vault. Two-way live sync to a private memory, concurrent edits reconciled server-side, plain Markdown you own. Up to 100 MB included.",
"author": "agentage",
"authorUrl": "https://agentage.io",
"helpUrl": "https://agentage.io/connect",
Expand Down
Loading
Loading