Ordo commited on
Commit ·
0bb82b0
0
Parent(s):
Initial public release
Browse files- .env.example +1 -0
- .gitignore +6 -0
- LICENSE +21 -0
- README.md +17 -0
- SECURITY.md +3 -0
- index.mjs +67 -0
- openclaw.plugin.json +10 -0
- package.json +15 -0
.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
OBSIDIAN_BIN=obsidian
|
.gitignore
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
.env.*
|
| 3 |
+
!.env.example
|
| 4 |
+
node_modules/
|
| 5 |
+
npm-debug.log*
|
| 6 |
+
*.log
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Patrick
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenClaw Obsidian Bridge
|
| 2 |
+
|
| 3 |
+
Minimal OpenClaw plugin extension that exposes Obsidian status/open helpers through gateway methods and an authenticated HTTP route.
|
| 4 |
+
|
| 5 |
+
## Methods
|
| 6 |
+
|
| 7 |
+
- `obsidian.bridge.status`
|
| 8 |
+
- `obsidian.bridge.open`
|
| 9 |
+
- `POST /hooks/obsidian-bridge`
|
| 10 |
+
|
| 11 |
+
## Local Check
|
| 12 |
+
|
| 13 |
+
```bash
|
| 14 |
+
npm test
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
The example assumes an `obsidian` helper binary is available. Adapt `index.mjs` for your own host path or wrapper.
|
SECURITY.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Security
|
| 2 |
+
|
| 3 |
+
Do not expose this bridge without OpenClaw plugin authentication. Vault paths and file names can be sensitive; avoid logging request bodies in public deployments.
|
index.mjs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
|
| 2 |
+
import { execFile } from 'node:child_process';
|
| 3 |
+
import { promisify } from 'node:util';
|
| 4 |
+
|
| 5 |
+
const execFileAsync = promisify(execFile);
|
| 6 |
+
|
| 7 |
+
async function run(command, args = []) {
|
| 8 |
+
const { stdout, stderr } = await execFileAsync(command, args, { encoding: 'utf8' });
|
| 9 |
+
return { stdout, stderr };
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
async function obsidianStatus() {
|
| 13 |
+
const { stdout } = await run(process.env.OBSIDIAN_BIN || 'obsidian', ['status']);
|
| 14 |
+
return JSON.parse(stdout);
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
async function obsidianOpen(vaultPath) {
|
| 18 |
+
const args = ['open'];
|
| 19 |
+
if (vaultPath) args.push(`path=${vaultPath}`);
|
| 20 |
+
const { stdout } = await run(process.env.OBSIDIAN_BIN || 'obsidian', args);
|
| 21 |
+
return stdout ? JSON.parse(stdout) : { launched: true };
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
export default definePluginEntry({
|
| 25 |
+
id: 'obsidian-bridge',
|
| 26 |
+
name: 'Obsidian Bridge',
|
| 27 |
+
description: 'Minimal OpenClaw-owned Obsidian bridge for Paperclip and local callers.',
|
| 28 |
+
register(api) {
|
| 29 |
+
api.registerGatewayMethod('obsidian.bridge.status', async ({ respond }) => {
|
| 30 |
+
try {
|
| 31 |
+
respond({ ok: true, status: await obsidianStatus() });
|
| 32 |
+
} catch (error) {
|
| 33 |
+
respond({ ok: false, error: String(error?.message || error) });
|
| 34 |
+
}
|
| 35 |
+
});
|
| 36 |
+
api.registerGatewayMethod('obsidian.bridge.open', async ({ params, respond }) => {
|
| 37 |
+
try {
|
| 38 |
+
respond({ ok: true, result: await obsidianOpen(params?.path || null) });
|
| 39 |
+
} catch (error) {
|
| 40 |
+
respond({ ok: false, error: String(error?.message || error) });
|
| 41 |
+
}
|
| 42 |
+
});
|
| 43 |
+
api.registerHttpRoute({
|
| 44 |
+
path: '/hooks/obsidian-bridge',
|
| 45 |
+
auth: 'plugin',
|
| 46 |
+
match: 'exact',
|
| 47 |
+
replaceExisting: true,
|
| 48 |
+
async handler(req, res) {
|
| 49 |
+
try {
|
| 50 |
+
const body = req.body && typeof req.body === 'object' ? req.body : {};
|
| 51 |
+
const action = body.action || 'status';
|
| 52 |
+
if (action === 'status') {
|
| 53 |
+
res.status(200).json({ ok: true, status: await obsidianStatus() });
|
| 54 |
+
return;
|
| 55 |
+
}
|
| 56 |
+
if (action === 'open') {
|
| 57 |
+
res.status(200).json({ ok: true, result: await obsidianOpen(body.path || null) });
|
| 58 |
+
return;
|
| 59 |
+
}
|
| 60 |
+
res.status(400).json({ ok: false, error: 'unsupported action' });
|
| 61 |
+
} catch (error) {
|
| 62 |
+
res.status(500).json({ ok: false, error: String(error?.message || error) });
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
});
|
| 66 |
+
}
|
| 67 |
+
});
|
openclaw.plugin.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "obsidian-bridge",
|
| 3 |
+
"name": "Obsidian Bridge",
|
| 4 |
+
"description": "Minimal OpenClaw-owned Obsidian bridge for Paperclip and local callers.",
|
| 5 |
+
"configSchema": {
|
| 6 |
+
"type": "object",
|
| 7 |
+
"additionalProperties": false,
|
| 8 |
+
"properties": {}
|
| 9 |
+
}
|
| 10 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "obsidian-bridge",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"type": "module",
|
| 5 |
+
"description": "Minimal OpenClaw-owned Obsidian bridge for Paperclip and local callers.",
|
| 6 |
+
"private": false,
|
| 7 |
+
"scripts": {
|
| 8 |
+
"test": "node --check index.mjs"
|
| 9 |
+
},
|
| 10 |
+
"openclaw": {
|
| 11 |
+
"extensions": [
|
| 12 |
+
"./index.mjs"
|
| 13 |
+
]
|
| 14 |
+
}
|
| 15 |
+
}
|