diff --git a/CLAUDE.md b/CLAUDE.md index 561af2b..3e242e3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,16 +14,16 @@ Guidance for Claude Code when working in this repository. - **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` (mobile `vault.adapter` fs-shim — built, not yet wired), `stream-utils.ts`, `git-test-server.ts` (test-only `git-http-backend`). +- **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, node fs on desktop, the ribbon/status-bar/command). Obsidian-coupled files are coverage-excluded; the rest is unit/integration-tested. +- **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. ## 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). -- **Desktop** uses node fs (the tested path); mobile (`vault.adapter` via `vault-fs.ts`) is a later milestone. +- **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. ## Development @@ -39,4 +39,4 @@ npm version # bump manifest.json + versions.json (release prep) App-level e2e (Playwright-Electron + the live `sync`/`auth` wire) lives in **`agentage/e2e`** (`tests/obsidian`, `tests/sync`), not this repo. ## Conventions -Node 22+, TypeScript strict (ES2024, ESM), esbuild → CommonJS `main.js`. Named exports only (the sole default export is the `Plugin` subclass in `main.ts`). Vitest. ESLint + Prettier (incl. `eslint-plugin-obsidianmd` store rules). `minAppVersion 1.11.4` (secretStorage), `isDesktopOnly: false`. `normalizePath()` on vault paths; **no client-side telemetry**; the README must disclose network hosts + account/payment requirements (enforced by `check:docs`/`check:hosts`). Conventional commits. Inherits the global Agentage standards (`~/projects/CLAUDE.md`). +Node 22+, TypeScript strict (ES2024, ESM), esbuild → CommonJS `main.js`. Named exports only (the sole default export is the `Plugin` subclass in `main.ts`). Vitest. ESLint + Prettier (incl. `eslint-plugin-obsidianmd` store rules). `minAppVersion 1.11.4` (secretStorage), `isDesktopOnly: true` (desktop-only; mobile deferred). `normalizePath()` on vault paths; **no client-side telemetry**; the README must disclose network hosts + account/payment requirements (enforced by `check:docs`/`check:hosts`). Conventional commits. Inherits the global Agentage standards (`~/projects/CLAUDE.md`). diff --git a/README.md b/README.md index 820ef9a..434b42c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Two-way **Git** sync between your Obsidian vault and your private [Agentage Memory](https://agentage.io) — the shared memory layer for every AI. Your notes stay plain Markdown that you own, and Claude, ChatGPT, Cursor, and any MCP client read and write the *same* files. -> ⚠️ **Status:** desktop git sync + Agentage sign-in (OAuth 2.1 / PKCE) work today. Mobile is wired but experimental (not yet device-verified). Background auto-sync is next. +> ⚠️ **Status:** **desktop only** today (`isDesktopOnly`). Desktop git sync + Agentage sign-in (OAuth 2.1 / PKCE) work. Mobile is planned (see [Mobile](#mobile-planned)); background auto-sync is next. ## Features @@ -13,7 +13,7 @@ Two-way **Git** sync between your Obsidian vault and your private [Agentage Memo - 🤝 **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** — OAuth 2.1 / PKCE; the token is kept in Obsidian's encrypted secret storage, never in your notes or config. - 🧩 **Plain Markdown, safe merges** — notes stay `.md`; concurrent edits reconcile with a 3-way merge (per-field frontmatter + diff3 body), and conflicts surface as markers + a note — never a silent drop. -- 📊 **Status at a glance** — on desktop, a status-bar dot (green / red / gray) with a click menu: Sync now, Open dashboard, settings. On mobile (no status bar), the same actions open from the ribbon icon and the command palette. +- 📊 **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 @@ -32,8 +32,8 @@ From the [latest release](https://github.com/agentage/obsidian-sync/releases/lat ## Getting started 1. **Settings → Agentage Sync → Start sync with agentage** — sign in. A browser window opens once; no password is stored by the plugin. -2. **Choose a memory** — open the actions (desktop: click the status-bar dot; mobile: tap the ribbon icon, or run **Agentage Sync: Open menu** 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 same actions menu or the command palette (**Agentage Sync: Sync now**). Your notes are committed and pushed. +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. 4. **Connect your AI apps** — point Claude / ChatGPT / Cursor at your memory over MCP. [See how to connect →](https://agentage.io/connect) ## Settings @@ -48,11 +48,9 @@ From the [latest release](https://github.com/agentage/obsidian-sync/releases/lat - **Sync** runs a real git client (vendored [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git)) over Obsidian's network layer, authenticated with your sign-in token (sent only as an `Authorization` header, never in the URL). It never force-pushes — it commits before pulling and 3-way merges. - The same repo is what AI apps read and write over MCP — one memory, every AI. -## Mobile support (⚠️ experimental) +## Mobile (planned) -The plugin is `isDesktopOnly: false` and runs its git engine over Obsidian's vault adapter on mobile too, but mobile sync is **not yet device-verified**. Try it on a throwaway vault first. Desktop is the supported path today. - -Mobile has no status bar, so the actions (Sign in, Choose memory, Sync now, Open dashboard) open from the **ribbon icon** (in the left drawer) or the **command palette** (search *Agentage Sync*). Everything else works the same as desktop. +This release is **desktop only** (`isDesktopOnly: true`), so Obsidian won't offer it on phones yet. The git engine already runs over Obsidian's vault adapter (no Node APIs on the sync path), so the groundwork is in place — what's left is verifying sign-in and first sync on real iOS/Android. Mobile will be re-enabled once that's solid. ## Privacy & network use diff --git a/manifest.json b/manifest.json index 54b9ac0..d3e4673 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { "id": "agentage-memory", "name": "Agentage Sync", - "version": "0.1.5", + "version": "0.1.6", "minAppVersion": "1.11.4", "description": "Sync your vault to a private memory that any AI reads and writes over MCP: Claude, ChatGPT, Cursor, and any MCP client share the same Markdown you own.", "author": "agentage", "authorUrl": "https://agentage.io", - "isDesktopOnly": false + "isDesktopOnly": true } diff --git a/package-lock.json b/package-lock.json index 5794ae6..5f9192c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-sync", - "version": "0.1.5", + "version": "0.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-sync", - "version": "0.1.5", + "version": "0.1.6", "license": "SEE LICENSE IN LICENSE", "dependencies": { "diff3": "^0.0.4", diff --git a/package.json b/package.json index 883c035..1b40dc3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-sync", - "version": "0.1.5", + "version": "0.1.6", "description": "Agentage Memory — Obsidian plugin. One memory, every AI, owned by you.", "private": true, "type": "module", diff --git a/src/main.ts b/src/main.ts index 1149327..3b8497d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -101,9 +101,9 @@ export default class AgentageMemoryPlugin extends Plugin implements SettingsHost void this.auth.handleCallback(params).then(() => this.onAuthChanged()); }); - // Ribbon shows on mobile (the status bar does NOT). It opens a modal action-picker - // (not a Menu — those don't render from a tap on mobile), so Sync now / Choose memory - // / dashboard are reachable on phones. + // Ribbon + command open a modal action-picker (Sync now / Choose memory / dashboard). + // Kept alongside the status-bar dot so the same actions survive when there's no status + // bar (the mobile case, once mobile is re-enabled — desktop-only for now). this.addRibbonIcon('refresh-cw', 'Agentage Sync', () => this.openActions()); const sb = this.addStatusBarItem(); this.statusBar = sb; diff --git a/versions.json b/versions.json index b3963de..53b23d0 100644 --- a/versions.json +++ b/versions.json @@ -4,5 +4,6 @@ "0.1.2": "1.11.4", "0.1.3": "1.11.4", "0.1.4": "1.11.4", - "0.1.5": "1.11.4" + "0.1.5": "1.11.4", + "0.1.6": "1.11.4" }