Lifosy

Lifosy is a personal life OS: notes, todos, habits, dashboards, bookmarks and a knowledge base, all stored as plain files in a GitHub repository you own. The product faces users under the name kaihuman (web app at https://app.kaihuman.com), while the code, packages and binaries use the name Lifosy (@lifosy/*, lifosy-palette). The monorepo lifosy/monorepo holds a web console, a terminal CLI, a browser extension, a desktop command palette, an MCP server and the landing pages.

Lifosy vs kaihuman: which name is which

The project uses three names. They all refer to the same product.

NameWhere it appears
kaihumanThe brand: landing pages, the app at https://app.kaihuman.com, the console title, the popup header
LifosyCode and packages: @lifosy/app-console, @lifosy/core, lifosy-palette, the GitHub org lifosy
LifeOSOlder code: the .lifeos/ app folder, the IndexedDB database life-os-db, localStorage keys such as lifeos_gh_token

“Kai” (改) stands for kaizen, continuous improvement. The tagline is “The ever-improving human”. The data folder in a repository is .kh/ (for kaihuman). Code that reads it falls back to the legacy .lifeos/ folder when .kh/ does not exist.

What the idea behind Lifosy is

Lifosy treats your life data as files in your own GitHub repositories. There is no Lifosy database server holding your notes.

  • Every widget is backed by a file with a known suffix, for example .todos.md, .habit.csv, .events.csv, .actionlog.md, .bookmarks.json.
  • The files are human-readable Markdown, CSV or JSON. You can edit them in any editor, grep them, or open them on github.com.
  • Every app reads and writes the same files through the same parsers in packages/formats, so an entry added in the browser shows up in the console and the desktop palette.
  • Git history is the backup and the audit log. A deleted entry can be recovered from history.
  • AI agents (for example Claude Code) can work on the same repository. The .kh/ knowledge base and its /kb-ingest style commands are built for that.

The landing page states the principle as “Local-First. We store nothing.”

Which apps are in the monorepo

Each app has its own chunk of documentation. The “Doc” column names that chunk’s title.

AppStackWhat it doesDoc
apps/consolePreact, Vite, Tailwind, PWAThe main web app at app.kaihuman.com: file browser, Markdown editor, widgets, kanban board, analytics, wiki, knowledge graph, commit history, local AI chat, push remindersWeb console, Console widgets, Console views
apps/cliRust, RatatuiTerminal UI with notes, file browser, AI prompt, dashboard view; cli capture and cli server subcommandsTerminal CLI
apps/cli/scriptsshell, wofiSuper+K Hyprland overlay that pipes a thought into cli captureDesktop quick capture
apps/kh-capture-nativeZig, native-sdk (experimental)Native quick-capture window that writes to .kh/raw/Desktop quick capture
apps/browser-extensionPreact, CRXJS, VitePopup, bookmarks mirror, pinned-tab groups, read later, start page, Ctrl+Shift+K paletteBrowser extension
apps/desktop-paletteTauri (Rust) + ViteThe same command palette as a Wayland overlay, plus the braindump windowDesktop palette
apps/kb-mcpNode, MCP SDKstdio MCP server over the .kh/ knowledge base in a local cloneKnowledge base MCP server
apps/landing-page-nextgenAstro, TailwindThe production landing pageLanding pages
apps/landing-pageAstro, TailwindThe older landing page, no longer deployedLanding pages

Which shared packages exist

The packages/ folder holds code that more than one app uses. The “Doc” column names the chunk that covers it.

PackageNameWhat it holdsDoc
packages/core@lifosy/coreGitHub service, auth and file stores (Preact Signals), IndexedDB cache, demo modeShared packages
packages/ui@lifosy/uiShared Preact UI components, the editor, StorybookShared packages
packages/formats@lifosy/formatsParsers and writers for every widget file format (todos, action logs, events, prompts, commands, habits, time tracking, timezones, read later)Repo file formats
packages/palette@lifosy/paletteThe command palette UI and protocol shared by the extension and the desktop paletteCommand palette
packages/config@lifosy/configShared tsconfig.base.json, biome.json and tailwind-preset.jsShared packages
packages/docspack@lifosy/docspackThis documentation, packaged for AI agentsDeveloping in the monorepo

The knowledge-base concepts (.kh/raw/, .kh/wiki/, /kb-ingest) are covered in the “Knowledge base” doc.

How data flows between GitHub and the apps

The GitHub repository is the single source of truth. Each app talks to it in its own way.

  • Web console: reads the repository tree and files through the GitHub REST API (githubService in packages/core). Files are cached in IndexedDB (life-os-db, stores files and trees). Saves commit through the API against the file’s last known sha.
  • Browser extension: calls the GitHub API from its service worker. It caches bookmarks and pinned-tab groups in chrome.storage.local and refreshes them at most once an hour, using ETag/If-None-Match so unchanged files cost a 304.
  • Desktop palette: a Rust backend calls the GitHub API and caches files under $XDG_CACHE_HOME/lifosy/.
  • Terminal CLI: keeps a shallow git clone of the active repository in its config directory (repos/<owner>/<repo>) and syncs it before file operations.
  • kb-mcp: reads a local clone on disk; it never calls GitHub.

The console detects conflicts: when a file changed on GitHub while you had unsaved local edits, it offers “keep local” or “use remote” (resolveConflict in packages/core/src/stores/github-file.store.ts).

What happens when you are offline

Offline behaviour differs per app.

  • Console: it is a PWA (vite-plugin-pwa), so the shell loads from the service worker cache. Files already opened are served from the IndexedDB cache. Saving needs the GitHub API.
  • cli capture: offline-safe. Every capture is written to a local spool directory before the push. A failed push leaves the note on disk, and the next capture flushes the backlog, oldest first.
  • kh-capture-native: on a failed push it writes the note into the CLI’s spool directory, so the next cli capture sends it.
  • Command palettes (>q): push straight to GitHub with no spool. A failed push shows the error and keeps the text in the input.
  • Desktop palette braindumps: saved locally as you type, synced later; a failed sync is retried a minute later.
  • Browser extension: serves bookmarks and tab groups from its cache; writes (saving a bookmark, loading a group change) need the network.

Where my data lives and what Lifosy stores

Your content lives only in your GitHub repository. Each app keeps local copies and a GitHub token on your machine.

AppToken stored inLocal cache
Web consoleIndexedDB life-os-db (auth store), mirrored in localStorage key lifeos_gh_tokenIndexedDB files and trees stores
Terminal CLIauth.json in the CLI config directoryshallow clone under repos/, spool/
Browser extensionchrome.storage.localchrome.storage.local
Desktop palettethe Secret Service (gnome-keyring, KeePassXC), never a file$XDG_CACHE_HOME/lifosy/, $XDG_DATA_HOME/lifosy/

Demo mode (/demo, or View demo on the login screen) uses seeded in-memory data and writes nothing to localStorage or IndexedDB. The extension and palette only send data to GitHub, plus DeepL for >t translations when you set a key. AI assistant links (>a) only build a URL.

Signing in with GitHub

All apps authenticate with a GitHub token. The web console is where the token is first obtained.

  1. In the console, Login sends the browser to the OAuth gateway named by VITE_GH_LOGIN (set in apps/console/.env.prod).
  2. The gateway redirects back with ?access_token=…. authStore.handleCallback() in packages/core/src/stores/auth.store.ts stores it and removes it from the URL.
  3. You pick the active repository in the console.

The other apps take the token from the console:

  • CLI: press l. The CLI starts a callback server on 127.0.0.1 and opens the console. The console POSTs the token, the repository and a random state back.
  • Desktop palette: lifosy-palette login (or Log in with the Lifosy console) opens app.kaihuman.com/auth/cli and receives the token the same way.
  • Browser extension: while you are logged in, the console exposes the token on document.body as data-lifosy-auth (ExtensionAuthBridge). The extension’s content script reads it and sends an AUTH_TOKEN_UPDATE message to the service worker. Before that, the start page shows only Open Console.

Logging out of the console sets lifeos_logged_out and deletes the stored token.