@ilivemylife/graph-sdk
v1.0.23
Published
SDK and CLI for iLiveMyLife Knowledge Graph
Downloads
1,169
Maintainers
Readme
@ilivemylife/graph-sdk
SDK for iLiveMyLife Knowledge Graph.
What is iLiveMyLife?
iLiveMyLife is your personal informational wallet — a unified platform to organize everything in your life.
Key Features
- Knowledge Graph — Structure your information as interconnected nodes, not just files and folders
- Cross-platform — Apps for iOS, Android, Web, Desktop (macOS, Windows, Linux)
- Real-time Sync — Changes sync instantly across all your devices
- Push Notifications — Stay informed with smart notifications
- Team Collaboration — Share nodes with team members, control privacy with tags (private/wallet/kyc/etc.)
- AI Assistants (Lifebot) — Built-in AI that understands your knowledge graph context
- Offline Support — Your data is synced on your device for offline access if not connected
- Powerful Tags System — Customize node behavior with tags (privacy, AI assistance, colors, markers, and more)
- Markers (Status System) — Track item status with customizable markers (like task states)
- Rich Content — Support for text, images, files, links, checklists, due dates, and more
- Robust API & SDK — Full-featured TypeScript SDK and GraphQL API for developers
- CLI Tool — Command-line interface for scripting and automation
- CLI Plugins — Extend
ilmlwith domain-specific automation (LinkedIn, Indeed, custom workflows) via tarball-based plugin packages - MCP Server — Integrate with AI assistants like Claude, Cursor, Windsurf via Model Context Protocol
Use Cases
- Personal knowledge management
- Team project coordination
- Task and goal tracking
- Note-taking with AI assistance
- Information organization for any domain
Get the app: https://iLiveMyLife.io
Prerequisites
- Node.js 18+ — download
- iLiveMyLife account — sign up at iLiveMyLife.io
Getting Started
1. Install
npm install -g @ilivemylife/graph-sdk2. Login
ilml login
ilml doctor # verify installationToken saved to ~/.ilivemylife/config.json. For per-project accounts, see Token Priority.
3. Add to AI tools (optional)
# Claude Code (global — available in all projects)
claude mcp add --scope user ilml -- npx -y @ilivemylife/graph-sdk
# Claude Code (current project only)
claude mcp add ilml -- npx -y @ilivemylife/graph-sdkFor Cursor, Windsurf, Claude Desktop — see MCP Setup.
Authentication Details
Config File
After ilml login, config is saved as JSON:
// ~/.ilivemylife/config.json
{
"token": "your-access-token",
"user": {
"id": "user-id",
"email": "[email protected]",
"displayName": "Your Name"
},
"rootItemId": "your-root-node-id"
}Token Priority
The SDK, CLI, and MCP server resolve tokens in this order (first found wins):
| Priority | Source | How to set |
|----------|--------|------------|
| 1 | Local config | ilml login --local → .ilivemylife/config.json in project dir |
| 2 | .env file | ILML_TOKEN entry in .env (project dir) |
| 3 | Global config | ilml login → ~/.ilivemylife/config.json |
| 4 | Shell env var | ILML_TOKEN in the shell environment (e.g. $env:ILML_TOKEN / export ILML_TOKEN=...) |
Use different accounts per project:
# Global account (default)
ilml login
# Project-specific account (overrides global)
cd my-project
ilml login --localOther Authentication Methods
Programmatic login (for bots and automations):
import { login, createGraphClient } from '@ilivemylife/graph-sdk';
const { user, accessToken } = await login('[email protected]', 'password');
const graph = createGraphClient({ token: accessToken });Browser DevTools (manual):
- Open https://app.ilivemylife.io and log in
- Open DevTools (F12) → Network tab
- Click any GraphQL request → Headers tab
- Copy
access-tokenvalue
Finding Node IDs
Node IDs are visible in the URL when viewing a node:
https://app.ilivemylife.io/item/00001191f13c5c81-ee4ae8284f820001
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the node IDOr get your root node ("My Life") programmatically:
const me = await graph.me();
console.log(me.rootItemId); // Your root node IDQuick Start
import { createGraphClient } from '@ilivemylife/graph-sdk';
// Create client (token from ilml login or env variable)
const graph = createGraphClient({
token: process.env.ILML_TOKEN
});
// Get your root node
const me = await graph.me();
const rootId = me.rootItemId;
// See what's in your graph
const items = await graph.items(rootId);
console.log('My Life:', items[0].title);
console.log('Children:', items.slice(1).map(i => i.title));
// Create a new node
const project = await graph.addItem(rootId, { title: 'My Project' });
// Add children to it
await graph.addItem(project.id, { title: 'First Task' });
await graph.addItem(project.id, { title: 'Second Task' });
// Ask AI about it (requires 'assist' tag on the node)
const reply = await graph.askLifebot(project.id, 'What tasks do I have?');
console.log(reply.content);For a complete walkthrough, run: node examples/getting-started.mjs
Features
- TypeScript — Full type definitions included
- Dual Format — Works with both ESM (
import) and CommonJS (require) - Typed Errors —
GraphError,LifebotTimeoutError,LifebotRateLimitError - Injectable Logger — Pass your own logger or use silent mode
- Real-time Subscriptions — Subscribe to message and item changes
Examples
See examples/ directory for complete usage examples:
- getting-started.mjs — Start here! Complete walkthrough (no NODE_ID needed)
- messenger-basics.mjs — Messages and Lifebot AI
- ask-lifebot.mjs — AI interactions with error handling
- manage-items.mjs — Create, edit, reorder items
- subscriptions.mjs — Real-time updates
- error-handling.mjs — Error handling patterns
API Reference
Creating a Client
import { createGraphClient } from '@ilivemylife/graph-sdk';
const graph = createGraphClient({
token: 'your-access-token', // Required
logger: console // Optional: enable logging
});Queries (Read Operations)
// Get node and its children
const items = await graph.items(nodeId);
// items[0] = node itself, items[1...n] = children
// Get messages
const messages = await graph.messages(nodeId, 20);
// Get change history
const history = await graph.itemHistory(nodeId);
// Get users with access
const users = await graph.itemUsers(nodeId);
// Get child count
const { count } = await graph.subItemCount(nodeId);
// Get node settings
const settings = await graph.itemSettings(nodeId);
// Edit node settings (AI provider, notifications, etc)
import { AI_PROVIDER_TYPES, TOGGLE_VALUES } from '@ilivemylife/graph-sdk';
await graph.editItemSettings({
itemId: nodeId,
artificialIntelligenceProvider: AI_PROVIDER_TYPES.openai, // switch AI to OpenAI
intelligence: TOGGLE_VALUES.enabled // use smart model
});
// Reset to app defaults (user's global settings)
await graph.editItemSettings({
itemId: nodeId,
artificialIntelligenceProvider: null, // null = use user's app default
intelligence: null
});
// Search messages
const found = await graph.searchMessages(nodeId, 'keyword');Mutations (Write Operations)
// Create item (all fields optional, node added to end of list by default)
const item = await graph.addItem(parentId, {
title: 'New Item',
description: 'Description',
tags: ['tag1', 'tag2'],
dueDate: '1735689600000' // timestamp string (ms)
});
// Create link/shortcut to another node
const link = await graph.addItem(parentId, {
refId: originalNodeId, // reference to original node
title: 'Custom Title', // override original title
description: 'Custom Desc' // override original description
});
// Edit item
await graph.editItem({
id: itemId,
title: 'Updated Title'
});
// Archive/unarchive
await graph.archiveItem(itemId);
await graph.unarchiveItem(itemId);
// Move item to different parent
await graph.moveItem(itemId, fromParentId, toParentId);
// Move item to different parent at specific position (1-based)
await graph.moveItemToPosition(itemId, fromParentId, toParentId, 3);
// Reorder within parent (positions are 1-based)
await graph.reorderChild(parentId, fromPosition, toPosition);
// Example: move child from position 4 to position 1
await graph.reorderChild(parentId, 4, 1);
// Move specific node to position (positions are 1-based)
await graph.setPosition(nodeId, parentId, position);
// Example: move node to position 2
await graph.setPosition(nodeId, parentId, 2);File Upload
// Upload from file path (Node.js)
const file = await graph.uploadFile(nodeId, '/path/to/image.jpg');
console.log(file.uri); // CDN URL
console.log(file.filename); // 'image.jpg'
console.log(file.mimetype); // 'image/jpeg'
// Upload from Buffer
const buffer = fs.readFileSync('/path/to/data.csv');
const file = await graph.uploadFile(nodeId, buffer, {
filename: 'data.csv',
mimetype: 'text/csv'
});
// Upload from Blob (browser)
const file = await graph.uploadFile(nodeId, blob, { filename: 'photo.png' });Messages
import { LIFEBOT_MESSAGE_TYPES, MESSAGE_TYPES } from '@ilivemylife/graph-sdk';
// Simple message (no AI)
await graph.addMessage(nodeId, 'Hello!', {
lifebot: LIFEBOT_MESSAGE_TYPES.off
});
// Message that triggers AI response
await graph.addMessage(nodeId, 'Help me with this', {
lifebot: LIFEBOT_MESSAGE_TYPES.on
});
// Reply to a message
await graph.addMessage(nodeId, 'Thanks!', {
type: MESSAGE_TYPES.reply,
refId: originalMessageId
});
// Edit/archive messages
await graph.editMessage({ id: msgId, content: 'Updated' });
await graph.archiveMessage(msgId);Real-time Subscriptions
import { MESSAGE_CHANGE_TYPES, ITEM_CHANGE_TYPES } from '@ilivemylife/graph-sdk';
// Subscribe to messages
const unsubscribeMessages = await graph.subscribeToMessages(nodeId, (change, message) => {
if (change === MESSAGE_CHANGE_TYPES.added) {
console.log('New message:', message.content);
} else if (change === MESSAGE_CHANGE_TYPES.edited) {
console.log('Message edited:', message.content);
} else if (change === MESSAGE_CHANGE_TYPES.deleted) {
console.log('Message deleted:', message.id);
}
});
// Subscribe to item changes
const unsubscribeItems = await graph.subscribeToItems(nodeId, (change, item) => {
if (change === ITEM_CHANGE_TYPES.added) {
console.log('New item:', item.title);
} else if (change === ITEM_CHANGE_TYPES.movedIn) {
console.log('Item moved here:', item.title);
} else if (change === ITEM_CHANGE_TYPES.reorder) {
console.log('Item reordered:', item.title);
}
});
// Stop listening
unsubscribeMessages();
unsubscribeItems();
// Wait for specific message
const msg = await graph.waitForMessage(nodeId,
(change, msg) => change === MESSAGE_CHANGE_TYPES.added && msg.createdBy === 'Lifebot',
30000 // timeout
);
// Wait for any reply after your message
const reply = await graph.waitForReply(nodeId, myMessageId);
// Wait for a reply that directly references your message (via refId)
const exactReply = await graph.waitForReply(nodeId, myMessageId, { exactMatch: true, timeout: 60000 });AI Interactions
import { LifebotTimeoutError, LifebotRateLimitError } from '@ilivemylife/graph-sdk';
try {
const response = await graph.askLifebot(nodeId, 'What is 2 + 2?', {
timeout: 60000 // 60 seconds
});
console.log(response.content);
} catch (error) {
if (error instanceof LifebotTimeoutError) {
console.error('AI did not respond in time');
} else if (error instanceof LifebotRateLimitError) {
console.error('Rate limit exceeded');
}
}What is Lifebot?
Lifebot is the AI assistant that lives inside your iLiveMyLife knowledge graph. It can:
- Answer questions about your data and context
- Search and find information across your nodes
- Create new nodes with titles and descriptions
- Organize information by creating nested structure
- Execute actions on your behalf
// Ask Lifebot to create a project with sub-tasks
await graph.askLifebot(nodeId,
'Create a node "Weekly Plan" with 3 tasks inside: Monday review, Wednesday sync, Friday summary'
);
// Lifebot will create the parent node AND all children!Enable Lifebot: Add assist tag to a node to enable Lifebot in that context.
Helper Methods
// Get single item
const item = await graph.getItem(nodeId);
// Resolve reference (shortcut) to actual node
const actual = await graph.resolveRef(nodeId);
// Get items with references resolved
const items = await graph.itemsResolved(nodeId);Cleanup
// Close all connections and free resources when done
graph.destroy();Call destroy() at the end of scripts to close WebSocket connections and let the process exit. Safe to call multiple times. Not needed for HTTP-only usage (queries/mutations without subscriptions).
Validation
import { isValidNodeId, parseNodeId, validateNodeId } from '@ilivemylife/graph-sdk';
// Check if string is valid node ID
isValidNodeId('00001191f13c5c81-ee4ae8284f820001') // true
isValidNodeId('invalid') // false
// Parse node ID from raw ID or full URL
parseNodeId('00001191f13c5c81-ee4ae8284f820001')
// → '00001191f13c5c81-ee4ae8284f820001'
parseNodeId('https://app.ilivemylife.io/item/00001191f13c5c81-ee4ae8284f820001')
// → '00001191f13c5c81-ee4ae8284f820001'
parseNodeId('invalid')
// → null
// Validate and throw error if invalid
validateNodeId(userInput); // throws GraphError if invalidREST API
// Login (standalone — no client needed)
import { login } from '@ilivemylife/graph-sdk';
const { user, accessToken } = await login(email, password);
// Login (instance method — if client already exists)
const result = await graph.login(email, password);
// Get current user
const me = await graph.me();Error Handling
import {
GraphError, // Base error
GraphNetworkError, // Network/connection issues
GraphAuthError, // Authentication failed
NodeAuthRequiredError, // Read on a gated node failed — caller does not have access yet
NodeKycRequiredError, // Node requires KYC (open in app to verify)
LifebotError, // AI error base
LifebotTimeoutError, // AI timeout
LifebotRateLimitError // AI rate limit
} from '@ilivemylife/graph-sdk';
try {
await graph.items(nodeId);
// Same pattern applies to every read method: items, getItem, resolveRef,
// itemsResolved, messages, itemHistory, itemUsers, subItemCount, itemSettings,
// searchMessages — all throw NodeAuthRequiredError on auth gate.
} catch (error) {
if (error instanceof NodeAuthRequiredError) {
// error.itemId, error.options ('private' | 'kyc' | 'wallet' | ...), error.requestStatus
// error.message is pre-formatted with the recommended next step
console.error(error.message);
// Decide what to do: graph.requestAccess(nodeId) for private/ask, open in app for kyc.
} else if (error instanceof GraphAuthError) {
console.error('Check your token');
} else if (error instanceof GraphError) {
console.error('Operation failed:', error.code);
}
}Requesting Access to Gated Nodes
Some nodes are gated. The SDK / CLI / MCP can request access programmatically without going through KYC — the server creates a request record and notifies the node owner via Lifebot. The user waits for the owner to approve, then retries the original read.
Two server-side processes
The server's ACCESS_PROCESS enum mirrors the two independent gating processes a node can require:
| ACCESS_PROCESS value | Triggered by tags | Headless support |
|---|---|---|
| ACCESS_PROCESS.PRIVATE ('private') | Tags like private, ask | Yes — requestAccess() sends the request, owner approves via @@username mention |
| ACCESS_PROCESS.KYC ('kyc') | Tags like kyc, wallet, bank | No — needs the in-app PaymentRequest UI; SDK throws NodeKycRequiredError |
A node may need both (e.g. wallet triggers KYC + PRIVATE). The server inspects the tags and returns the union of required processes in arrayOfOptions. The SDK never branches on raw tag names — only on these two process labels — so adding a new tag server-side does not require an SDK change.
SDK
import { ACCESS_RESULT_CODE, NodeKycRequiredError } from '@ilivemylife/graph-sdk';
try {
const r = await graph.requestAccess(nodeId);
// r.ok: always true (errors are thrown)
// r.accessStatus: true if the caller has access right now
// r.code: one of ACCESS_RESULT_CODE values
// r.message: SDK-formatted convenience string for terminals/logs
if (r.accessStatus) {
console.log('Have access — proceed:', r.message);
} else if (r.code === ACCESS_RESULT_CODE.REQUEST_PENDING) {
console.log('Request still pending; ask user to come back later');
} else if (r.code === ACCESS_RESULT_CODE.REQUEST_SENT) {
console.log('Request just sent to owner; wait for approval');
}
} catch (error) {
if (error instanceof NodeKycRequiredError) {
console.log(`Open ${error.itemId} in app.iLiveMyLife.io to complete KYC.`);
} else {
throw error;
}
}The mutation is idempotent — calling on a node where you already have access returns ALREADY_ACCESSED, on a node with a pending request returns REQUEST_PENDING. The same method works as both "send request" and "check status" — no preflight read, no separate poll endpoint.
CLI
# Default output — human-readable, exit 0 on success, 1 on KYC required or error
ilml request-access <nodeId>
# → Request sent to owner of <id>. They will get a Lifebot notification.
# JSON output — mirrors server's AddUserAccessResponse for scripts
ilml request-access <nodeId> --json
# → { "ok": true, "accessStatus": false, "code": "REQUEST_SENT", "message": "...", ... }MCP
The graph_request_access tool returns the same server-shape ({ ok, accessStatus, code, message, itemAccessedResponse? }). KYC is not thrown — it's converted into { ok: false, code: 'KYC_REQUIRED', ... } so the LLM can relay the message to the user without retrying.
Constants
import {
// Message options
MESSAGE_TYPES, // normal, reply, forward
LIFEBOT_MESSAGE_TYPES, // on, off
NOTIFY_MESSAGE_TYPES, // on, off, postpone
// Subscription change types
MESSAGE_CHANGE_TYPES, // added ('+1'), edited ('edit'), deleted ('-1')
ITEM_CHANGE_TYPES, // added ('+1'), edited ('edit'), deleted ('-1'), movedIn ('movedIn'), reorder ('reorder')
// Access and settings
ITEM_ACCESS_TYPES, // human, bot, request
AI_PROVIDER_TYPES, // openai, claude, gemini
TOGGLE_VALUES, // enabled, disabled (for intelligence, lifebotRootAccess)
NODE_SPECIAL_TYPES, // user_settings, user_system, non_drop
// Access request result codes (server's response code in addUserAccessRequest)
ACCESS_RESULT_CODE, // ALREADY_ACCESSED, REQUEST_PENDING, REQUEST_SENT, ACCESS_GRANTED, KYC_REQUIRED
// GraphQL error codes (server's error.extensions.code on read failures)
GRAPHQL_ERROR_CODES, // NODE_AUTH_REQUIRED, UNAUTHENTICATED, NOT_FOUND, INVALID_REF, UNKNOWN, GRAPHQL_VALIDATION_FAILED
// Process labels in NODE_AUTH_REQUIRED's `arrayOfOptions` — what gating processes apply
ACCESS_PROCESS, // KYC, PRIVATE (server normalizes raw tags into these)
// States of an existing access request (NODE_AUTH_REQUIRED's `requestStatus`)
REQUEST_STATUS, // PENDING
// Server's default arrayOfOptions when tags are not in scope (KYC + PRIVATE — safest)
DEFAULT_ACCESS_OPTIONS
} from '@ilivemylife/graph-sdk';
// Subscription change values:
// MESSAGE_CHANGE_TYPES.added === '+1'
// MESSAGE_CHANGE_TYPES.edited === 'edit'
// MESSAGE_CHANGE_TYPES.deleted === '-1'
//
// ITEM_CHANGE_TYPES.added === '+1'
// ITEM_CHANGE_TYPES.edited === 'edit'
// ITEM_CHANGE_TYPES.deleted === '-1'
// ITEM_CHANGE_TYPES.movedIn === 'movedIn'
// ITEM_CHANGE_TYPES.reorder === 'reorder'Tags & Markers
Tags control node behavior. Format: #tagname#value or just tagname.
Special Tags
| Tag | Description |
|-----|-------------|
| wallet | High privacy — entering the node requires access. Hidden from AI completely (invisible in search, context, and responses). Can be shared between users (e.g. corporate wallet between CEO and accountant) by granting access. Known limitation: currently wallet nodes are still visible (title, description) in parent's children list and in messenger link previews to users without access. This will be fixed — wallet nodes should be completely invisible to unauthorized users |
| private | Lower privacy — node itself is visible but entering (seeing children) requires access. Users with access can use AI to search inside if allowAIPrivateSearch setting is enabled |
| assist | Enables Lifebot AI in this node |
| #color#r,g,b,a | Node background color |
| #markerList#<listId> | Enables status dropdown for children |
| #marker#<statusId> | Sets item status from marker list |
Markers (Status System)
Markers let you track status of items (like task states). It's a pattern, not a built-in feature:
- Create a marker list — a node with status options as children (emoji titles)
- Enable on parent — add
#markerList#<listId>tag - Set status — add
#marker#<statusId>tag to items
// Enable default markers on a parent node
await graph.editItem({
id: parentId,
tags: ['#markerList#0000018190aaf813-a61a6f263e6a0000']
});
// Mark item as completed (default marker)
await graph.editItem({
id: taskId,
tags: ['#marker#0000018195a2d792-a61a6f263e6a0000'] // ☑️ Completed
});See CLAUDE.md for full marker documentation and default marker IDs.
CLI Usage
# Install globally
npm install -g @ilivemylife/graph-sdk
# Login (saves token to ~/.ilivemylife/config.json)
ilml login
# Queries (read operations) - all support --json for scripting
ilml items <nodeId> # List node and children (short)
ilml items <nodeId> --single # Node only, no children
ilml items <nodeId> --long # Full details (all fields)
ilml items <nodeId> --json # JSON output (for scripting)
ilml items <nodeId> --ids # IDs only (for piping)
ilml messages <nodeId> [count] # Show messages (short)
ilml messages <nodeId> --long # Full message details
ilml messages <nodeId> --json # JSON output
ilml itemHistory <nodeId> # Show change history
ilml itemHistory <nodeId> --json # JSON output
ilml itemUsers <nodeId> --json # Users with access
ilml subItemCount <nodeId> # Get children count
ilml itemSettings <nodeId> # Show node settings
ilml search <nodeId> "text" # Search messages
ilml tree <nodeId> [depth] # Show tree structure (truncated)
ilml tree <nodeId> --long # Full titles, no truncation
# Item mutations
ilml addItem <parentId> --title "Task" --desc "Description" --tags work,urgent
ilml addItem <parentId> --ref <nodeId> # Create link to another node
ilml editItem <nodeId> --title "New Title" --tags updated
ilml archiveItem <nodeId>
ilml unarchiveItem <nodeId>
ilml moveItem <nodeId> <fromParentId> <toParentId>
ilml moveItemToPosition <nodeId> <from> <to> <pos> # Move to parent at position (1-based)
ilml reorderChild <parentId> <from> <to> # Reorder child (1-based positions)
ilml setPosition <nodeId> <parentId> <pos> # Move to position (1-based)
ilml editSettings <nodeId> --provider claude # Change AI provider
ilml editSettings <nodeId> --intelligence disabled # Use cheaper AI model
ilml editSettings <nodeId> --rootAccess enabled # AI searches entire graph for context
ilml editSettings <nodeId> --push true --inApp true # Enable notifications
ilml editSettings <nodeId> --provider null # Reset to app default
# Messaging
ilml send <nodeId> "message" # Send message (no AI)
ilml ask <nodeId> "question" # Ask AI and wait for response
ilml editMessage <msgId> "new content"
ilml archiveMessage <msgId>
# Access (request access to gated nodes — see "Requesting Access" section above)
ilml request-access <nodeId> # Send request OR check status (idempotent)
ilml request-access <nodeId> --json # Structured output for scripts
# File upload
ilml upload <nodeId> /path/to/file.jpg
# Management
ilml config # Show configuration
ilml me # Current user info
# Plugins (extend ilml with domain-specific automation)
ilml plugin install linkedin # auto-prefix → npm:ilml-plugin-linkedin (latest)
ilml plugin install linkedin npm:[email protected] # pinned npm version
ilml plugin install custom https://example.com/x.tgz # direct HTTPS tarball (private/paid)
ilml plugin install custom file:///abs/path/x.tgz # local tarball (development)
ilml plugin list # List installed plugins (with source kind)
ilml plugin update # Check all installed; npm sources are version-checked
ilml plugin update linkedin # Update one plugin (uses stored source)
ilml plugin remove <name> # Uninstall plugin
ilml plugin config <name> # Show plugin config in active scope
ilml plugin config <name> set KEY VALUE # Set a config value
ilml plugin config <name> unset KEY # Remove a config value
ilml plugin config <name> reset # Wipe config (re-prompt on next run)
ilml <pluginName> <command> [args] # Run an installed plugin's commandToken Configuration
Token is resolved in order:
- Local config —
.ilivemylife/config.jsonin current directory (or parent directories) .envfile withILML_TOKENentry in current directory- Global config —
~/.ilivemylife/config.json(set byilml login) - Shell env var
ILML_TOKENin the shell environment
This allows per-project tokens:
# Project A uses user A's token
project-a/.ilivemylife/config.json # via: ilml login --local
# Or use .env file
project-b/.env # ILML_TOKEN=xxxMCP Server (AI Assistant Integration)
MCP (Model Context Protocol) allows AI assistants to interact with external tools. This SDK includes an MCP server that exposes graph operations to Claude, Cursor, Windsurf and other MCP-compatible tools.
Setup
Prerequisites: Run ilml login first (see Getting Started).
Claude Code:
# Global (available in all projects)
claude mcp add --scope user ilml -- ilml-mcp
# Or current project only
claude mcp add ilml -- ilml-mcpIf you didn't install globally (npm install -g), use npx:
claude mcp add --scope user ilml -- npx -y @ilivemylife/graph-sdkOther AI tools — add to config file:
{
"mcpServers": {
"ilml": {
"command": "npx",
"args": ["-y", "@ilivemylife/graph-sdk"]
}
}
}Or if installed globally:
{
"mcpServers": {
"ilml": {
"command": "ilml-mcp"
}
}
}| AI Tool | Config Path |
|---------|------------|
| Claude Code | claude mcp add (recommended) or ~/.claude/settings.json |
| Claude Desktop | macOS: ~/Library/Application Support/Claude/claude_desktop_config.json |
| | Windows: %APPDATA%\Claude\claude_desktop_config.json |
| Cursor | ~/.cursor/mcp.json |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
| IntelliJ IDEA | Settings → Tools → AI Assistant → Model Context Protocol (MCP) |
Token is resolved automatically: local config > .env file > global config > shell env var ILML_TOKEN. Restart your AI tool after adding the config.
Available Tools
Read Operations (no confirmation needed):
graph_items— Get node and childrengraph_get_item— Get a single node by IDgraph_resolve_ref— Resolve a reference node to its targetgraph_items_resolved— Get items with all references resolvedgraph_messages— Get messages from nodegraph_search_messages— Search messagesgraph_item_history— Get change historygraph_item_users— Get users with accessgraph_sub_item_count— Get count of childrengraph_item_settings— Get node settingsgraph_me— Get current user infograph_info— Get documentation about graph features (topics: markers, tags, links, privacy, lifebot, settings, plugins, all)graph_plugins— List installed CLI plugins (name, version, commands)graph_plugin_config— Show plugin configuration in active scope (secrets masked)
Write Operations (require confirmation):
graph_add_item— Create new nodegraph_edit_item— Edit node (title, description, tags)graph_edit_settings— Edit node settings (AI provider, notifications)graph_archive_item/graph_unarchive_item— Archive/restoregraph_move_item— Move to different parentgraph_reorder_child/graph_set_position— Reordergraph_add_message— Send messagegraph_edit_message— Edit existing messagegraph_archive_message— Delete a messagegraph_ask_lifebot— Ask AI and wait for responsegraph_upload_file— Upload file to a nodegraph_request_access— Request access to a gated node (private/ask), or check status of an existing request. Returns structured{ status: 'access' | 'pending' | 'sent' | 'kyc_required' | 'error', code, message, ... }. KYC nodes returnkyc_required(the LLM should tell the user to open in app)
Example Prompts
"Show me items in node abc123"
"Create a task called 'Review PR' under node xyz789"
"What messages are in my root node?"
"Ask Lifebot to summarize this project"
"Switch AI to OpenAI for node xyz789"
"Use cheaper AI model for this node to save costs"
"What ilml plugins do I have installed?"
"Why doesn't `ilml linkedin apply` work? Check what's missing."Plugins
ilml CLI can be extended with plugins — npm-style tarballs that add domain-specific commands like ilml linkedin apply or ilml indeed search. End users install plugins once per machine; their configuration is stored per-scope (mirroring ilml's token resolution).
ilml plugin install linkedin # auto-prefix → npm:ilml-plugin-linkedin (latest)
ilml linkedin apply # first run prompts for required config
ilml plugin list # see what's installed
ilml plugin config linkedin # inspect saved config (secrets masked)
ilml plugin update # check all installed; npm sources only re-download if newerThe bare-name install uses the convention ilml-plugin-<name> on npm. Private/paid plugins ship as direct HTTPS tarballs:
ilml plugin install secret-tool https://yourhost.example/secret-tool-1.0.0.tgzFor Plugin Authors
Plugins ship an ilml-plugin.json manifest declaring commands and a config.schema. Inside the plugin, configuration is read via the SDK:
import { getPluginConfig, getCachedUser } from '@ilivemylife/graph-sdk';
const cfg = await getPluginConfig(); // { LINKEDIN_EMAIL, ..., from current scope }
const user = getCachedUser(); // sync, no network — { id, email, ... } or nullThe SDK auto-discovers the plugin's manifest by walking up from process.cwd(), validates the schema, and pulls values from the right place automatically — production reads from the active scope (<scope>/.ilivemylife/plugins-state/<name>.json), dev mode reads from .env next to the manifest. Plugin authors don't need to handle scopes themselves.
See PLUGINS.md for the full architecture spec, dev workflow, and isolation/trust model.
TypeScript
Full type definitions included:
import {
// Functions
createGraphClient,
login, // standalone login (no client needed)
// Plugin SDK (for plugin authors)
getPluginConfig,
getPluginConfigDetails,
getCachedUser,
// Constants
MESSAGE_TYPES, LIFEBOT_MESSAGE_TYPES, NOTIFY_MESSAGE_TYPES,
ITEM_ACCESS_TYPES, AI_PROVIDER_TYPES, TOGGLE_VALUES
} from '@ilivemylife/graph-sdk';
import type {
// Data types
Item, Message, User, ItemSettings,
HistoryEntry, ItemUser, SubItemCount,
FileInfo, UploadFileOptions,
// Client types
GraphClient, GraphClientOptions,
AddItemOptions, EditItemInput, EditItemSettingsInput,
AddMessageOptions, EditMessageInput, AskLifebotOptions,
// Plugin SDK types
PluginManifest, PluginConfigSchemaEntry,
PluginConfigDetails, PluginConfigSource, CachedUser,
// Subscription change types (for type-safe comparisons)
MessageChangeType, // '+1' | 'edit' | '-1'
ItemChangeType // '+1' | 'edit' | '-1' | 'movedIn' | 'reorder'
} from '@ilivemylife/graph-sdk';Staying Updated
Get notified about new releases:
- In-app notifications — open the Releases node in iLiveMyLife and enable push notifications. You'll receive a notification whenever a new version is published.
- CLI — run
ilml updateto check for updates and install the latest version.
License
© 2026 Ilya Sorokin. All rights reserved.
This package is proprietary software. For licensing inquiries, business partnerships, or enterprise usage, please contact the author.
Author
Created by Ilya Sorokin — founder and developer of iLiveMyLife.
- LinkedIn: linkedin.com/in/ilyasorokin
- Email: [email protected]
- App: https://iLiveMyLife.io
