feat: init
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# deps
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# build output (regenerated by `make install` / `make zip`)
|
||||||
|
main.js
|
||||||
|
*.zip
|
||||||
|
|
||||||
|
# plugin runtime — Obsidian writes settings here when the symlinked dir
|
||||||
|
# is used as the live plugin; never want it in the repo
|
||||||
|
data.json
|
||||||
|
|
||||||
|
# misc
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
PLUGIN_ID := beaver
|
||||||
|
ASSETS := manifest.json main.js versions.json
|
||||||
|
PM := $(shell command -v pnpm 2>/dev/null || command -v npm 2>/dev/null)
|
||||||
|
|
||||||
|
.PHONY: install zip _build
|
||||||
|
|
||||||
|
_build:
|
||||||
|
@test -n "$(PM)" || (echo "no pnpm or npm in PATH" >&2; exit 1)
|
||||||
|
$(PM) install --silent
|
||||||
|
$(PM) run build
|
||||||
|
|
||||||
|
install: _build
|
||||||
|
@test -n "$(VAULT)" || (echo "set VAULT=<path-to-vault>" >&2; exit 1)
|
||||||
|
@test -d "$(VAULT)/.obsidian" || (echo "no .obsidian dir at $(VAULT)" >&2; exit 1)
|
||||||
|
@target="$(VAULT)/.obsidian/plugins/$(PLUGIN_ID)"; \
|
||||||
|
mkdir -p "$$target"; \
|
||||||
|
cp $(ASSETS) "$$target/"; \
|
||||||
|
echo "installed → $$target"
|
||||||
|
|
||||||
|
zip: _build
|
||||||
|
@version=$$(node -p "require('./manifest.json').version"); \
|
||||||
|
out="beaver-plugin-$$version.zip"; \
|
||||||
|
rm -f "$$out"; \
|
||||||
|
zip -q -j "$$out" $(ASSETS); \
|
||||||
|
echo "wrote $$out"
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Beaver — Obsidian plugin
|
||||||
|
|
||||||
|
[](https://sladge.net)
|
||||||
|
|
||||||
|
Sends the current note to Beaver's `markdown frontend` (`POST /chat` on `beaver-gateway`) and writes the agent's reply back into the file. Skips the Obsidian Sync round-trip by POSTing the buffer contents directly.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
Both commands appear in the command palette only when the active note's YAML frontmatter has an `agent:` field:
|
||||||
|
|
||||||
|
- **Beaver: Send using selected agent** — uses `frontmatter.agent`.
|
||||||
|
- **Beaver: Send using different agent** — fetches the agent list from the gateway and opens a fuzzy picker.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
- **Base URL** — markdown-frontend root, e.g. `http://localhost:62993`.
|
||||||
|
- **Bearer token** — a token with the `messages` scope (mint via the admin frontend or `BOOTSTRAP_TOKENS`).
|
||||||
|
- **Test connection** — calls `GET /agents`.
|
||||||
|
|
||||||
|
## Note format
|
||||||
|
|
||||||
|
A minimal note that the plugin can send:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
agent: beaver-opus-medium
|
||||||
|
---
|
||||||
|
|
||||||
|
hi there
|
||||||
|
```
|
||||||
|
|
||||||
|
The gateway parses `### User:` / `### Assistant:` markers; a note with no markers is treated as a single user turn. After a successful send, the gateway returns the new file content (including an `### Assistant:` block and a fresh `### User:` scaffold), and the plugin writes it back to the buffer.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```
|
||||||
|
make install VAULT=~/Obsidian/my-vault # build + copy manifest.json + main.js into <vault>/.obsidian/plugins/beaver
|
||||||
|
make zip # build → beaver-plugin-<version>.zip you can carry to any vault
|
||||||
|
```
|
||||||
|
|
||||||
|
Then enable **Beaver** in Settings → Community plugins.
|
||||||
|
|
||||||
|
### Mobile
|
||||||
|
|
||||||
|
The plugin is desktop-and-mobile (`isDesktopOnly: false`). Two things to get right on a phone:
|
||||||
|
|
||||||
|
- The base URL in plugin settings has to be reachable from the phone — `http://localhost:62993` won't work; use the gateway's LAN IP, a tunnel, or a public hostname.
|
||||||
|
- Get the plugin files onto the phone vault via Obsidian Sync (toggle "Installed community plugins" in your sync settings) or any file-sync tool you already use (Working Copy, Syncthing, etc.). `make install` only knows how to write to a local path.
|
||||||
|
|
||||||
|
### Hand-rolled
|
||||||
|
|
||||||
|
Three files are all Obsidian needs — `manifest.json`, `main.js`, `versions.json`. Drop them in `<vault>/.obsidian/plugins/beaver/` however you like (unzip, `scp`, `rsync`, your own git, …).
|
||||||
|
|
||||||
|
## Bumping the version
|
||||||
|
|
||||||
|
Edit the version field in `manifest.json`, `package.json`, and `versions.json` by hand. Three files, one change each.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import esbuild from "esbuild";
|
||||||
|
import process from "node:process";
|
||||||
|
import builtins from "builtin-modules";
|
||||||
|
|
||||||
|
const prod = process.argv[2] === "production";
|
||||||
|
|
||||||
|
const ctx = await esbuild.context({
|
||||||
|
entryPoints: ["src/main.ts"],
|
||||||
|
bundle: true,
|
||||||
|
external: [
|
||||||
|
"obsidian",
|
||||||
|
"electron",
|
||||||
|
"@codemirror/autocomplete",
|
||||||
|
"@codemirror/collab",
|
||||||
|
"@codemirror/commands",
|
||||||
|
"@codemirror/language",
|
||||||
|
"@codemirror/lint",
|
||||||
|
"@codemirror/search",
|
||||||
|
"@codemirror/state",
|
||||||
|
"@codemirror/view",
|
||||||
|
"@lezer/common",
|
||||||
|
"@lezer/highlight",
|
||||||
|
"@lezer/lr",
|
||||||
|
...builtins,
|
||||||
|
],
|
||||||
|
format: "cjs",
|
||||||
|
target: "es2022",
|
||||||
|
logLevel: "info",
|
||||||
|
sourcemap: prod ? false : "inline",
|
||||||
|
treeShaking: true,
|
||||||
|
outfile: "main.js",
|
||||||
|
minify: prod,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (prod) {
|
||||||
|
await ctx.rebuild();
|
||||||
|
await ctx.dispose();
|
||||||
|
} else {
|
||||||
|
await ctx.watch();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"id": "beaver",
|
||||||
|
"name": "Beaver",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"minAppVersion": "1.5.0",
|
||||||
|
"description": "Send the current note to a Beaver agent via the markdown frontend.",
|
||||||
|
"author": "Beaver",
|
||||||
|
"isDesktopOnly": false
|
||||||
|
}
|
||||||
Generated
+602
@@ -0,0 +1,602 @@
|
|||||||
|
{
|
||||||
|
"name": "beaver-plugin-obsidian",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "beaver-plugin-obsidian",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"builtin-modules": "^4.0.0",
|
||||||
|
"esbuild": "^0.21.5",
|
||||||
|
"obsidian": "^1.5.7",
|
||||||
|
"typescript": "^5.4.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/state": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/view": {
|
||||||
|
"version": "6.38.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz",
|
||||||
|
"integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.5.0",
|
||||||
|
"crelt": "^1.0.6",
|
||||||
|
"style-mod": "^4.1.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/codemirror": {
|
||||||
|
"version": "5.60.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz",
|
||||||
|
"integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/tern": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/estree": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||||
|
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "20.19.41",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
|
||||||
|
"integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/tern": {
|
||||||
|
"version": "0.23.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
||||||
|
"integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/builtin-modules": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-p1n8zyCkt1BVrKNFymOHjcDSAl7oq/gUvfgULv2EblgpPVQlQr9yHnWjg9IJ2MhfwPqiYqMMrr01OY7yQoK2yA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/crelt": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.21.5",
|
||||||
|
"@esbuild/android-arm": "0.21.5",
|
||||||
|
"@esbuild/android-arm64": "0.21.5",
|
||||||
|
"@esbuild/android-x64": "0.21.5",
|
||||||
|
"@esbuild/darwin-arm64": "0.21.5",
|
||||||
|
"@esbuild/darwin-x64": "0.21.5",
|
||||||
|
"@esbuild/freebsd-arm64": "0.21.5",
|
||||||
|
"@esbuild/freebsd-x64": "0.21.5",
|
||||||
|
"@esbuild/linux-arm": "0.21.5",
|
||||||
|
"@esbuild/linux-arm64": "0.21.5",
|
||||||
|
"@esbuild/linux-ia32": "0.21.5",
|
||||||
|
"@esbuild/linux-loong64": "0.21.5",
|
||||||
|
"@esbuild/linux-mips64el": "0.21.5",
|
||||||
|
"@esbuild/linux-ppc64": "0.21.5",
|
||||||
|
"@esbuild/linux-riscv64": "0.21.5",
|
||||||
|
"@esbuild/linux-s390x": "0.21.5",
|
||||||
|
"@esbuild/linux-x64": "0.21.5",
|
||||||
|
"@esbuild/netbsd-x64": "0.21.5",
|
||||||
|
"@esbuild/openbsd-x64": "0.21.5",
|
||||||
|
"@esbuild/sunos-x64": "0.21.5",
|
||||||
|
"@esbuild/win32-arm64": "0.21.5",
|
||||||
|
"@esbuild/win32-ia32": "0.21.5",
|
||||||
|
"@esbuild/win32-x64": "0.21.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.29.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||||
|
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/obsidian": {
|
||||||
|
"version": "1.12.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz",
|
||||||
|
"integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/codemirror": "5.60.8",
|
||||||
|
"moment": "2.29.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@codemirror/state": "6.5.0",
|
||||||
|
"@codemirror/view": "6.38.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/style-mod": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/w3c-keyname": {
|
||||||
|
"version": "2.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "beaver-plugin-obsidian",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Obsidian plugin for Beaver's markdown frontend.",
|
||||||
|
"main": "main.js",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node esbuild.config.mjs",
|
||||||
|
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"builtin-modules": "^4.0.0",
|
||||||
|
"esbuild": "^0.21.5",
|
||||||
|
"obsidian": "^1.5.7",
|
||||||
|
"typescript": "^5.4.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { App, FuzzySuggestModal } from "obsidian";
|
||||||
|
|
||||||
|
export class AgentPickerModal extends FuzzySuggestModal<string> {
|
||||||
|
private agents: string[];
|
||||||
|
private resolve: (agent: string | null) => void;
|
||||||
|
private resolved = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
app: App,
|
||||||
|
agents: string[],
|
||||||
|
resolve: (agent: string | null) => void,
|
||||||
|
) {
|
||||||
|
super(app);
|
||||||
|
this.agents = agents;
|
||||||
|
this.resolve = resolve;
|
||||||
|
this.setPlaceholder("Pick a Beaver agent…");
|
||||||
|
}
|
||||||
|
|
||||||
|
getItems(): string[] {
|
||||||
|
return this.agents;
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemText(item: string): string {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
onChooseItem(item: string): void {
|
||||||
|
this.resolved = true;
|
||||||
|
this.resolve(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(): void {
|
||||||
|
super.onClose();
|
||||||
|
if (!this.resolved) this.resolve(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pickAgent(app: App, agents: string[]): Promise<string | null> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
new AgentPickerModal(app, agents, resolve).open();
|
||||||
|
});
|
||||||
|
}
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
import { requestUrl, RequestUrlParam } from "obsidian";
|
||||||
|
import type { BeaverSettings } from "./settings";
|
||||||
|
|
||||||
|
export interface ChatRequest {
|
||||||
|
filename: string;
|
||||||
|
content: string;
|
||||||
|
agent?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatResponse {
|
||||||
|
status: "ok" | "nothing_to_do" | "in_progress";
|
||||||
|
reason?: string;
|
||||||
|
agent?: string;
|
||||||
|
turns_appended?: number;
|
||||||
|
new_content?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BeaverApiError extends Error {
|
||||||
|
status: number;
|
||||||
|
body: unknown;
|
||||||
|
constructor(status: number, message: string, body: unknown) {
|
||||||
|
super(message);
|
||||||
|
this.status = status;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseUrl(settings: BeaverSettings): string {
|
||||||
|
const url = settings.baseUrl.trim().replace(/\/+$/, "");
|
||||||
|
if (!url) throw new Error("Beaver: base URL is not configured");
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function authHeader(settings: BeaverSettings): Record<string, string> {
|
||||||
|
const token = settings.token.trim();
|
||||||
|
if (!token) throw new Error("Beaver: bearer token is not configured");
|
||||||
|
return { Authorization: `Bearer ${token}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function call(
|
||||||
|
settings: BeaverSettings,
|
||||||
|
init: Omit<RequestUrlParam, "url"> & { path: string },
|
||||||
|
): Promise<unknown> {
|
||||||
|
const { path, ...rest } = init;
|
||||||
|
const url = `${baseUrl(settings)}${path}`;
|
||||||
|
const headers = {
|
||||||
|
...authHeader(settings),
|
||||||
|
...(rest.headers ?? {}),
|
||||||
|
};
|
||||||
|
// throw=false → we handle non-2xx ourselves so we can extract FastAPI's
|
||||||
|
// {detail: "..."} body and surface a useful message.
|
||||||
|
const res = await requestUrl({ url, throw: false, ...rest, headers });
|
||||||
|
if (res.status < 200 || res.status >= 300) {
|
||||||
|
let detail: unknown;
|
||||||
|
try {
|
||||||
|
detail = res.json;
|
||||||
|
} catch {
|
||||||
|
detail = res.text;
|
||||||
|
}
|
||||||
|
const detailMsg =
|
||||||
|
detail && typeof detail === "object" && "detail" in detail
|
||||||
|
? String((detail as { detail: unknown }).detail)
|
||||||
|
: typeof detail === "string"
|
||||||
|
? detail
|
||||||
|
: `HTTP ${res.status}`;
|
||||||
|
throw new BeaverApiError(res.status, detailMsg, detail);
|
||||||
|
}
|
||||||
|
return res.json;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listAgents(settings: BeaverSettings): Promise<string[]> {
|
||||||
|
const body = (await call(settings, { path: "/agents", method: "GET" })) as {
|
||||||
|
agents?: Array<{ name?: unknown }>;
|
||||||
|
};
|
||||||
|
const list = body.agents ?? [];
|
||||||
|
return list
|
||||||
|
.map((a) => (typeof a?.name === "string" ? a.name : null))
|
||||||
|
.filter((n): n is string => !!n);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendChat(
|
||||||
|
settings: BeaverSettings,
|
||||||
|
req: ChatRequest,
|
||||||
|
): Promise<ChatResponse> {
|
||||||
|
return (await call(settings, {
|
||||||
|
path: "/chat",
|
||||||
|
method: "POST",
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify(req),
|
||||||
|
})) as ChatResponse;
|
||||||
|
}
|
||||||
+194
@@ -0,0 +1,194 @@
|
|||||||
|
import {
|
||||||
|
Editor,
|
||||||
|
MarkdownView,
|
||||||
|
Notice,
|
||||||
|
Plugin,
|
||||||
|
TFile,
|
||||||
|
} from "obsidian";
|
||||||
|
import {
|
||||||
|
BeaverApiError,
|
||||||
|
ChatResponse,
|
||||||
|
listAgents,
|
||||||
|
sendChat,
|
||||||
|
} from "./api";
|
||||||
|
import { pickAgent } from "./agentPicker";
|
||||||
|
import {
|
||||||
|
BeaverSettings,
|
||||||
|
BeaverSettingsTab,
|
||||||
|
DEFAULT_SETTINGS,
|
||||||
|
} from "./settings";
|
||||||
|
|
||||||
|
const AGENT_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
export default class BeaverPlugin extends Plugin {
|
||||||
|
settings: BeaverSettings = { ...DEFAULT_SETTINGS };
|
||||||
|
|
||||||
|
private cachedAgents: string[] | null = null;
|
||||||
|
private cachedAt = 0;
|
||||||
|
private lastListFailed = false;
|
||||||
|
|
||||||
|
async onload(): Promise<void> {
|
||||||
|
await this.loadSettings();
|
||||||
|
this.addSettingTab(new BeaverSettingsTab(this.app, this));
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: "send-selected",
|
||||||
|
name: "Send using selected agent",
|
||||||
|
checkCallback: (checking) => {
|
||||||
|
const ctx = this.getFrontmatterAgentContext();
|
||||||
|
if (!ctx) return false;
|
||||||
|
if (!checking) void this.dispatch(ctx.file, ctx.agent);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: "send-different",
|
||||||
|
name: "Send using different agent",
|
||||||
|
checkCallback: (checking) => {
|
||||||
|
const ctx = this.getFrontmatterAgentContext();
|
||||||
|
if (!ctx) return false;
|
||||||
|
if (!checking) void this.runDifferent(ctx.file);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadSettings(): Promise<void> {
|
||||||
|
this.settings = Object.assign(
|
||||||
|
{},
|
||||||
|
DEFAULT_SETTINGS,
|
||||||
|
await this.loadData(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveSettings(): Promise<void> {
|
||||||
|
await this.saveData(this.settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheAgents(agents: string[]): void {
|
||||||
|
this.cachedAgents = agents;
|
||||||
|
this.cachedAt = Date.now();
|
||||||
|
this.lastListFailed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFrontmatterAgentContext():
|
||||||
|
| { file: TFile; agent: string }
|
||||||
|
| null {
|
||||||
|
const file = this.app.workspace.getActiveFile();
|
||||||
|
if (!file || file.extension !== "md") return null;
|
||||||
|
const cache = this.app.metadataCache.getFileCache(file);
|
||||||
|
const raw = cache?.frontmatter?.agent;
|
||||||
|
if (typeof raw !== "string" || !raw.trim()) return null;
|
||||||
|
return { file, agent: raw.trim() };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runDifferent(file: TFile): Promise<void> {
|
||||||
|
const agents = await this.getAgents();
|
||||||
|
if (!agents) return;
|
||||||
|
if (agents.length === 0) {
|
||||||
|
new Notice("Beaver: no agents available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const picked = await pickAgent(this.app, agents);
|
||||||
|
if (!picked) return;
|
||||||
|
await this.dispatch(file, picked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getAgents(): Promise<string[] | null> {
|
||||||
|
const fresh =
|
||||||
|
this.cachedAgents &&
|
||||||
|
!this.lastListFailed &&
|
||||||
|
Date.now() - this.cachedAt < AGENT_CACHE_TTL_MS;
|
||||||
|
if (fresh) return this.cachedAgents;
|
||||||
|
try {
|
||||||
|
const agents = await listAgents(this.settings);
|
||||||
|
this.cacheAgents(agents);
|
||||||
|
return agents;
|
||||||
|
} catch (err) {
|
||||||
|
this.lastListFailed = true;
|
||||||
|
this.notifyError("listing agents", err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async dispatch(file: TFile, agent: string): Promise<void> {
|
||||||
|
const { editor, view } = this.findEditorFor(file);
|
||||||
|
const content = editor
|
||||||
|
? editor.getValue()
|
||||||
|
: await this.app.vault.read(file);
|
||||||
|
|
||||||
|
const notice = new Notice(`Beaver: sending to ${agent}…`, 0);
|
||||||
|
let response: ChatResponse;
|
||||||
|
try {
|
||||||
|
response = await sendChat(this.settings, {
|
||||||
|
filename: file.path,
|
||||||
|
content,
|
||||||
|
agent,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
notice.hide();
|
||||||
|
if (err instanceof BeaverApiError && err.status === 409) {
|
||||||
|
new Notice("Beaver: already running for this file", 6000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.notifyError(`sending to ${agent}`, err);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
notice.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === "nothing_to_do") {
|
||||||
|
new Notice(`Beaver: nothing to do (${response.reason ?? "no reason"})`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof response.new_content === "string") {
|
||||||
|
await this.writeBack(file, editor, view, response.new_content);
|
||||||
|
}
|
||||||
|
new Notice(`Beaver: ${agent} replied`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private findEditorFor(file: TFile): {
|
||||||
|
editor: Editor | null;
|
||||||
|
view: MarkdownView | null;
|
||||||
|
} {
|
||||||
|
// Walk open markdown views — we want the editor instance that owns
|
||||||
|
// ``file`` so we can use setValue (keeps the buffer's edit history
|
||||||
|
// intact) instead of falling back to vault.modify.
|
||||||
|
const leaves = this.app.workspace.getLeavesOfType("markdown");
|
||||||
|
for (const leaf of leaves) {
|
||||||
|
const view = leaf.view as MarkdownView;
|
||||||
|
if (view?.file?.path === file.path) {
|
||||||
|
return { editor: view.editor, view };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { editor: null, view: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async writeBack(
|
||||||
|
file: TFile,
|
||||||
|
editor: Editor | null,
|
||||||
|
_view: MarkdownView | null,
|
||||||
|
newContent: string,
|
||||||
|
): Promise<void> {
|
||||||
|
if (editor && editor.getValue() !== newContent) {
|
||||||
|
editor.setValue(newContent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!editor) {
|
||||||
|
await this.app.vault.modify(file, newContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private notifyError(action: string, err: unknown): void {
|
||||||
|
const msg =
|
||||||
|
err instanceof BeaverApiError
|
||||||
|
? `${err.status}: ${err.message}`
|
||||||
|
: err instanceof Error
|
||||||
|
? err.message
|
||||||
|
: String(err);
|
||||||
|
new Notice(`Beaver (${action}): ${msg}`, 8000);
|
||||||
|
console.error("Beaver:", action, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { App, Notice, PluginSettingTab, Setting } from "obsidian";
|
||||||
|
import { listAgents, BeaverApiError } from "./api";
|
||||||
|
import type BeaverPlugin from "./main";
|
||||||
|
|
||||||
|
export interface BeaverSettings {
|
||||||
|
baseUrl: string;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_SETTINGS: BeaverSettings = {
|
||||||
|
baseUrl: "http://localhost:62993",
|
||||||
|
token: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
export class BeaverSettingsTab extends PluginSettingTab {
|
||||||
|
plugin: BeaverPlugin;
|
||||||
|
|
||||||
|
constructor(app: App, plugin: BeaverPlugin) {
|
||||||
|
super(app, plugin);
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
display(): void {
|
||||||
|
const { containerEl } = this;
|
||||||
|
containerEl.empty();
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Base URL")
|
||||||
|
.setDesc("Markdown frontend root, e.g. http://localhost:62993")
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder("http://localhost:62993")
|
||||||
|
.setValue(this.plugin.settings.baseUrl)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.baseUrl = value.trim().replace(/\/+$/, "");
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Bearer token")
|
||||||
|
.setDesc("Token with the `messages` scope.")
|
||||||
|
.addText((text) => {
|
||||||
|
text.inputEl.type = "password";
|
||||||
|
text
|
||||||
|
.setPlaceholder("paste token")
|
||||||
|
.setValue(this.plugin.settings.token)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.token = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Test connection")
|
||||||
|
.setDesc("Calls GET /agents and reports the count.")
|
||||||
|
.addButton((btn) =>
|
||||||
|
btn.setButtonText("Test").onClick(async () => {
|
||||||
|
try {
|
||||||
|
const agents = await listAgents(this.plugin.settings);
|
||||||
|
this.plugin.cacheAgents(agents);
|
||||||
|
new Notice(`Beaver: found ${agents.length} agents`);
|
||||||
|
} catch (err) {
|
||||||
|
const msg =
|
||||||
|
err instanceof BeaverApiError
|
||||||
|
? `${err.status}: ${err.message}`
|
||||||
|
: err instanceof Error
|
||||||
|
? err.message
|
||||||
|
: String(err);
|
||||||
|
new Notice(`Beaver: ${msg}`, 8000);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"inlineSourceMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "ES2022",
|
||||||
|
"allowJs": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"importHelpers": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["DOM", "ES2022"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"0.1.0": "1.5.0"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user