Your graph — from the command line,
and from your AI.
One open package gives you a TypeScript SDK, the ilml CLI, and an MCP server — so you can script your graph, or let Claude, Cursor and Windsurf work inside it directly. A second one, for React, drops a working graph chat onto your own site. And a plugin turns a skill into a package: publish it once, and it runs in someone else’s terminal under their own account, writing results straight into their graph — no app, no login screen, no database to build.
Your graph, live in the terminal.
Real ilml output — read who you are, walk your graph, and replay every change: who, what, when.
════════════════════════════════════════ User: Ilya Sorokin Root node: 000001731b8b…de9ac087 Source: global config ════════════════════════════════════════
📁 iLiveMyLife — Collaborative Knowledge Graph [000001736056c2cb…]
├── 📄 Team
├── 📄 Growth
├── 📄 Features
├── 📄 Whitepaper written by Lifebot [REF]
├── 📄 Product Roadmap
└── 📄 Learning Resources
… and 16 moreHistory (2 entries): ──────────────────────────────────────── [5/19/2026, 2:08 PM] Ilya Sorokin — subCreated Title: Process: Digital Clone Build-out tags: "" → "assist" ──────────────────────────────────────── [5/19/2026, 2:22 PM] Ilya Sorokin — subCreated Title: Principle: Unit-Economics Break-Even
One install — the CLI and the MCP server.
Node 18+. @ilivemylife/cli gives you the ilml command and the MCP server; for your own code, add the SDK to the project with npm install @ilivemylife/graph-sdk.
npm install -g @ilivemylife/cli
ilml login
ilml doctor # verify everything worksDrive your graph with ilml.
The full command set — and every command speaks text or --json, reads from stdin or files, and pipes into shell scripts.
One CLI, many accounts. ilml login signs you in globally — every folder. Run ilml login --local inside a project to use a different account just there (personal vs work, or a dedicated bot account for an automation). Resolution order: local config → .env token → global → shell variable.
# a node + its children, piped into jq
ilml items <node> --json | jq '.[].title'
# walk 4 levels down — nested JSON, children inside children
ilml tree <node> 4 --json > branch.json
# pipe a file straight into the description
cat notes.md | ilml editItem <node> --desc -Plug your graph into your AI.
It runs locally over stdio — no server to host, no URL to expose. Claude Code takes one command; Claude Desktop, Cursor and Windsurf take the same package as a config entry.
claude mcp add --scope user ilml -- npx -y --package=@ilivemylife/cli ilml-mcp{ "mcpServers": {
"ilml": { "command": "npx",
"args": ["-y", "--package=@ilivemylife/cli", "ilml-mcp"] }
} }That goes in your client’s MCP config — claude_desktop_config.json for Claude Desktop, ~/.cursor/mcp.json for Cursor, ~/.codeium/windsurf/mcp_config.json for Windsurf. In an assistant that can already edit your files, skip the hunt: “add the MCP server ilml-mcp from @ilivemylife/cli” — and it writes the config for you. The server runs locally over stdio: nothing to host, no URL to paste.
Then just ask, in plain language:
- Read my latest notifications and summarize what needs my attention.
- What changed in my “Projects” node this week — and who changed it?
- Create a task “Call the bank” under My Life, due tomorrow.
- Search my whole graph for everything about the landing redesign.
And the other way round: with ilml MCP connected as above, start the agent with ilml run claude (or any other terminal agent), and any node can send it work: write /agent and the task, even from your phone. Agent sessions →
Or script it yourself.
One typed package (ESM + CommonJS), and your code becomes a participant in the graph rather than a client of a database: it reads and writes nodes under your own access rights, speaks in a node’s chat, asks Lifebot with that node as context, replays who changed what and when, and streams every change as it happens. Storage, real-time, permissions and an AI — none of which you had to build.
const me = await graph.me()
const items = await graph.items(me.rootItemId)
console.log(items[0].title, '→', items.slice(1).map(i => i.title))const project = await graph.addItem(me.rootItemId, { title: 'My Project' })
await graph.addItem(project.id, { title: 'First task' })const reply = await graph.askLifebot(project.id, 'What tasks do I have?')
console.log(reply.content)const history = await graph.itemHistory(project.id)
// every edit on the node: who made it, what changed, and whenawait graph.uploadFile(project.id, './spec.pdf')
// one node's chat, exact match — how a bot
// finds the marker line it posted earlier
const hits = await graph.searchMessages(
project.id, 'status: deployed')import { ITEM_CHANGE_TYPES } from '@ilivemylife/graph-sdk'
const stop = await graph.subscribeToItems(
project.id,
(change, item) => {
// added · edited · removed · movedIn · reorder
if (change === ITEM_CHANGE_TYPES.added) {
console.log('new node:', item.title)
}
},
// without it, a dead stream looks like silence
(err) => console.error(err.name)
)import {
NodeAuthRequiredError,
ForbiddenError,
NotFoundError,
} from '@ilivemylife/graph-sdk'
try {
await graph.editMessage({ id, content })
} catch (e) {
// editing a message checks the node it lives in:
// e.itemId + e.options ('private' and/or 'kyc')
// + e.requestStatus say what the user must do
if (e instanceof NodeAuthRequiredError) askAccess(e)
if (e instanceof ForbiddenError) toast('Not yours')
if (e instanceof NotFoundError) refresh()
}const result = await graph.requestAccess(nodeId)
// REQUEST_SENT · REQUEST_PENDING
// ALREADY_ACCESSED · NO_REQUEST_NEEDED
// Anyone already inside gets a Lifebot
// message and approves with a @@mention.
// (A KYC gate throws NodeKycRequiredError —
// only a human with a card can pass it.)Errors that tell you what to do next. Every read and mutation throws a specific class — NodeAuthRequiredError, ForbiddenError, NotFoundError, InvalidOperationError, InvalidRefError, GraphAuthError. The auth error carries.itemId, .options (private and/or kyc — the processes the node demands) and a .requestStatus, so you can dispatch straight to a "request access" or "complete KYC" flow. A wallet node asks for both. Subscriptions take the same callback — so a stream the server refuses tells you why, instead of going quiet.
Chats and the login, on your own site.
The other half of the pair runs in the browser: @ilivemylife/react-sdk — components and hooks. Two chat surfaces, and the account they share. That account is the visitor's iLiveMyLife wallet: the same one they already use on every other partner site, so there is no sign-up form on your side and no password for you to store. They sign in once, and the session, the profile and the conversations follow them from site to site.
import { IlmlAskWidget } from '@ilivemylife/react-sdk'
// You own the transport: `ask` calls your own
// service, which holds the account token and asks
// Lifebot in a node you chose. Nothing identifies
// the visitor, nothing is stored.
<IlmlAskWidget
title="Ask us anything"
greeting="No account needed."
ask={q => fetch('/api/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question: q }),
}).then(r => r.json()).then(d => d.answer)}
/>import { IlmlChatWidget } from '@ilivemylife/react-sdk'
// Runs under the VISITOR's own token, so it needs
// nothing from your servers — a static site is
// enough. The thread is a node: history, files,
// references, your whole team able to join.
<IlmlChatWidget
ssoUrl="https://app.ilivemylife.io"
nodeId={conversationNodeId}
title="Support"
/>import { useIlmlSession, IlmlButton } from '@ilivemylife/react-sdk'
// One wallet account across every partner site.
// Mount it where login lives (your header): no
// sign-up form on your side, no password to store.
const s = useIlmlSession({
ssoUrl: 'https://app.ilivemylife.io',
ssoRoute: 'partnerLogin',
graphqlUrl: 'https://api.ilivemylife.io/graphql/v1',
clientId: 'your-site',
})
if (!s.ready) return null // no sign-in flash
return s.isLoggedIn
? <button onClick={s.logout}>{s.profile.name}</button>
: <IlmlButton onClick={s.login}>Sign in</IlmlButton>import { useIlmlSessionState } from '@ilivemylife/react-sdk'
// No config, no second login, no prop drilling.
// One `me` fetch is shared by every consumer, and
// a login or logout on any surface reaches all of
// them — across tabs too.
const { isLoggedIn, profile } = useIlmlSessionState()Which one needs a backend — the fact that decides your build. The signed-in messenger needs nothing from your servers: it works under the visitor's own token, so a static site is enough. The anonymous one does need a small service, because a service token cannot live in a browser where anyone can read it — that service is the only thing standing between your account and the public. We host it, or you run it inside your own perimeter. The chat in the corner of this page is the anonymous kind; the messenger behind a wallet login is the other.
Install a plugin — or build your own.
Plugins install into your ilml CLI as packages. The LinkedIn plugin is the reference: it keeps a local mirror of your LinkedIn data and runs AI-assisted workflows under your direction — and doubles as the template for authoring your own.
ilml plugin install linkedin # from npm:ilml-plugin-linkedin
ilml plugin list # what's installed
ilml linkedin <command> # run a plugin command
# author one → examples/plugin-author.mjs,
# inside the installed packageBuild on your graph.
Your data, your code, your AI — all on one open graph.
Start your graph →