Logseq Skill
Thesis: Unified interface for Logseq operations - read/write pages, search blocks, graph analytics, AI summarization via Fire CLI (lsq) or Python API.
Trigger: "add to Logseq", "create Logseq page", "search Logseq", "логсек", "/logseq"
Quick Reference
# Page operations
lsq get_node "Page Name" # Full context (properties, tags, queries)
lsq get_page "Page Name" # Basic page data
lsq list_all_pages # List all pages
lsq create_page "Name" "Content" # Create page
lsq delete_page "Page" # Delete page
lsq rename_page "Old" "New" # Change display title
lsq open_page_in_logseq "Page" # Open in Logseq app
# Block operations
lsq search_blocks "query" # Search blocks
lsq append_block "Page" "Content" # Add block to page
# Graph analytics
lsq get_top_pages # Top 20 by PageRank
lsq get_page_importance "Page" # All metrics for page
lsq get_orphan_pages # Pages with no refs
lsq get_hierarchy "Library" # Show hierarchy tree
# AI summarization
lsq summarize_node "Page" # Generate AI summary
lsq daily_summary # Daily summary
# Placement suggestion (LLM-powered)
lsq suggest_placement "content description" # 3-5 candidates with scores
Architecture (2025-12-29)
data_sources/logseq/
├── protocol.py # HTTP API (367 lines, single source of truth)
├── ops/ # Operations package (2683 lines total)
│ ├── __init__.py # Auto-import from submodules
│ ├── __main__.py # Fire CLI entry + auto-wrap
│ ├── _normalize.py # Input: Fire quirks (lists, [[brackets]])
│ ├── _formatters.py # Output: smart format by return type
│ ├── node.py # NodeData, get_node, format_node, change_node
│ ├── pages.py # Page CRUD
│ ├── blocks.py # Block CRUD
│ ├── graph.py # Graph building, hierarchy
│ ├── analytics.py # PageRank, betweenness, communities
│ ├── ai.py # AI summarization
│ ├── library_search.py # LLM search + suggest_placement
│ ├── entity.py # Entity comparison
│ └── utils.py # Utilities
├── bin/lsq # Shell wrapper (17 lines)
├── filters/ # Graph filtering system
├── dashboard/ # Web visualization
├── pipeline/ # ETL (extractors, loaders)
└── tests/ # Test suite
Auto-discovery: Add function to ops/*.py → CLI command automatically (no registration).
1. CLI Commands (38 total)
| Category | Command | Purpose |
|----------|---------|---------|
| Connection | check_connection | Verify Logseq running |
| Pages | list_all_pages | List all pages |
| | get_page NAME | Basic page data |
| | get_node NAME | Full context (props, tags, queries) |
| | create_page NAME CONTENT | Create page |
| | delete_page NAME | Delete page (force by default) |
| | rename_page OLD NEW | Change display title |
| Blocks | search_blocks QUERY | Search by content |
| | append_block PAGE TEXT | Add block to page |
| | get_block UUID | Get block by ID |
| Graph | get_hierarchy ROOT | Show hierarchy tree |
| | get_page_neighbors PAGE | Get connected pages |
| | build_graph_data | Build full graph |
| | get_children PARENT | Get direct children of page |
| Navigation | subtree NODE --depth N | Hierarchy from any node down |
| | path_to_root NODE | Path from node to Library |
| | around NODE | Radar: path + siblings + children |
| | hierarchy_with_summary ROOT | Tree with first-sentence summaries |
| Search | search QUERY --provider gemini | LLM semantic search (~$0.0004/query) |
| | suggest_placement QUERY | LLM suggests 3-5 best Library locations |
| | count_tokens ROOT --depth N | Estimate tokens for LLM context |
| Analytics | get_top_pages | Top 20 by PageRank |
| | get_page_importance PAGE | All metrics for page |
| | get_orphan_pages | Pages with no refs |
| AI | summarize_node PAGE | AI summary (sentence + paragraph) |
| | daily_summary | Daily summary from sessions |
| Node | change_node NAME --title T --add-tag X | Modify node |
2. Python API
Core Functions
from data_sources.logseq.ops import (
# Unified node access (DRY)
get_node, format_node, change_node, NodeData,
# Page operations
list_all_pages, get_page, create_page, delete_page, rename_page,
# Block operations
search_blocks, append_block, insert_block, update_block, remove_block,
# Graph
build_graph_data, get_hierarchy, get_page_neighbors,
# Analytics
get_top_pages, get_page_importance, get_orphan_pages,
# AI
summarize_node, daily_summary,
# Library search (LLM-powered)
search, suggest_placement,
)
get_node() — The Main Function
Returns complete NodeData with EVERYTHING:
- Properties (resolved UUIDs → names)
- Tag instances (pages tagged with this tag)
- Embedded queries with filtered results
- Child pages (pages with parent = this)
- Block content tree
from data_sources.logseq.ops import get_node, format_node
node = get_node("jira_ticket") # Use internal name, not display title!
if node:
print(format_node(node)) # Readable text output
# Access structured data
print(node.properties) # {'status': 'Done', ...}
print(node.tag_instances) # [{'name': 'page1', ...}, ...]
print(node.child_pages) # [{'name': 'child1', ...}, ...]
change_node() — Symmetric Write
from data_sources.logseq.ops import change_node
# Change title
change_node("page", title="New Title")
# Set properties
change_node("page", properties={"status": "done"})
# Add/remove tags
change_node("page", add_tags=["task"], remove_tags=["draft"])
# Replace all content
change_node("page", clear_blocks=True, add_blocks=["New content", "More content"])
create_page() — Proper Block Structure
IMPORTANT: Automatically splits content into proper Logseq blocks:
- Code blocks (
mermaid) stay together as ONE block - Empty lines create block boundaries
- Headers (##), list items (-) become standalone blocks
from data_sources.logseq.ops import create_page
content = """## Section
- Item 1
- Item 2
```mermaid
graph TD
A --> B
```"""
create_page("My Page", content) # Creates page with proper block structure
suggest_placement() — Find Best Library Location
Analyzes content description and returns 3-5 best placement candidates:
from data_sources.logseq.ops import suggest_placement
# Returns list of SearchResult with score, reason, tradeoff
results = suggest_placement("Gong call notes for HP customer")
for r in results:
print(f"{r.name} (score: {r.score})")
print(f" Reason: {r.reason}")
print(f" Tradeoff: {r.tradeoff}")
Evaluation criteria (via Gemini Flash):
- Semantic fit: how well content matches parent topic
- Sibling coherence: alignment with existing child pages
- Hierarchy level: appropriate depth (not too deep/shallow)
- Specificity match: general vs specific content placement
CLI usage:
lsq suggest_placement "marketing analytics dashboard design"
3. Page Names (CRITICAL)
Logseq DB uses internal names, not display titles:
| Display Title | Internal Name | Use in API |
|---------------|---------------|------------|
| Jira Ticket | jira_ticket | get_page("jira_ticket") |
| Miras Architecture | miras architecture | get_page("miras architecture") |
| My Page Name | my page name | get_page("my page name") |
Find internal name:
from data_sources.logseq.ops import list_all_pages
pages = list_all_pages()
for p in pages:
if 'jira' in p.get('name', '').lower():
print(f"name: '{p['name']}', title: '{p.get('title')}'")
4. Shell Quoting (CRITICAL)
ALWAYS quote [[page]] syntax in bash/zsh:
# ✅ Correct:
lsq get_node "[[Gong]]"
lsq get_page 'jira_ticket'
lsq search_blocks "claude code"
# ❌ WRONG - zsh interprets [[]] as glob:
lsq get_node [[Gong]] # Error: no matches found
5. DB Mode Properties
Class Properties vs Plugin Properties
from data_sources.logseq.protocol import call_api
# ✅ CORRECT - fills class property (gray "T" icon)
call_api("logseq.Editor.upsertBlockProperty",
[page_uuid, ":user.property/call-id-pS4tAfbV", "12345"])
# ❌ WRONG - creates plugin property (white "☆" icon)
call_api("logseq.Editor.upsertBlockProperty",
[page_uuid, "call-id", "12345"])
Setting Page Tags
# ❌ WRONG - adds "#tag" as TEXT, not actual tag
insert_block(page_uuid, "#claude_task")
# ✅ CORRECT - sets actual page tag
call_api("logseq.Editor.upsertBlockProperty",
[page_uuid, "block/tags", [141, 2569]]) # 2569 = claude_task ID
6. Library Hierarchy
View Hierarchy Tree
lsq get_hierarchy "Library"
Output:
Library
├─ General knowledge and research
├─ Improvado Root nodes
│ └─ Customers
│ └─ hp.com
└─ Miras Knowledge Platform
├─ Miras Agent Integration
└─ Miras Platform Core
Move Page to Parent
from data_sources.logseq.ops import get_page
from data_sources.logseq.protocol import call_api
page = get_page('page name')
parent = get_page('target parent') # Use internal name!
call_api('logseq.Editor.upsertBlockProperty',
[page['uuid'], 'block/parent', parent['id']])
7. Connection Config
| Setting | Value |
|---------|-------|
| Port | 12315 |
| Token | ~/Library/.../Logseq/configs.edn (auto) or ~/.logseq_token |
| HTTP API | http://127.0.0.1:12315/api |
8. Safe Deletion (CRITICAL)
⚠️ NEVER delete pages without user confirmation!
from data_sources.logseq.ops import get_node, format_node, delete_page
# 1. Show what will be deleted
node = get_node("page name")
print(format_node(node))
# 2. Wait for user confirmation
# ... user says "yes" ...
# 3. Only then delete
delete_page("page name")
Troubleshooting
| Error | Solution |
|-------|----------|
| Token not found | Copy from Logseq Settings → Advanced → API Server |
| Connection refused | Start Logseq, enable API server in settings |
| Page not found | Use internal name (jira_ticket), not display title |
| [[]] not working | Quote the argument: lsq get_node "[[Page]]" |
| Duplicate page created | Fixed in 2026-01-02: append_block() now auto-resolves display title → internal name. Old bug: if internal name differs from display title, API creates new page instead of appending. |
| Content not rendering | Don't mix list items in one block! Logseq shows warning "Full content is not displayed". Each - item must be separate append_block() call. Code blocks (```) are OK as single block. |
Related Files
| File | Purpose |
|------|---------|
| data_sources/logseq/ops/ | All operations (31 CLI commands) |
| data_sources/logseq/protocol.py | HTTP API calls |
| data_sources/logseq/bin/lsq | Shell wrapper |
| data_sources/logseq/tests/test_all.py | Test suite |
Created: 2025-12-20
Updated: 2026-01-02 — Fixed page duplication bug: append_block() and insert_block() now auto-resolve display titles to internal names