feat: add Obsidian CLI API service
Build and Push Docker Container / build-and-push (push) Successful in 4m26s
Build and Push Docker Container / build-and-push (push) Successful in 4m26s
- Add Docker image for the official Obsidian desktop app and CLI. - Start Obsidian headlessly with Xvfb and DBus setup. - Expose a safe structured HTTP command API without shell execution. - Add JWT-based vault, path, and command authorization. - Support single-vault and multi-vault container mounts. - Add TypeScript SDK helpers for Obsidian CLI commands. - Add n8n community node package with Obsidian operations. - Add docs, compose config, tests, and production image workflow.
This commit is contained in:
+133
@@ -0,0 +1,133 @@
|
||||
# n8n usage notes
|
||||
Use the TypeScript SDK from an n8n **Code** node when n8n can import local or bundled npm packages. In self-hosted n8n this usually means installing the SDK where n8n can resolve it and allowing external modules, for example with `NODE_FUNCTION_ALLOW_EXTERNAL=@obsidian-api/sdk` or `NODE_FUNCTION_ALLOW_EXTERNAL=*`.
|
||||
|
||||
The SDK only sends structured `args` arrays to the HTTP API. It never sends shell command strings.
|
||||
|
||||
## Environment variables
|
||||
Recommended n8n env vars:
|
||||
|
||||
```env
|
||||
OBSIDIAN_API_URL=http://obsidian-api:8080
|
||||
OBSIDIAN_API_JWT=<jwt-from-scripts/create_jwt.py>
|
||||
OBSIDIAN_VAULT=work
|
||||
```
|
||||
|
||||
Recommended: use a restricted JWT for n8n. Create it with:
|
||||
```bash
|
||||
OBSIDIAN_JWT_SECRET=change-this-long-random-secret \
|
||||
python scripts/create_jwt.py \
|
||||
--sub n8n \
|
||||
--vault work \
|
||||
--path Inbox/n8n/ \
|
||||
--path Automation.md \
|
||||
--command create \
|
||||
--command read \
|
||||
--command append \
|
||||
--command search \
|
||||
--command search:context \
|
||||
--days 365
|
||||
```
|
||||
|
||||
The JWT contains claims like:
|
||||
```json
|
||||
{
|
||||
"sub": "n8n",
|
||||
"vaults": ["work"],
|
||||
"paths": ["Inbox/n8n/", "Automation.md"],
|
||||
"commands": ["create", "read", "append", "search", "search:context"],
|
||||
"exp": 1791536000
|
||||
}
|
||||
```
|
||||
|
||||
If n8n runs outside the Docker network, use the exposed host URL instead:
|
||||
|
||||
```env
|
||||
OBSIDIAN_API_URL=http://localhost:8080
|
||||
```
|
||||
|
||||
## Basic Code node
|
||||
```ts
|
||||
import { ObsidianCliClient } from '@obsidian-api/sdk';
|
||||
|
||||
const obsidian = new ObsidianCliClient({
|
||||
baseUrl: process.env.OBSIDIAN_API_URL!,
|
||||
token: process.env.OBSIDIAN_API_JWT!,
|
||||
vault: process.env.OBSIDIAN_VAULT,
|
||||
});
|
||||
|
||||
const title = $json.title ?? 'n8n note';
|
||||
const content = `# ${title}\n\nCreated from n8n.\n`;
|
||||
|
||||
const result = await obsidian.create({
|
||||
path: `Inbox/${title}.md`,
|
||||
content,
|
||||
overwrite: true,
|
||||
});
|
||||
|
||||
return [{ json: result }];
|
||||
```
|
||||
|
||||
## Append incoming webhook payload to a daily note
|
||||
```ts
|
||||
import { ObsidianCliClient } from '@obsidian-api/sdk';
|
||||
|
||||
const obsidian = new ObsidianCliClient({
|
||||
baseUrl: process.env.OBSIDIAN_API_URL!,
|
||||
token: process.env.OBSIDIAN_API_JWT!,
|
||||
vault: process.env.OBSIDIAN_VAULT,
|
||||
});
|
||||
|
||||
const payload = JSON.stringify($json, null, 2).replace(/```/g, '``\\`');
|
||||
|
||||
const result = await obsidian.dailyAppend(
|
||||
`\n## n8n event\n\n\`\`\`json\n${payload}\n\`\`\`\n`,
|
||||
);
|
||||
|
||||
return [{ json: result }];
|
||||
```
|
||||
|
||||
## Search notes
|
||||
```ts
|
||||
import { ObsidianCliClient } from '@obsidian-api/sdk';
|
||||
|
||||
const obsidian = new ObsidianCliClient({
|
||||
baseUrl: process.env.OBSIDIAN_API_URL!,
|
||||
token: process.env.OBSIDIAN_API_JWT!,
|
||||
vault: process.env.OBSIDIAN_VAULT,
|
||||
});
|
||||
|
||||
const result = await obsidian.search($json.query, { format: 'json' });
|
||||
return [{ json: { matches: JSON.parse(result.stdout) } }];
|
||||
```
|
||||
|
||||
## Multi-vault usage
|
||||
If the API container registers multiple vaults, use a scoped client per vault:
|
||||
```ts
|
||||
const base = new ObsidianCliClient({
|
||||
baseUrl: process.env.OBSIDIAN_API_URL!,
|
||||
token: process.env.OBSIDIAN_API_JWT!,
|
||||
});
|
||||
|
||||
const work = base.inVault('work');
|
||||
const personal = base.inVault('personal');
|
||||
|
||||
await work.create({ path: 'Inbox/Work.md', content: 'Work note', overwrite: true });
|
||||
await personal.create({ path: 'Inbox/Personal.md', content: 'Personal note', overwrite: true });
|
||||
```
|
||||
|
||||
## Without SDK
|
||||
If package imports are not available in your n8n environment, call the HTTP API directly:
|
||||
```ts
|
||||
const response = await fetch(`${process.env.OBSIDIAN_API_URL}/commands`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
authorization: `Bearer ${process.env.OBSIDIAN_API_JWT}`,
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
args: ['vault=work', 'create', 'path=Inbox/n8n.md', 'content=Hello from n8n', 'overwrite'],
|
||||
}),
|
||||
});
|
||||
|
||||
return [{ json: await response.json() }];
|
||||
```
|
||||
Reference in New Issue
Block a user