This is a plugin to add CL(A)I to Obsidian. CL(A)I is an LLM helper to quickly run workflows to generate or modify content. This is useful for automating things like creating notes for TTRPGs and other repetitive use cases (summarise, convert to bullet points, spell & grammar check, and more). CL(A)I can serve as a LLM macro toolbox in Obsidian.
Important
Note
In the above example the left workflow results in the right result when run. The template syntax essentially tells the template to insert the setting
note where the basic information about the setting is stored. Then the notes race
, background
and cities
contain said content as list. The Template will take random lines from them before sending it to the LLM. For example {{ call .SampleLines "./races.md" 1 }}
means to take 1 random line from the races.md note and put it there
.
With this you can build really creative generator workflows!
When the plugin is installed, you need to go to the settings:
- Press the button to install / update CL(A)I; this will fetch the latest version of CL(A)I from GitHub.
- Select your API provider (OpenAI, OpenRouter, or Custom).
- Enter your API key.
- Select the model you want to use.
- After installing the plugin, you can use the
Run Workflow
command to execute a workflow. This allows you to select the desired workflow, and the result will be inserted at the current cursor position or replace the current selection. - For testing or debugging purposes, use the
Run Workflow (Dry)
command. This inserts the content that would be sent to the API at the current cursor position without actually executing the workflow. - To quickly set up a new workflow, use the
Insert Boilerplate
command. This will insert the basic structure of a workflow at the current cursor position. - To simplify the process of creating a workflow, use the
Insert Template Statement
command. This inserts a template statement at the current cursor position, making it easier to build your workflow.
The plugin supports various template tags that can be inserted using the Insert Template Statement
command:
{{ .Input }}
: Prompts the user for input when the workflow is run. The entered text will be inserted at this position.{{ .AskForNote }}
: Opens a file selector to choose a note from your vault. The content of the selected note will be inserted at this position.
{{ call .File "./path/to/file.md" }}
: Inserts the entire contents of the specified file{{ call .SampleFiles "./folder/" n meta }}
: Insertsn
random files from the specified folder. Ifmeta
is true, the filename will be passed to the LLM as metadata{{ call .SampleFilesDeep "./folder/" n meta }}
: Same as SampleFiles but includes files from subfolders{{ call .SampleLines "./file.md" n }}
: Insertsn
random lines from the specified file{{ call .SampleChunk "./file.md" n }}
: Inserts a random chunk ofn
consecutive lines from the specified file
{{ .Clipboard }}
: Inserts the current contents of your clipboard{{ .ActiveTitle }}
: Inserts the title of the currently active note{{ .ActiveNote }}
: Inserts the entire contents of the currently active note{{ .Selection }}
: Inserts the currently selected text in the editor
You can easily insert any of these tags using the Insert Template Statement
command, which provides an interactive interface with:
- A fuzzy-searchable list of all available template types with explanations
- File/folder picker for paths when needed
- Number selector for quantities
- Yes/No prompts for metadata inclusion
A very simple example of what you can do with CL(A)I is to refactor the selected text based on user input.
You can create a Refactor
note in the clai-workflows
folder and input:
---
name: "📙 Refactor"
description: "Refactor based on input"
---
# CLAI::SYSTEM
You are a helpful assistant that refactors text.
Only output the refactored text, nothing else.
Your goal is: {{ .Input }}
# CLAI::USER
{{ .Selection }}
Let's break down what the workflow does:
- The frontmatter defines the name and description of the workflow that will be shown in the selection list.
- The
# CLAI::SYSTEM
defines the system prompt for this workflow.- When
{{ .Input }}
is present running the workflow will open a text input dialog. The result will be inserted at this position.
- When
- The following
# CLAI::USER
defines a user message:- The
{{ .Selection }}
inserts the current selection from the user.
- The
Let's walk you through an example of what I use CL(A)I for to understand how it works.
- Imagine you have a
monsters
folder in your vault. - In the
monsters
folder, you have all kinds of monsters for your TTRPG as separate notes. - Additionally, you have a
setting.md
file where a quick description of the setting is stored. - You want to generate a random monster.
Now you can create a NewMonster
note in the clai-workflows
folder and input:
---
name: "🐊 Generate a random Monster"
description: "Generates a random monster based on selection text"
---
# CLAI::SYSTEM
You are a helpful assistant that generates new monsters for a tabletop roleplaying game.
You will generate a new monster based on the given input.
# CLAI::USER
This is the setting:
{{ call .File "./setting.md" }}
Here are some examples of monsters:
{{ call .SampleFiles "./monsters/" 3 false }}
# CLAI::ASSISTANT
Thank you for the examples! Now tell me about the monster you want to generate.
# CLAI::USER
{{ .Selection }}
Now you can run the workflow by pressing the Run Workflow
command and selecting NewMonster
.
Let's break down what the workflow does:
- The frontmatter defines the name and description of the workflow that will be shown in the selection list.
- The
# CLAI::SYSTEM
defines the system prompt for this workflow. - The following
# CLAI::USER
defines a user message:- The
{{ call .File "./setting.md" }}
inserts the content of thesetting.md
file. - The
{{ call .SampleFiles "./monsters/" 3 false }}
inserts 3 random files from themonsters
folder used as examples. Thefalse
indicates that the file names should not be included.
- The
- The
# CLAI::ASSISTANT
defines the assistant message. - The
{{ .Selection }}
inserts the current selection from the user.
For more information about the call
functions, see the CL(A)I documentation.
I didn't know about the awesome Text Generator initially because CL(A)I
started without Obsidian in mind, only after building it I realized that I wanted the functionality of it inside of Obsidian too. So to make it quick:
CL(A)I
is also a standalone CLI tool that can be used without Obsidian, whileText Generator
is a plugin for Obsidian and tightly integrated with it.CL(A)I
focuses more on thetake random notes from folder
,take random lines from note
,insert note X here
use cases to build a interesting base for the LLMs. I thinkText Generator
doesn't cover this in the same way.CL(A)I
supports dynamic variables like{{ .Input }}
and{{ .AskForNote }}
that prompt for additional input, when running the workflow.Text Generator
has a template hub where you can share your own templates, whileCL(A)I
doesn'tCL(A)I
has a different syntax based on go's html/templateCL(A)I
lets you define system, user and assistant messages however you want on per template basis. I thinkText Generator
doesn't let you do this.- Both support a wide range of models
Text Generator
is around longer, has more diverse features and more mature thanCL(A)I