mirror of
https://github.com/binhong87/obsidian-note-agent.git
synced 2026-07-22 06:52:10 +00:00
feat: rename to Obsidian Note Agent, fix default model display in status bar and indicator
- Plugin id/name updated to obsidian-note-agent (Bin-Home) - Class renamed ObsidianAgentPlugin → ObsidianNoteAgentPlugin throughout - VIEW_TYPE and default chatsFolder path updated to match new id - Status bar and provider indicator button now use activeProfile() so the default model name shows instead of "-" when no override is set - Indicator button separator changed from " / " to ":" to match status bar - Add README.md and README-cn.md Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
This commit is contained in:
parent
3c39fdadea
commit
ff6940cbba
17 changed files with 207 additions and 32 deletions
|
|
@ -11,7 +11,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||
- Single test: `npx vitest run tests/path/to/file.test.ts` (optionally `-t "test name"`).
|
||||
- No lint script is wired up, though `eslint` + `eslint-plugin-svelte` are installed; invoke via `npx eslint <path>` if needed.
|
||||
|
||||
There is no install-into-vault step in scripts — `main.js`, `manifest.json`, and `styles.css` at the repo root are the plugin artifacts. Symlink or copy the repo into `<vault>/.obsidian/plugins/obsidian-agent/` for local testing.
|
||||
There is no install-into-vault step in scripts — `main.js`, `manifest.json`, and `styles.css` at the repo root are the plugin artifacts. Symlink or copy the repo into `<vault>/.obsidian/plugins/obsidian-note-agent/` for local testing.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ This is an **Obsidian plugin** that adds an agentic chat pane capable of reading
|
|||
|
||||
### Layered structure (all under `src/`)
|
||||
|
||||
- **`main.ts`** — `ObsidianAgentPlugin` (extends `Plugin`). Wires everything together, owns the single active `Conversation`, exposes `sendMessage()` as an async generator that streams `LoopEvent`s to the UI. Also contains the `commitWrite` / `computeDiff` bridge that turns pending tool calls into real vault mutations after UI approval, and runs `SchedulerService` jobs.
|
||||
- **`main.ts`** — `ObsidianNoteAgentPlugin` (extends `Plugin`). Wires everything together, owns the single active `Conversation`, exposes `sendMessage()` as an async generator that streams `LoopEvent`s to the UI. Also contains the `commitWrite` / `computeDiff` bridge that turns pending tool calls into real vault mutations after UI approval, and runs `SchedulerService` jobs.
|
||||
- **`agent/`** — provider-agnostic orchestration.
|
||||
- `agent-loop.ts` — the core tool-calling loop. For each iteration it assembles `[system, ...conversation.messages]`, passes through `trimHistory(budget)`, streams the provider response (text deltas + tool calls), then dispatches each tool call. Uses a per-iteration `AbortController` linked to both outer cancel and `turnTimeoutMs` — `AbortError` is translated into a user-facing timeout `LoopEvent`.
|
||||
- `approval-queue.ts` — gate write tools behind UI approval.
|
||||
|
|
|
|||
86
README-cn.md
Normal file
86
README-cn.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Obsidian Note Agent
|
||||
|
||||
一款为 [Obsidian](https://obsidian.md) 设计的智能体 AI 助手插件,能够读取并(在您批准后)修改笔记库中的笔记,支持多种 LLM 服务提供商。
|
||||
|
||||
[English](README.md)
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **智能对话** — 多轮对话,支持自主工具调用循环
|
||||
- **笔记库工具** — 全文搜索、读取笔记、列出文件夹、跟随反链与出链、获取当前笔记和选中文本
|
||||
- **编辑模式** — 创建、编辑(全量替换或统一补丁)、删除、移动笔记;所有修改以差异对比形式展示,需您审批后方可写入
|
||||
- **多服务提供商** — OpenAI、Anthropic、DeepSeek、通义千问(阿里云)、Kimi(月之暗面)、智谱(GLM)、MiniMax、OpenRouter、Ollama(本地)及任意 OpenAI 或 Anthropic 兼容的自定义端点
|
||||
- **三种模式**
|
||||
- **问答模式** — 只读,适合问答和研究,不修改笔记库
|
||||
- **编辑模式** — 完整写入权限,所有修改通过差异对比界面审批后生效
|
||||
- **定时模式** — 自动后台运行(每日摘要、每周回顾),写入权限受限
|
||||
- **定时任务** — 自动生成每日摘要和每周回顾,写入到可配置的文件夹
|
||||
- **自动压缩** — 当对话历史接近模型上下文限制时,自动透明地进行压缩
|
||||
- **分提供商配置** — 每个提供商独立保存 API Key、基础 URL 和模型名称
|
||||
- **用户画像** — 可选的个人描述,注入到每次系统提示词中
|
||||
- **国际化** — 支持英文和简体中文 UI,自动跟随 Obsidian 的语言设置
|
||||
|
||||
## 安装
|
||||
|
||||
### 通过 BRAT(推荐,适合预发布版本)
|
||||
|
||||
1. 安装社区插件 [BRAT](https://github.com/TfTHacker/obsidian42-brat)
|
||||
2. 在 BRAT 设置中添加:`Bin-Home/obsidian-note-agent`
|
||||
3. 在**设置 → 第三方插件**中启用该插件
|
||||
|
||||
### 手动安装
|
||||
|
||||
1. 从[最新发布页面](../../releases/latest)下载 `main.js`、`manifest.json` 和 `styles.css`
|
||||
2. 创建文件夹 `<您的笔记库>/.obsidian/plugins/obsidian-note-agent/`
|
||||
3. 将三个文件复制到该文件夹
|
||||
4. 重启 Obsidian,在**设置 → 第三方插件**中启用该插件
|
||||
|
||||
## 配置
|
||||
|
||||
1. 打开**设置 → Obsidian Note Agent**
|
||||
2. 选择 LLM 服务提供商并输入 API Key
|
||||
3. 可选:设置自定义基础 URL(用于自托管或代理端点)及模型名称
|
||||
4. 选择模式:**问答模式**(只读)或**编辑模式**(可写入)
|
||||
|
||||
## 使用方法
|
||||
|
||||
- 点击左侧工具栏中的**机器人图标**,或在命令面板中运行 `Open Note Agent`
|
||||
- 输入消息后按 Enter 发送(Shift+Enter 换行)
|
||||
- 在**编辑模式**下,智能体会以统一差异格式展示笔记修改建议,逐项审批或拒绝后方可写入磁盘
|
||||
- 使用**新建对话**开始全新会话;历史对话已保存,可通过历史面板随时访问
|
||||
|
||||
## 支持的服务提供商
|
||||
|
||||
| 提供商 | 说明 |
|
||||
|---|---|
|
||||
| OpenAI | GPT-4o、GPT-4o-mini、o1、o3 等 |
|
||||
| Anthropic | Claude 3.5 / 4 系列 |
|
||||
| DeepSeek | deepseek-v3、deepseek-r1 |
|
||||
| 通义千问 | 阿里云 Dashscope(qwen-plus、qwen-max 等) |
|
||||
| Kimi | 月之暗面(moonshot-v1 系列) |
|
||||
| 智谱 | GLM-4 系列 |
|
||||
| MiniMax | MiniMax-Text 系列 |
|
||||
| OpenRouter | 通过 openrouter.ai 访问任意模型 |
|
||||
| Ollama | 本地模型(Llama、Mistral、Qwen 等) |
|
||||
| 自定义 | 任意 OpenAI 兼容或 Anthropic 兼容端点 |
|
||||
|
||||
## 开发
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev # esbuild 监听模式,保存即重新构建
|
||||
npm run build # tsc 类型检查 + 生产构建
|
||||
npm test # 单元测试(Vitest)
|
||||
```
|
||||
|
||||
本地测试时,将仓库文件夹软链接或复制到您的笔记库:
|
||||
|
||||
```
|
||||
<笔记库>/.obsidian/plugins/obsidian-note-agent/
|
||||
```
|
||||
|
||||
插件产物为仓库根目录下的 `main.js`、`manifest.json` 和 `styles.css`。
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
86
README.md
Normal file
86
README.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Obsidian Note Agent
|
||||
|
||||
An agentic AI assistant plugin for [Obsidian](https://obsidian.md) that can read and — with your approval — modify vault notes, powered by your choice of LLM provider.
|
||||
|
||||
[中文文档](README-cn.md)
|
||||
|
||||
## Features
|
||||
|
||||
- **Agentic chat** — multi-turn conversations with an autonomous tool-calling loop
|
||||
- **Vault tools** — full-text search, read notes, list folders, follow backlinks and outgoing links, get the active note and current selection
|
||||
- **Edit mode** — create, edit (full replace or unified patch), delete, and move notes; every change is shown as a diff for your review before it is committed
|
||||
- **Multiple providers** — OpenAI, Anthropic, DeepSeek, Qwen (Alibaba), Kimi (Moonshot), Zhipu (GLM), MiniMax, OpenRouter, Ollama (local), and any custom OpenAI- or Anthropic-compatible endpoint
|
||||
- **Three modes**
|
||||
- **Ask** — read-only; for Q&A and research without touching your vault
|
||||
- **Edit** — full write access; all changes require your approval via a diff UI
|
||||
- **Scheduled** — automated background runs (daily summary, weekly review) with restricted write access
|
||||
- **Scheduled tasks** — daily summaries and weekly reviews written automatically to configurable folders
|
||||
- **Auto-compaction** — conversation history is compacted transparently when approaching the model's context limit
|
||||
- **Per-provider profiles** — separate API key, base URL, and model saved per provider
|
||||
- **User profile** — optional personal description injected into every system prompt
|
||||
- **i18n** — English and Simplified Chinese UI, auto-detected from Obsidian's language setting
|
||||
|
||||
## Installation
|
||||
|
||||
### Via BRAT (recommended for pre-release)
|
||||
|
||||
1. Install the [BRAT](https://github.com/TfTHacker/obsidian42-brat) community plugin
|
||||
2. In BRAT settings, add: `Bin-Home/obsidian-note-agent`
|
||||
3. Enable the plugin in **Settings → Community plugins**
|
||||
|
||||
### Manual
|
||||
|
||||
1. Download `main.js`, `manifest.json`, and `styles.css` from the [latest release](../../releases/latest)
|
||||
2. Create the folder `<your-vault>/.obsidian/plugins/obsidian-note-agent/`
|
||||
3. Copy the three files into that folder
|
||||
4. Reload Obsidian and enable the plugin in **Settings → Community plugins**
|
||||
|
||||
## Setup
|
||||
|
||||
1. Open **Settings → Obsidian Note Agent**
|
||||
2. Select your LLM provider and enter your API key
|
||||
3. Optionally set a custom base URL (for self-hosted or proxy endpoints) and model name
|
||||
4. Choose a mode: **Ask** for read-only access, **Edit** for write access
|
||||
|
||||
## Usage
|
||||
|
||||
- Click the **bot icon** in the left ribbon, or run `Open Note Agent` from the command palette
|
||||
- Type your message and press Enter (or Shift+Enter for a new line)
|
||||
- In **Edit mode**, the agent proposes note changes as unified diffs — approve or reject each one before it is written to disk
|
||||
- Use `New Chat` to start a fresh conversation; previous conversations are saved and accessible via the history panel
|
||||
|
||||
## Supported Providers
|
||||
|
||||
| Provider | Notes |
|
||||
|---|---|
|
||||
| OpenAI | GPT-4o, GPT-4o-mini, o1, o3, etc. |
|
||||
| Anthropic | Claude 3.5 / 4 series |
|
||||
| DeepSeek | deepseek-v3, deepseek-r1 |
|
||||
| Qwen | Alibaba Cloud Dashscope (qwen-plus, qwen-max, etc.) |
|
||||
| Kimi | Moonshot AI (moonshot-v1 series) |
|
||||
| Zhipu | GLM-4 series |
|
||||
| MiniMax | MiniMax-Text series |
|
||||
| OpenRouter | Any model via openrouter.ai |
|
||||
| Ollama | Local models — Llama, Mistral, Qwen, etc. |
|
||||
| Custom | Any OpenAI-compatible or Anthropic-compatible endpoint |
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev # esbuild watch mode — rebuilds on save
|
||||
npm run build # tsc type-check + production bundle
|
||||
npm test # unit tests (Vitest)
|
||||
```
|
||||
|
||||
For local testing, symlink or copy the repo folder into your vault:
|
||||
|
||||
```
|
||||
<vault>/.obsidian/plugins/obsidian-note-agent/
|
||||
```
|
||||
|
||||
The plugin artifacts are `main.js`, `manifest.json`, and `styles.css` at the repo root.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"id": "obsidian-agent",
|
||||
"name": "Obsidian Agent",
|
||||
"id": "obsidian-note-agent",
|
||||
"name": "Obsidian Note Agent",
|
||||
"version": "0.1.0",
|
||||
"minAppVersion": "1.5.0",
|
||||
"description": "Agentic assistant for personal knowledge management.",
|
||||
"author": "You",
|
||||
"description": "Agentic AI assistant for reading and editing vault notes.",
|
||||
"author": "Bin-Home",
|
||||
"authorUrl": "https://github.com/Bin-Home",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "obsidian-agent",
|
||||
"name": "obsidian-note-agent",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { AgentChatView, VIEW_TYPE_AGENT_CHAT } from "./ui/chat-view";
|
|||
import { StatusBar } from "./ui/status-bar";
|
||||
import { applyUnifiedPatch } from "./utils/patch";
|
||||
|
||||
export default class ObsidianAgentPlugin extends Plugin {
|
||||
export default class ObsidianNoteAgentPlugin extends Plugin {
|
||||
settings!: Settings;
|
||||
i18n!: I18n;
|
||||
vault!: VaultService;
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export const DEFAULT_SETTINGS: Settings = {
|
|||
providerId: "openai",
|
||||
providers: defaultProviders(),
|
||||
mode: "ask",
|
||||
chatsFolder: ".obsidian/plugins/obsidian-agent/chats",
|
||||
chatsFolder: ".obsidian/plugins/obsidian-note-agent/chats",
|
||||
locale: "auto",
|
||||
userProfile: "",
|
||||
historyRetentionDays: 30,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type ObsidianAgentPlugin from "../main";
|
||||
export let plugin: ObsidianAgentPlugin;
|
||||
import type ObsidianNoteAgentPlugin from "../main";
|
||||
export let plugin: ObsidianNoteAgentPlugin;
|
||||
|
||||
let summary = plugin.lastTurnSummary;
|
||||
plugin.onSummaryChange(s => (summary = s));
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
import MessageList from "./MessageList.svelte";
|
||||
import ModeToggle from "./ModeToggle.svelte";
|
||||
import ConversationList from "./ConversationList.svelte";
|
||||
import type ObsidianAgentPlugin from "../main";
|
||||
export let plugin: ObsidianAgentPlugin;
|
||||
import type ObsidianNoteAgentPlugin from "../main";
|
||||
import { activeProfile } from "../settings";
|
||||
export let plugin: ObsidianNoteAgentPlugin;
|
||||
|
||||
let input = "";
|
||||
let busy = false;
|
||||
|
|
@ -92,7 +93,7 @@
|
|||
setting.openTabById?.(plugin.manifest.id);
|
||||
}
|
||||
}
|
||||
$: providerLabel = (settingsTick, `${plugin.i18n.t(`provider.${plugin.settings.providerId}`)} / ${plugin.settings.providers[plugin.settings.providerId]?.model || "–"}`);
|
||||
$: providerLabel = (settingsTick, `${plugin.i18n.t(`provider.${plugin.settings.providerId}`)}:${activeProfile(plugin.settings).model}`);
|
||||
$: charCount = input.length;
|
||||
$: showCharCount = charCount > 500;
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount, createEventDispatcher } from "svelte";
|
||||
import type ObsidianAgentPlugin from "../main";
|
||||
export let plugin: ObsidianAgentPlugin;
|
||||
import type ObsidianNoteAgentPlugin from "../main";
|
||||
export let plugin: ObsidianNoteAgentPlugin;
|
||||
|
||||
const dispatch = createEventDispatcher<{ select: void; newChat: void }>();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { Notice } from "obsidian";
|
||||
import type ObsidianAgentPlugin from "../main";
|
||||
import type ObsidianNoteAgentPlugin from "../main";
|
||||
export let p: any;
|
||||
export let plugin: ObsidianAgentPlugin;
|
||||
export let plugin: ObsidianNoteAgentPlugin;
|
||||
|
||||
const t = (k: string) => plugin.i18n.t(k);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
import DiffReviewBlock from "./DiffReviewBlock.svelte";
|
||||
import ChangeSummary from "./ChangeSummary.svelte";
|
||||
import { markdown } from "./markdown-action";
|
||||
import type ObsidianAgentPlugin from "../main";
|
||||
import type ObsidianNoteAgentPlugin from "../main";
|
||||
|
||||
export let messages: any[];
|
||||
export let streamBuf: string;
|
||||
export let pending: any[];
|
||||
export let plugin: ObsidianAgentPlugin;
|
||||
export let plugin: ObsidianNoteAgentPlugin;
|
||||
export let busy: boolean = false;
|
||||
export let compacting: boolean = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onDestroy } from "svelte";
|
||||
import type ObsidianAgentPlugin from "../main";
|
||||
export let plugin: ObsidianAgentPlugin;
|
||||
import type ObsidianNoteAgentPlugin from "../main";
|
||||
export let plugin: ObsidianNoteAgentPlugin;
|
||||
|
||||
let mode = plugin.settings.mode;
|
||||
let autoApprove = plugin.settings.autoApprove;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { App, PluginSettingTab, Setting } from "obsidian";
|
||||
import type ObsidianAgentPlugin from "../main";
|
||||
import type ObsidianNoteAgentPlugin from "../main";
|
||||
import { listProviderIds } from "../providers/registry";
|
||||
import { PROVIDER_DEFAULTS, defaultProfile } from "../providers/defaults";
|
||||
import type { ProviderId } from "../types";
|
||||
|
||||
export class AgentSettingsTab extends PluginSettingTab {
|
||||
constructor(app: App, private plugin: ObsidianAgentPlugin) { super(app, plugin); }
|
||||
constructor(app: App, private plugin: ObsidianNoteAgentPlugin) { super(app, plugin); }
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this; containerEl.empty();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { ItemView, WorkspaceLeaf } from "obsidian";
|
||||
import ChatView from "./ChatView.svelte";
|
||||
import type ObsidianAgentPlugin from "../main";
|
||||
import type ObsidianNoteAgentPlugin from "../main";
|
||||
|
||||
export const VIEW_TYPE_AGENT_CHAT = "obsidian-agent-chat";
|
||||
export const VIEW_TYPE_AGENT_CHAT = "obsidian-note-agent-chat";
|
||||
|
||||
export class AgentChatView extends ItemView {
|
||||
private component: ChatView | null = null;
|
||||
constructor(leaf: WorkspaceLeaf, private plugin: ObsidianAgentPlugin) { super(leaf); }
|
||||
constructor(leaf: WorkspaceLeaf, private plugin: ObsidianNoteAgentPlugin) { super(leaf); }
|
||||
getViewType() { return VIEW_TYPE_AGENT_CHAT; }
|
||||
getDisplayText() { return "Agent"; }
|
||||
getIcon() { return "bot"; }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { MarkdownRenderer, Component } from "obsidian";
|
||||
import type ObsidianAgentPlugin from "../main";
|
||||
import type ObsidianNoteAgentPlugin from "../main";
|
||||
|
||||
export interface MarkdownParams {
|
||||
text: string;
|
||||
plugin: ObsidianAgentPlugin;
|
||||
plugin: ObsidianNoteAgentPlugin;
|
||||
}
|
||||
|
||||
export function markdown(node: HTMLElement, params: MarkdownParams) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import type ObsidianAgentPlugin from "../main";
|
||||
import type ObsidianNoteAgentPlugin from "../main";
|
||||
import { activeProfile } from "../settings";
|
||||
|
||||
export class StatusBar {
|
||||
constructor(private plugin: ObsidianAgentPlugin, private el: HTMLElement) {
|
||||
constructor(private plugin: ObsidianNoteAgentPlugin, private el: HTMLElement) {
|
||||
this.render("idle");
|
||||
}
|
||||
render(state: "idle" | "thinking" | "waiting" | "compacting") {
|
||||
|
|
@ -10,6 +11,6 @@ export class StatusBar {
|
|||
: state === "thinking" ? "…"
|
||||
: state === "compacting" ? "⟳"
|
||||
: "?";
|
||||
this.el.setText(`${label} ${s.providerId}:${s.providers[s.providerId]?.model || "-"}`);
|
||||
this.el.setText(`${label} ${s.providerId}:${activeProfile(s).model}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue