Agent Skills: Logseq Skill

Operate Logseq workspace by reading and writing pages, searching blocks, and running graph analytics. Use when user says "add to Logseq", "create Logseq page", "search Logseq", "логсек", or "/logseq".

UncategorizedID: tekliner/improvado-agentic-frameworks-and-skills/logseq

Install this agent skill to your local

pnpm dlx add-skill https://github.com/tekliner/improvado-agentic-frameworks-and-skills/tree/HEAD/skills/logseq

Skill Files

Browse the full folder contents for logseq.

Download Skill

Loading file tree…

skills/logseq/SKILL.md

Skill Metadata

Name
logseq
Description
Operate Logseq workspace by reading and writing pages, searching blocks, and running graph analytics. Use when user says "add to Logseq", "create Logseq page", "search Logseq", "логсек", or "/logseq".

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