mirror of
https://github.com/fancive/obsidian-parallel-reader.git
synced 2026-07-22 06:53:43 +00:00
feat: restore Claude Code CLI backend
Add back Claude Code CLI as a backend option alongside API. Claude Code's `-p --output-format json` is a documented interface that works well with prompt mode. API remains the default. Change-Id: I048936a7d59e6768952e0cb007382f75413d1ba0
This commit is contained in:
parent
9e369ae1fa
commit
84095fa0eb
4 changed files with 111 additions and 37 deletions
25
README.md
25
README.md
|
|
@ -28,7 +28,16 @@
|
|||
3. 将三个文件放入该文件夹
|
||||
4. Obsidian → **设置 → 第三方插件 → 已安装插件** → 启用 **Parallel Reader**
|
||||
|
||||
### 配置 Provider
|
||||
### 选择后端
|
||||
|
||||
插件支持两种后端:
|
||||
|
||||
| 后端 | 说明 |
|
||||
|------|------|
|
||||
| **API / Provider**(默认) | 直连 LLM API,支持 20+ provider |
|
||||
| **Claude Code CLI** | 通过本地 `claude -p` 调用,复用 Claude Code 登录态 |
|
||||
|
||||
### API / Provider 模式
|
||||
|
||||
在插件设置中选择一个 Provider preset,填入 API Key 和模型 ID 即可。
|
||||
|
||||
|
|
@ -43,6 +52,20 @@
|
|||
|
||||
Model ID 支持 `provider/model` 写法(如 `anthropic/claude-sonnet-4-6`),匹配当前 preset 时自动剥离前缀。
|
||||
|
||||
### Claude Code CLI 模式
|
||||
|
||||
切换后端为 **Claude Code CLI**,插件通过 `claude -p --output-format json` 调用本地 Claude Code。
|
||||
|
||||
Obsidian 从 GUI 启动时不继承 shell `PATH`,需要在设置中填写 `claude` 的绝对路径:
|
||||
|
||||
```bash
|
||||
# 查看你的 claude 路径
|
||||
which claude
|
||||
# 例如:/Users/you/.claude/local/claude
|
||||
```
|
||||
|
||||
在插件设置的 **CLI 路径** 中填入该路径,点击 **Test** 验证。
|
||||
|
||||
## 命令
|
||||
|
||||
| 命令 | 说明 |
|
||||
|
|
|
|||
46
main.js
46
main.js
File diff suppressed because one or more lines are too long
22
main.ts
22
main.ts
|
|
@ -3,7 +3,7 @@ import { MarkdownView, Notice, Plugin, requestUrl, TFile } from 'obsidian';
|
|||
import { findLineForAnchor } from './src/anchor';
|
||||
import { serializeCacheFile, shouldConfirmRegenerate, touchCacheEntry } from './src/cache';
|
||||
import { activeIndexAfterCardDelete, removeCardAt, updateCardAt } from './src/cards';
|
||||
import { resolveCliPath } from './src/cli';
|
||||
import { resolveCliPath, summarizeViaClaudeCode } from './src/cli';
|
||||
import {
|
||||
classifyGenerationError,
|
||||
type GenerationJob,
|
||||
|
|
@ -57,15 +57,19 @@ async function summarizeDocument(
|
|||
) {
|
||||
const { system, user } = buildPrompts(content, settings);
|
||||
let cards: RawCard[];
|
||||
const useStreaming = supportsStreaming(settings);
|
||||
const abortController = useStreaming ? new AbortController() : null;
|
||||
if (abortController) {
|
||||
job.onCancel(() => abortController.abort());
|
||||
}
|
||||
if (useStreaming) {
|
||||
cards = await summarizeViaApiStreaming(system, user, settings, onStreamProgress, abortController!.signal);
|
||||
if (settings.backend === 'claude-code') {
|
||||
cards = await summarizeViaClaudeCode(system, user, settings, job);
|
||||
} else {
|
||||
cards = await summarizeViaApi(requestUrl, system, user, settings);
|
||||
const useStreaming = supportsStreaming(settings);
|
||||
const abortController = useStreaming ? new AbortController() : null;
|
||||
if (abortController) {
|
||||
job.onCancel(() => abortController.abort());
|
||||
}
|
||||
if (useStreaming) {
|
||||
cards = await summarizeViaApiStreaming(system, user, settings, onStreamProgress, abortController!.signal);
|
||||
} else {
|
||||
cards = await summarizeViaApi(requestUrl, system, user, settings);
|
||||
}
|
||||
}
|
||||
const resolved: ResolvedCard[] = cards.map((c) => ({
|
||||
title: c.title,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import { type App, Notice, type Plugin, PluginSettingTab, requestUrl, Setting } from 'obsidian';
|
||||
import { resolveCliPath, runCli } from './cli';
|
||||
import { testApiBackend } from './providers';
|
||||
import {
|
||||
API_AUTH_TYPES,
|
||||
|
|
@ -17,6 +18,11 @@ import {
|
|||
import type { PluginHost, PluginSettings } from './types';
|
||||
|
||||
async function testBackend(settings: PluginSettings) {
|
||||
if (settings.backend === 'claude-code') {
|
||||
const cmd = resolveCliPath('claude', settings.cliPath);
|
||||
const { stdout } = await runCli(cmd, ['--version'], '', 10000);
|
||||
return `claude @ ${cmd}\n${stdout.trim()}`;
|
||||
}
|
||||
return testApiBackend(requestUrl, settings);
|
||||
}
|
||||
|
||||
|
|
@ -48,9 +54,46 @@ export class ParallelReaderSettingTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
containerEl.createEl('h3', { text: tr('apiProviderHeader') });
|
||||
new Setting(containerEl)
|
||||
.setName(tr('settingBackendName'))
|
||||
.setDesc(tr('settingBackendDesc'))
|
||||
.addDropdown((d) =>
|
||||
d
|
||||
.addOption('api', 'API / Provider')
|
||||
.addOption('claude-code', 'Claude Code CLI')
|
||||
.setValue(this.plugin.settings.backend)
|
||||
.onChange(async (v) => {
|
||||
this.plugin.settings.backend = v;
|
||||
if (v === 'api' && !this.plugin.settings.apiBaseUrl) {
|
||||
applyApiProviderPreset(this.plugin.settings, this.plugin.settings.apiProvider || 'anthropic');
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
}),
|
||||
);
|
||||
|
||||
{
|
||||
const isCliBacked = this.plugin.settings.backend === 'claude-code';
|
||||
|
||||
if (isCliBacked) {
|
||||
new Setting(containerEl)
|
||||
.setName(tr('settingCliPathName'))
|
||||
.setDesc(tr('settingCliPathDesc'))
|
||||
.addText((t) =>
|
||||
t
|
||||
.setPlaceholder(tr('settingCliPathPlaceholder'))
|
||||
.setValue(this.plugin.settings.cliPath)
|
||||
.onChange(async (v) => {
|
||||
this.plugin.settings.cliPath = v.trim();
|
||||
this.plugin.saveSettingsDebounced();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (!isCliBacked) {
|
||||
containerEl.createEl('h3', { text: tr('apiProviderHeader') });
|
||||
}
|
||||
|
||||
if (!isCliBacked) {
|
||||
const preset = getApiPreset(this.plugin.settings);
|
||||
new Setting(containerEl)
|
||||
.setName(tr('settingProviderPresetName'))
|
||||
|
|
@ -173,13 +216,13 @@ export class ParallelReaderSettingTab extends PluginSettingTab {
|
|||
|
||||
new Setting(containerEl)
|
||||
.setName(tr('settingModelName'))
|
||||
.setDesc(tr('settingModelDescApi'))
|
||||
.setDesc(isCliBacked ? tr('settingModelDescCli') : tr('settingModelDescApi'))
|
||||
.addText((t) =>
|
||||
t
|
||||
.setPlaceholder(getApiPreset(this.plugin.settings).model || 'model-id')
|
||||
.setPlaceholder(isCliBacked ? DEFAULT_SETTINGS.model : getApiPreset(this.plugin.settings).model || 'model-id')
|
||||
.setValue(this.plugin.settings.model)
|
||||
.onChange(async (v) => {
|
||||
this.plugin.settings.model = v.trim();
|
||||
this.plugin.settings.model = v.trim() || (isCliBacked ? DEFAULT_SETTINGS.model : '');
|
||||
this.plugin.saveSettingsDebounced();
|
||||
}),
|
||||
);
|
||||
|
|
@ -259,7 +302,7 @@ export class ParallelReaderSettingTab extends PluginSettingTab {
|
|||
|
||||
new Setting(containerEl)
|
||||
.setName(tr('settingTestBackendName'))
|
||||
.setDesc(tr('settingTestBackendDescApi'))
|
||||
.setDesc(isCliBacked ? tr('settingTestBackendDescCli') : tr('settingTestBackendDescApi'))
|
||||
.addButton((b) =>
|
||||
b.setButtonText('Test').onClick(async () => {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue