npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ilivemylife/graph-sdk

v1.0.23

Published

SDK and CLI for iLiveMyLife Knowledge Graph

Downloads

1,169

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 ilml with 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

Getting Started

1. Install

npm install -g @ilivemylife/graph-sdk

2. Login

ilml login
ilml doctor  # verify installation

Token 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-sdk

For 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 --local

Other 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):

  1. Open https://app.ilivemylife.io and log in
  2. Open DevTools (F12) → Network tab
  3. Click any GraphQL request → Headers tab
  4. Copy access-token value

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 ID

Or get your root node ("My Life") programmatically:

const me = await graph.me();
console.log(me.rootItemId);  // Your root node ID

Quick 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 ErrorsGraphError, 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:


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 invalid

REST 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 | YesrequestAccess() 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:

  1. Create a marker list — a node with status options as children (emoji titles)
  2. Enable on parent — add #markerList#<listId> tag
  3. 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 command

Token Configuration

Token is resolved in order:

  1. Local config — .ilivemylife/config.json in current directory (or parent directories)
  2. .env file with ILML_TOKEN entry in current directory
  3. Global config — ~/.ilivemylife/config.json (set by ilml login)
  4. Shell env var ILML_TOKEN in 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=xxx

MCP 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-mcp

If you didn't install globally (npm install -g), use npx:

claude mcp add --scope user ilml -- npx -y @ilivemylife/graph-sdk

Other 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 children
  • graph_get_item — Get a single node by ID
  • graph_resolve_ref — Resolve a reference node to its target
  • graph_items_resolved — Get items with all references resolved
  • graph_messages — Get messages from node
  • graph_search_messages — Search messages
  • graph_item_history — Get change history
  • graph_item_users — Get users with access
  • graph_sub_item_count — Get count of children
  • graph_item_settings — Get node settings
  • graph_me — Get current user info
  • graph_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 node
  • graph_edit_item — Edit node (title, description, tags)
  • graph_edit_settings — Edit node settings (AI provider, notifications)
  • graph_archive_item / graph_unarchive_item — Archive/restore
  • graph_move_item — Move to different parent
  • graph_reorder_child / graph_set_position — Reorder
  • graph_add_message — Send message
  • graph_edit_message — Edit existing message
  • graph_archive_message — Delete a message
  • graph_ask_lifebot — Ask AI and wait for response
  • graph_upload_file — Upload file to a node
  • graph_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 return kyc_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 newer

The 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.tgz

For 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 null

The 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 update to 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.