Agent skill

Obsidian Actions

by aidenlx in aidenlx/zotlit

Patterns for registering commands (action modules) and building context menus (menu segments) in the Obsidian plugin.

AGPL-3.0Auto-check passed

Install Obsidian Actions

skills CLI
$ npx skills add aidenlx/zotlit --skill obsidian-actions -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install aidenlx/zotlit obsidian-actions --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/aidenlx/zotlit.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/obsidian-actions .claude/skills/obsidian-actions && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
obsidian-actions
GitHub stars
1k
Token cost
~2.6k tokens
SKILL.md length
664 words
Files
1
Skills in repo
16
Repo updated
First seen
Licence
AGPL-3.0

At a glance

Patterns for registering commands (action modules) and building context menus (menu segments) in the Obsidian plugin.

  • Adding commands
  • SKILL.md covers Action Modules, Menu Segments, Relationship Between Actions… and Shared Context Resolution, plus 1 more section
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md
  • Context menu logic

What it does

Obsidian Actions is an agent skill from aidenlx/zotlit. Patterns for registering commands (action modules) and building context menus (menu segments) in the Obsidian plugin. Use when adding commands, menu items, context menu logic, or wiring action/menu code in apps/obsidian/src/services/. Also use when a service needs to expose functionality to the user via the command palette or right-click menus.

Its SKILL.md is about 2.6k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It works with Obsidian. The repository describes itself as: Bring your Zotero library into Obsidian. Create literature notes, insert citations, and annotate PDFs without leaving your vault. The licence is AGPL-3.0.

When your agent uses it

  • Adding commands
  • Context menu logic
  • Wiring action/menu code in apps/obsidian/src/services/
  • A service needs to expose functionality to the user via the command palette

Example prompts

  • “Use the obsidian-actions skill to pattern for registering commands (action modules) and building context menus (menu segments) in the Obsidian plugin”
  • “/obsidian-actions”

What it can do on your machine

Read from SKILL.md and the folder at commit 27f5752. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are typescript).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Obsidian Actions loads about 2.6k tokens when it runs. Until then it costs about 91 tokens; SKILL.md has 664 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~91
When it runs · the whole SKILL.md, loaded when a task matches
~2.6k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from aidenlx/zotlit at commit 27f5752, republished under its AGPL-3.0 licence (© aidenlx). 664 words, ~2,583 tokens.

Download SKILL.mdSave it as .claude/skills/obsidian-actions/SKILL.md (or your agent's skills folder).
name
obsidian-actions
description
Patterns for registering commands (action modules) and building context menus (menu segments) in the Obsidian plugin. Use when adding commands, menu items, context menu logic, or wiring action/menu code in apps/obsidian/src/services/. Also use when a service needs to expose functionality to the user via the command palette or right-click menus.

Action Modules & Menu Segments

Two separate concerns for exposing service functionality to users:

  • Action modules — register commands (keyboard / command palette)
  • Menu segments — build context menu items (right-click, pane menus)

Both close over service deps. Both colocate with their feature domain. For the underlying service architecture, see the obsidian-services skill.

Action Modules

Each domain owns its action module. No centralized ActionService. Each feature exports an add*Actions(plugin, deps) function as the entry point convention. Internally, action modules are free to organize however makes sense: define command descriptors, register disposables via plugin.register(...), set up repeat-key handlers, etc. The convention is the entry point shape, not the implementation.

File placement

Colocate with the service: services/<domain>/actions.ts

Shape
ts
// services/database/actions.ts
export function addDatabaseActions(
  plugin: ZotLitPlugin,
  deps: { db: DatabaseService },
) {
  plugin.addCommand({
    id: "zotlit:refresh-db",
    name: "Refresh Zotero database",
    callback: async () => {
      try {
        await deps.db.ready;
        await deps.db.refresh();
      } catch {
        new Notice("Database is not available");
      }
    },
  });
}
Graceful degradation

Command handlers check service.ready at invocation time. If the backing service failed to init, the command shows a notice rather than crashing:

ts
try {
  await deps.db.ready;
  // ... do work
} catch {
  new Notice("Database is not available");
}

onError (in ServiceContainer) handles detailed error reporting. Consumers only need to know whether the service is available.

editorCheckCallback pattern

For commands that only apply in certain editor contexts, use editorCheckCallback. The checking parameter separates visibility from execution:

ts
plugin.addCommand({
  id: "zotlit:update-literature-note",
  name: "Update literature note",
  editorCheckCallback(checking, _editor, ctx) {
    if (!ctx.file || !isLiteratureNote(ctx.file, plugin.app)) return false;
    if (checking) return true;
    void (async () => {
      try {
        await deps.db.ready;
        const itemKey = getItemKeyOf(ctx.file!, plugin.app.metadataCache);
        if (!itemKey) {
          new Notice("Cannot get Zotero item key from file");
          return;
        }
        // ... update logic
      } catch {
        new Notice("Database is not available");
      }
    })();
  },
});
Wiring in onload()

Action modules are called unconditionally in onload() after buildServices:

ts
addDatabaseActions(this, { db: services.db });
addNoteActions(this, { db: services.db, noteIndex: services.noteIndex });
addCitationActions(this, { db: services.db, settings: services.settings });

Menu Segments

No wrapper around Menu. Obsidian's imperative-declarative API (.addItem(i => i.setTitle(...).onClick(...))) is already clean enough. The only abstraction is a shared function signature and a unified context type.

MenuSegment type

A segment is a function that may add zero or more items to a menu based on context. No class, no interface beyond this. Returns true if it rendered any items, false otherwise — composites use this to skip separators or avoid empty submenu wrappers:

ts
type MenuSegment = (menu: Menu, ctx: ItemMenuContext) => boolean;
ItemMenuContext — unified menu context

Different menu events provide different raw data. ItemMenuContext normalizes them into a discriminated union separating event kind from menu source:

ts
type PaneMenuSource = 'more-options' | 'tab-header' | 'sidebar-context-menu';

type ItemMenuContext = {
  file: TFile | undefined;
  itemKey: string | undefined;
  isLitNote: boolean;
} & (
  | { kind: 'editor'; source: 'editor' }
  | { kind: 'file'; source: string }
  | { kind: 'pane'; source: PaneMenuSource }
);

kind distinguishes event origin for routing; only routing logic should inspect it. source carries the Obsidian-provided source string (especially useful for pane menus). Domain fields (file, itemKey, isLitNote) are resolved once — feature segments inspect these, never raw event args.

resolveItemContext

Converts raw Obsidian event args into ItemMenuContext:

ts
function resolveItemContext(
  app: App,
  ctx:
    | { kind: 'editor'; file: TFile | null | undefined }
    | { kind: 'file'; file: TAbstractFile; source: string }
    | { kind: 'pane'; source: PaneMenuSource; file: TFile | null | undefined },
): ItemMenuContext {
  const file = ctx.file instanceof TFile ? ctx.file : undefined;
  const base = {
    file,
    itemKey: file ? getItemKeyOf(file, app.metadataCache) : undefined,
    isLitNote: !!file && isLiteratureNote(file, app),
  };
  switch (ctx.kind) {
    case 'editor': return { ...base, kind: 'editor', source: 'editor' };
    case 'file':   return { ...base, kind: 'file', source: ctx.source };
    case 'pane':   return { ...base, kind: 'pane', source: ctx.source };
  }
}

To add a new context dimension (e.g., editor selection state), extend the relevant union branch — segments gain access automatically.

Writing a segment

Each feature exports a factory that closes over deps and returns a MenuSegment. File placement: services/<domain>/menu.ts

ts
// services/note-index/menu.ts
import type { DatabaseService } from "../database/service";
import type { NoteIndexService } from "./service";

interface NoteMenuDeps {
  db: DatabaseService;
  noteIndex: NoteIndexService;
}

export function noteMenuSegment(deps: NoteMenuDeps): MenuSegment {
  return (menu, ctx) => {
    if (!ctx.itemKey) return false;

    menu.addItem((item) =>
      item
        .setSection("zotlit")
        .setTitle("Open in Zotero")
        .setIcon("external-link")
        .onClick(async () => {
          try {
            await deps.db.ready;
            // ...
          } catch {
            new Notice("Database is not available");
          }
        }),
    );

    if (ctx.source !== "tab-header") {
      menu.addItem((item) =>
        item
          .setSection("zotlit")
          .setTitle("Update literature note")
          .setIcon("refresh-cw")
          .onClick(async () => {
            try {
              await deps.db.ready;
              // ...
            } catch {
              new Notice("Database is not available");
            }
          }),
      );
    }
    return true;
  };
}

Visibility logic (the if checks) lives inside the segment — the feature decides what to show where. The segment returns false when irrelevant, so composites can react to empty contributions without pre-filtering.

Show full SKILL.md (265 more words)Show less
Rules

Menu construction is synchronous. Never await during segment execution. Obsidian builds menus in one tick. Only onClick handlers may be async.

Use setSection() with a consistent section key on all items so they cluster together regardless of insertion order.

Return the boolean. false when the segment adds nothing (e.g., no itemKey). Composites depend on this.

Visibility uses sync state only. Services may provide a synchronous readiness accessor (e.g., a getter or method) that reflects whether init has completed, failed, or is still pending — implementation is up to the service. If a service is still loading, disable the item with a placeholder title (e.g., "Loading…") using that synchronous state — don't await inside the segment body.

Composing segments

Segments are composed into a single callable. Ordering is explicit:

ts
// services/menu.ts
export function buildMenuSegments(services: Services): MenuSegment {
  const segments = [
    noteMenuSegment({ db: services.db, noteIndex: services.noteIndex }),
    citationMenuSegment({ db: services.db, settings: services.settings }),
    templateMenuSegment({ db: services.db, settings: services.settings }),
  ];
  return (menu, ctx) => {
    let rendered = false;
    for (const seg of segments) {
      if (seg(menu, ctx)) rendered = true;
    }
    return rendered;
  };
}
Wiring to Obsidian events

Registration happens in onload(). Custom workspace events and DOM-based menu hooks also live in onload() for now; extract to a dedicated service if the wiring grows complex.

ts
const zotlitMenu = buildMenuSegments(services);

this.registerEvent(
  this.app.workspace.on("editor-menu", (menu, _editor, info) => {
    zotlitMenu(menu, resolveItemContext(this.app, { kind: "editor", file: info.file }));
  }),
);

this.registerEvent(
  this.app.workspace.on("file-menu", (menu, file, source) => {
    zotlitMenu(menu, resolveItemContext(this.app, { kind: "file", file, source }));
  }),
);

For view onPaneMenu (not a workspace event — called by Obsidian on the view instance). The view receives the composite segment via deps (closure capture in registerView factory):

ts
onPaneMenu(menu: Menu, source: string) {
  super.onPaneMenu(menu, source);
  this.#zotlitMenu(menu, resolveItemContext(
    this.app, { kind: "pane", source: source as PaneMenuSource, file: this.file },
  ));
}

Relationship Between Actions and Menus

Action modules and menu segments are separate concerns that may share the same underlying operation. Menu items may call app.commands.executeCommandById() to reuse command logic, but direct invocation is also fine when the menu handler needs different args or flow.

Shared Context Resolution

Shared context resolution (e.g., "which item is the current note about?") lives in utility functions (getItemKeyOf, isLiteratureNote), not a service — it's stateless frontmatter lookup.

View Degradation

Views also check service.ready at open time:

ts
async onOpen() {
  try {
    await this.#db.ready;
    // render normally
  } catch {
    // render unavailable state
  }
}

© aidenlx, AGPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .agents/skills/obsidian-actions of aidenlx/zotlit.

Open the folder on GitHubat commit 27f5752

Compare with similar skills

Obsidian Actions next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Obsidian Actions compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Obsidian Actions this skillaidenlx/zotlit1k—~2.6kAutomated safety check: PassAGPL-3.0
Obsidian BasesAtmosphere/atmosphere3.8k22 repos~3.2kAutomated safety check: PassApache-2.0
Knap Markdown Templateskepano/obsidian-skills49k2 repos~986Automated safety check: PassMIT
JSON Canvasheyitsnoah/claudesidian2.6k18 repos~3.5kAutomated safety check: PassMIT
Obsidian MarkdownAtmosphere/atmosphere3.8k20 repos~1.3kAutomated safety check: PassApache-2.0
Obsidian CLIAtmosphere/atmosphere3.8k13 repos~795Automated safety check: PassApache-2.0

Similar skills

  • Obsidian Bases

    Atmosphere/atmosphere

    Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries.

    3.8k GitHub starsUsed in 22 repos~3.2k tokens
    Backend & APIsAuto-check passed
  • Knap Markdown Templates

    kepano/obsidian-skills

    Renders Markdown notes from Knap templates and JSON data on the command line, including notes built from Defuddle web page output.

    49k GitHub starsUsed in 2 repos~986 tokens
    Documents & OfficeAuto-check passed
  • JSON Canvas

    heyitsnoah/claudesidian

    Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections.

    2.6k GitHub starsUsed in 18 repos~3.5k tokens
    DevelopmentAuto-check passed
  • Obsidian Markdown

    Atmosphere/atmosphere

    Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax.

    3.8k GitHub starsUsed in 20 repos~1.3k tokens
    Documents & OfficeAuto-check passed
  • Obsidian CLI

    Atmosphere/atmosphere

    Interact with Obsidian vaults using the Obsidian CLI to read, create, search, and manage notes, tasks, properties, and more.

    3.8k GitHub starsUsed in 13 repos~795 tokens
    Knowledge ManagementAuto-check passed
  • Obsidian Canvas Creator

    axtonliu/axton-obsidian-visual-skills

    Create Obsidian Canvas files from text content, supporting both MindMap and freeform layouts.

    3.6k GitHub stars~1.6k tokensUpdated 3 mo ago
    Auto-check passed

More from aidenlx/zotlit

All 16 skills in this repo
  • I18n UI Text

    aidenlx/zotlit

    Obsidian house style for the wording of user-facing UI strings — command names, setting labels, button text, notices, modal copy.

    1k GitHub stars~2.8k tokensUpdated today
    Auto-check passed
  • Obsidian CSS

    aidenlx/zotlit

    Style Obsidian plugin UI with Tailwind + native components. An agent skill from aidenlx/zotlit.

    1k GitHub stars~4.8k tokensUpdated today
    Auto-check passed
  • Arkregex

    aidenlx/zotlit

    Typed regex authoring with arkregex in this repo. An agent skill from aidenlx/zotlit.

    1k GitHub stars~952 tokensUpdated today
    Auto-check passed
  • Changelog Entry

    aidenlx/zotlit

    Write a user-facing changelog entry under apps/docs/content/changelog/.

    1k GitHub stars~1.6k tokensUpdated today
    Auto-check passed
  • Discord Announcement

    aidenlx/zotlit

    Draft a Discord announcement from a changelog entry. An agent skill from aidenlx/zotlit.

    1k GitHub stars~604 tokensUpdated today
    Auto-check passed
  • Inlang I18n

    aidenlx/zotlit

    Define ZotLit UI messages in the Inlang Message Format and consume them through the generated JSON Language Pack facade.

    1k GitHub stars~1k tokensUpdated today
    Auto-check passed

Works with

Questions about Obsidian Actions

What does Obsidian Actions do?

Patterns for registering commands (action modules) and building context menus (menu segments) in the Obsidian plugin. Obsidian Actions is an agent skill from aidenlx/zotlit. Patterns for registering commands (action modules) and building context menus (menu segments) in the Obsidian plugin.

When should I use Obsidian Actions?

Obsidian Actions fits situations like: adding commands; context menu logic; wiring action/menu code in apps/obsidian/src/services/; A service needs to expose functionality to the user via the command palette.

How do I install Obsidian Actions in Claude Code?

Run `npx skills add aidenlx/zotlit --skill obsidian-actions -a claude-code`. Or copy the skill folder (.agents/skills/obsidian-actions in aidenlx/zotlit) into .claude/skills/obsidian-actions in your project. Claude Code loads it when a task matches its description.

How do I install Obsidian Actions in Codex?

Run `npx skills add aidenlx/zotlit --skill obsidian-actions -a codex`. Or copy the skill folder (.agents/skills/obsidian-actions in aidenlx/zotlit) into .agents/skills/obsidian-actions in your project. Codex loads it when a task matches its description.

Can I use Obsidian Actions in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add aidenlx/zotlit --skill obsidian-actions -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/obsidian-actions, .gemini/skills/obsidian-actions, .github/skills/obsidian-actions and .opencode/skills/obsidian-actions in your project.

What does Obsidian Actions need to run?

SKILL.md names no scripts, command-line tools or credentials: Obsidian Actions is instructions for the agent only.

Does Obsidian Actions access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Obsidian Actions safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Obsidian Actions use?

Obsidian Actions is published under the AGPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Obsidian Actions use?

About 2.6k tokens (SKILL.md is roughly 10k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Obsidian Actions?

Skills that share tags, products or a category with Obsidian Actions: Obsidian Bases (Atmosphere/atmosphere, 3.8k stars), Knap Markdown Templates (kepano/obsidian-skills, 49k stars), JSON Canvas (heyitsnoah/claudesidian, 2.6k stars) and Obsidian Markdown (Atmosphere/atmosphere, 3.8k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Obsidian Actions?

aidenlx (a GitHub user) maintains it in aidenlx/zotlit, which has 1,029 GitHub stars. The repository holds 16 skills in this directory. The repository was last updated on October 10, 2026.

Source: aidenlx/zotlit on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.