feat: add OAuth subscription billing mode via claude CLI
- useSubscriptionBilling toggle in settings (default off) - claudeCLIPath setting (default /usr/local/bin/claude) - ClaudeClient.streamChatCLI: spawn claude CLI, delete both API key env vars for OAuth keychain routing - SettingsTab: toggle, CLI path input, API key field hidden in CLI mode, model refresh disabled in CLI mode - ChatView: guard allows send without API key when subscription billing active
This commit is contained in:
@@ -31319,8 +31319,8 @@ var ChatView = class extends import_obsidian.ItemView {
|
|||||||
const query = this.inputEl.value.trim();
|
const query = this.inputEl.value.trim();
|
||||||
if (!query || this.isLoading)
|
if (!query || this.isLoading)
|
||||||
return;
|
return;
|
||||||
if (!this.plugin.settings.apiKey) {
|
if (!this.plugin.settings.apiKey && !this.plugin.settings.useSubscriptionBilling) {
|
||||||
this.setStatus("\u26A0 Bitte API Key in den Einstellungen eingeben");
|
this.setStatus("\u26A0 Bitte API Key in den Einstellungen eingeben (oder Claude Abo aktivieren)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const mentions = [];
|
const mentions = [];
|
||||||
@@ -31479,7 +31479,9 @@ ${content}`;
|
|||||||
apiKey: this.plugin.settings.apiKey,
|
apiKey: this.plugin.settings.apiKey,
|
||||||
model: this.plugin.settings.model,
|
model: this.plugin.settings.model,
|
||||||
maxTokens: this.plugin.settings.maxTokens,
|
maxTokens: this.plugin.settings.maxTokens,
|
||||||
systemPrompt
|
systemPrompt,
|
||||||
|
useSubscriptionBilling: this.plugin.settings.useSubscriptionBilling,
|
||||||
|
claudeCLIPath: this.plugin.settings.claudeCLIPath
|
||||||
});
|
});
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
if (chunk.type === "text" && chunk.text) {
|
if (chunk.type === "text" && chunk.text) {
|
||||||
@@ -32748,6 +32750,7 @@ var HybridSearch = class {
|
|||||||
// src/ClaudeClient.ts
|
// src/ClaudeClient.ts
|
||||||
var import_obsidian2 = require("obsidian");
|
var import_obsidian2 = require("obsidian");
|
||||||
var https = __toESM(require("https"));
|
var https = __toESM(require("https"));
|
||||||
|
var import_child_process = require("child_process");
|
||||||
var ClaudeClient = class {
|
var ClaudeClient = class {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.baseUrl = "https://api.anthropic.com/v1/messages";
|
this.baseUrl = "https://api.anthropic.com/v1/messages";
|
||||||
@@ -32759,12 +32762,99 @@ var ClaudeClient = class {
|
|||||||
"anthropic-version": "2023-06-01"
|
"anthropic-version": "2023-06-01"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Stream via claude CLI using OAuth keychain billing.
|
||||||
|
* Both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN are deleted from env so the CLI
|
||||||
|
* falls back to the keychain OAuth token → subscription billing.
|
||||||
|
*/
|
||||||
|
async *streamChatCLI(messages, options) {
|
||||||
|
if (messages.length === 0) {
|
||||||
|
yield { type: "done" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const history = messages.slice(0, -1);
|
||||||
|
const lastMessage = messages[messages.length - 1];
|
||||||
|
let prompt = "";
|
||||||
|
if (history.length > 0) {
|
||||||
|
prompt += "Bisheriges Gespr\xE4ch:\n";
|
||||||
|
for (const m of history) {
|
||||||
|
prompt += `${m.role === "user" ? "User" : "Assistant"}: ${m.content}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
prompt += "\n";
|
||||||
|
}
|
||||||
|
prompt += lastMessage.content;
|
||||||
|
const cliPath = options.claudeCLIPath ?? "/usr/local/bin/claude";
|
||||||
|
const args = [
|
||||||
|
"--print",
|
||||||
|
"--model",
|
||||||
|
options.model,
|
||||||
|
"--output-format",
|
||||||
|
"text",
|
||||||
|
"--setting-sources",
|
||||||
|
"",
|
||||||
|
"--tools",
|
||||||
|
"",
|
||||||
|
"--system-prompt",
|
||||||
|
options.systemPrompt
|
||||||
|
];
|
||||||
|
const env3 = { ...process.env };
|
||||||
|
delete env3.ANTHROPIC_API_KEY;
|
||||||
|
delete env3.ANTHROPIC_AUTH_TOKEN;
|
||||||
|
const queue = [];
|
||||||
|
let done = false;
|
||||||
|
let wakeup = null;
|
||||||
|
const push = (c) => {
|
||||||
|
queue.push(c);
|
||||||
|
wakeup?.();
|
||||||
|
wakeup = null;
|
||||||
|
};
|
||||||
|
const finish = () => {
|
||||||
|
done = true;
|
||||||
|
wakeup?.();
|
||||||
|
wakeup = null;
|
||||||
|
};
|
||||||
|
const child = (0, import_child_process.spawn)(cliPath, args, { env: env3, stdio: ["pipe", "pipe", "pipe"] });
|
||||||
|
child.stdout.on("data", (chunk) => {
|
||||||
|
push({ type: "text", text: chunk.toString() });
|
||||||
|
});
|
||||||
|
child.stderr.on("data", () => {
|
||||||
|
});
|
||||||
|
child.on("error", (err) => {
|
||||||
|
push({ type: "error", error: `claude CLI error: ${err.message}` });
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
child.on("close", (code) => {
|
||||||
|
if (code !== 0 && code !== null && queue.every((c) => c.type !== "text")) {
|
||||||
|
push({ type: "error", error: `claude CLI exited with code ${code}` });
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
child.stdin.write(prompt);
|
||||||
|
child.stdin.end();
|
||||||
|
while (true) {
|
||||||
|
while (queue.length)
|
||||||
|
yield queue.shift();
|
||||||
|
if (done)
|
||||||
|
break;
|
||||||
|
await new Promise((r) => {
|
||||||
|
wakeup = r;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
while (queue.length)
|
||||||
|
yield queue.shift();
|
||||||
|
yield { type: "done" };
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Stream a chat completion via Node.js https + SSE, yielding text chunks as they arrive.
|
* Stream a chat completion via Node.js https + SSE, yielding text chunks as they arrive.
|
||||||
* Uses the Node.js https module (available in Obsidian's Electron renderer via Node integration)
|
* Uses the Node.js https module (available in Obsidian's Electron renderer via Node integration)
|
||||||
* to bypass Electron's CORS/CSP restrictions that block fetch and XHR to external APIs.
|
* to bypass Electron's CORS/CSP restrictions that block fetch and XHR to external APIs.
|
||||||
*/
|
*/
|
||||||
async *streamChat(messages, options) {
|
async *streamChat(messages, options) {
|
||||||
|
if (options.useSubscriptionBilling) {
|
||||||
|
yield* this.streamChatCLI(messages, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const queue = [];
|
const queue = [];
|
||||||
let done = false;
|
let done = false;
|
||||||
let wakeup = null;
|
let wakeup = null;
|
||||||
@@ -32926,6 +33016,8 @@ Wenn du Fragen beantwortest:
|
|||||||
embedExcludeFolders: [],
|
embedExcludeFolders: [],
|
||||||
useMempalace: false,
|
useMempalace: false,
|
||||||
mempalaceResults: 3,
|
mempalaceResults: 3,
|
||||||
|
useSubscriptionBilling: false,
|
||||||
|
claudeCLIPath: "/usr/local/bin/claude",
|
||||||
promptButtons: [
|
promptButtons: [
|
||||||
{
|
{
|
||||||
label: "Draft Check",
|
label: "Draft Check",
|
||||||
@@ -32936,7 +33028,7 @@ Wenn du Fragen beantwortest:
|
|||||||
label: "Monthly Check",
|
label: "Monthly Check",
|
||||||
filePath: "Schreibdenken/ferals/Code/Prompts/MONTHLY COHERENCE AUDIT",
|
filePath: "Schreibdenken/ferals/Code/Prompts/MONTHLY COHERENCE AUDIT",
|
||||||
searchMode: "date",
|
searchMode: "date",
|
||||||
searchFolders: ["Schreibdenken/ferals/Content/Artikel"]
|
searchFolders: ["Schreibdenken/ferals/Content/Artikel", "Schreibdenken/ferals/Content/Newsletter", "Schreibdenken/ferals/Content/Artikel Draft"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -32994,15 +33086,25 @@ var MemexChatSettingsTab = class extends import_obsidian3.PluginSettingTab {
|
|||||||
cls: "setting-item-description"
|
cls: "setting-item-description"
|
||||||
});
|
});
|
||||||
containerEl.createEl("h3", { text: "Claude API" });
|
containerEl.createEl("h3", { text: "Claude API" });
|
||||||
new import_obsidian3.Setting(containerEl).setName("API Key").setDesc("Dein Anthropic API Key (sk-ant-...)").addText(
|
new import_obsidian3.Setting(containerEl).setName("Claude Abo verwenden").setDesc("Claude CLI + OAuth statt API Key nutzen (nur Desktop, ben\xF6tigt claude im PATH)").addToggle(
|
||||||
|
(toggle) => toggle.setValue(this.plugin.settings.useSubscriptionBilling).onChange(async (value) => {
|
||||||
|
this.plugin.settings.useSubscriptionBilling = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
this.display();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const apiKeySetting = new import_obsidian3.Setting(containerEl).setName("API Key").setDesc("Dein Anthropic API Key (sk-ant-...)").addText(
|
||||||
(text) => text.setPlaceholder("sk-ant-api03-...").setValue(this.plugin.settings.apiKey).onChange(async (value) => {
|
(text) => text.setPlaceholder("sk-ant-api03-...").setValue(this.plugin.settings.apiKey).onChange(async (value) => {
|
||||||
this.plugin.settings.apiKey = value.trim();
|
this.plugin.settings.apiKey = value.trim();
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
if (this.plugin.settings.useSubscriptionBilling) {
|
||||||
|
apiKeySetting.settingEl.style.display = "none";
|
||||||
|
}
|
||||||
let modelDrop;
|
let modelDrop;
|
||||||
let refreshBtn;
|
let refreshBtn;
|
||||||
new import_obsidian3.Setting(containerEl).setName("Modell").setDesc("Welches Claude-Modell verwenden? (Aktualisieren zeigt Roh-IDs)").addDropdown((drop) => {
|
const modelSetting = new import_obsidian3.Setting(containerEl).setName("Modell").setDesc("Welches Claude-Modell verwenden? (Aktualisieren zeigt Roh-IDs)").addDropdown((drop) => {
|
||||||
modelDrop = drop;
|
modelDrop = drop;
|
||||||
for (const m of MODELS)
|
for (const m of MODELS)
|
||||||
drop.addOption(m.id, m.name);
|
drop.addOption(m.id, m.name);
|
||||||
@@ -33010,28 +33112,39 @@ var MemexChatSettingsTab = class extends import_obsidian3.PluginSettingTab {
|
|||||||
this.plugin.settings.model = value;
|
this.plugin.settings.model = value;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
});
|
});
|
||||||
}).addButton((btn) => {
|
|
||||||
refreshBtn = btn;
|
|
||||||
btn.setButtonText("Aktualisieren").onClick(async () => {
|
|
||||||
const prev = modelDrop.getValue();
|
|
||||||
refreshBtn.setDisabled(true);
|
|
||||||
refreshBtn.setButtonText("...");
|
|
||||||
try {
|
|
||||||
const models = await this.plugin.claude.fetchModels(this.plugin.settings.apiKey);
|
|
||||||
modelDrop.selectEl.empty();
|
|
||||||
for (const m of models)
|
|
||||||
modelDrop.addOption(m.id, m.name);
|
|
||||||
modelDrop.setValue(prev);
|
|
||||||
this.plugin.settings.model = modelDrop.getValue();
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
} catch (err) {
|
|
||||||
new import_obsidian3.Notice("Modelle konnten nicht geladen werden: " + err.message);
|
|
||||||
} finally {
|
|
||||||
refreshBtn.setDisabled(false);
|
|
||||||
refreshBtn.setButtonText("Aktualisieren");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
if (!this.plugin.settings.useSubscriptionBilling) {
|
||||||
|
modelSetting.addButton((btn) => {
|
||||||
|
refreshBtn = btn;
|
||||||
|
btn.setButtonText("Aktualisieren").onClick(async () => {
|
||||||
|
const prev = modelDrop.getValue();
|
||||||
|
refreshBtn.setDisabled(true);
|
||||||
|
refreshBtn.setButtonText("...");
|
||||||
|
try {
|
||||||
|
const models = await this.plugin.claude.fetchModels(this.plugin.settings.apiKey);
|
||||||
|
modelDrop.selectEl.empty();
|
||||||
|
for (const m of models)
|
||||||
|
modelDrop.addOption(m.id, m.name);
|
||||||
|
modelDrop.setValue(prev);
|
||||||
|
this.plugin.settings.model = modelDrop.getValue();
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
} catch (err) {
|
||||||
|
new import_obsidian3.Notice("Modelle konnten nicht geladen werden: " + err.message);
|
||||||
|
} finally {
|
||||||
|
refreshBtn.setDisabled(false);
|
||||||
|
refreshBtn.setButtonText("Aktualisieren");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.plugin.settings.useSubscriptionBilling) {
|
||||||
|
new import_obsidian3.Setting(containerEl).setName("Claude CLI Pfad").setDesc("Pfad zur claude-Binary (z.B. /usr/local/bin/claude). Nur Desktop.").addText(
|
||||||
|
(text) => text.setPlaceholder("/usr/local/bin/claude").setValue(this.plugin.settings.claudeCLIPath).onChange(async (value) => {
|
||||||
|
this.plugin.settings.claudeCLIPath = value.trim() || "/usr/local/bin/claude";
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
new import_obsidian3.Setting(containerEl).setName("Max. Antwort-Tokens").setDesc("Maximale L\xE4nge der Claude-Antwort. F\xFCr lange Analysen (z.B. Monthly Check) h\xF6her einstellen. (1024\u201316000)").addSlider(
|
new import_obsidian3.Setting(containerEl).setName("Max. Antwort-Tokens").setDesc("Maximale L\xE4nge der Claude-Antwort. F\xFCr lange Analysen (z.B. Monthly Check) h\xF6her einstellen. (1024\u201316000)").addSlider(
|
||||||
(slider) => slider.setLimits(1024, 16e3, 512).setValue(this.plugin.settings.maxTokens).setDynamicTooltip().onChange(async (value) => {
|
(slider) => slider.setLimits(1024, 16e3, 512).setValue(this.plugin.settings.maxTokens).setDynamicTooltip().onChange(async (value) => {
|
||||||
this.plugin.settings.maxTokens = value;
|
this.plugin.settings.maxTokens = value;
|
||||||
|
|||||||
+4
-2
@@ -256,8 +256,8 @@ export class ChatView extends ItemView {
|
|||||||
const query = this.inputEl.value.trim();
|
const query = this.inputEl.value.trim();
|
||||||
if (!query || this.isLoading) return;
|
if (!query || this.isLoading) return;
|
||||||
|
|
||||||
if (!this.plugin.settings.apiKey) {
|
if (!this.plugin.settings.apiKey && !this.plugin.settings.useSubscriptionBilling) {
|
||||||
this.setStatus("⚠ Bitte API Key in den Einstellungen eingeben");
|
this.setStatus("⚠ Bitte API Key in den Einstellungen eingeben (oder Claude Abo aktivieren)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,6 +458,8 @@ export class ChatView extends ItemView {
|
|||||||
model: this.plugin.settings.model,
|
model: this.plugin.settings.model,
|
||||||
maxTokens: this.plugin.settings.maxTokens,
|
maxTokens: this.plugin.settings.maxTokens,
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
|
useSubscriptionBilling: this.plugin.settings.useSubscriptionBilling,
|
||||||
|
claudeCLIPath: this.plugin.settings.claudeCLIPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { requestUrl } from "obsidian";
|
import { requestUrl } from "obsidian";
|
||||||
import * as https from "https";
|
import * as https from "https";
|
||||||
|
import { spawn } from "child_process";
|
||||||
|
|
||||||
export interface ClaudeMessage {
|
export interface ClaudeMessage {
|
||||||
role: "user" | "assistant";
|
role: "user" | "assistant";
|
||||||
@@ -11,6 +12,8 @@ export interface ClaudeOptions {
|
|||||||
model: string;
|
model: string;
|
||||||
maxTokens?: number;
|
maxTokens?: number;
|
||||||
systemPrompt: string;
|
systemPrompt: string;
|
||||||
|
useSubscriptionBilling?: boolean;
|
||||||
|
claudeCLIPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClaudeStreamChunk {
|
export interface ClaudeStreamChunk {
|
||||||
@@ -31,6 +34,85 @@ export class ClaudeClient {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream via claude CLI using OAuth keychain billing.
|
||||||
|
* Both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN are deleted from env so the CLI
|
||||||
|
* falls back to the keychain OAuth token → subscription billing.
|
||||||
|
*/
|
||||||
|
private async *streamChatCLI(
|
||||||
|
messages: ClaudeMessage[],
|
||||||
|
options: ClaudeOptions
|
||||||
|
): AsyncGenerator<ClaudeStreamChunk> {
|
||||||
|
if (messages.length === 0) {
|
||||||
|
yield { type: "done" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the prompt: history + current user message
|
||||||
|
const history = messages.slice(0, -1);
|
||||||
|
const lastMessage = messages[messages.length - 1];
|
||||||
|
let prompt = "";
|
||||||
|
if (history.length > 0) {
|
||||||
|
prompt += "Bisheriges Gespräch:\n";
|
||||||
|
for (const m of history) {
|
||||||
|
prompt += `${m.role === "user" ? "User" : "Assistant"}: ${m.content}\n`;
|
||||||
|
}
|
||||||
|
prompt += "\n";
|
||||||
|
}
|
||||||
|
prompt += lastMessage.content;
|
||||||
|
|
||||||
|
const cliPath = options.claudeCLIPath ?? "/usr/local/bin/claude";
|
||||||
|
const args = [
|
||||||
|
"--print",
|
||||||
|
"--model", options.model,
|
||||||
|
"--output-format", "text",
|
||||||
|
"--setting-sources", "",
|
||||||
|
"--tools", "",
|
||||||
|
"--system-prompt", options.systemPrompt,
|
||||||
|
];
|
||||||
|
|
||||||
|
const env = { ...process.env };
|
||||||
|
delete env.ANTHROPIC_API_KEY;
|
||||||
|
delete env.ANTHROPIC_AUTH_TOKEN;
|
||||||
|
|
||||||
|
const queue: ClaudeStreamChunk[] = [];
|
||||||
|
let done = false;
|
||||||
|
let wakeup: (() => void) | null = null;
|
||||||
|
const push = (c: ClaudeStreamChunk) => { queue.push(c); wakeup?.(); wakeup = null; };
|
||||||
|
const finish = () => { done = true; wakeup?.(); wakeup = null; };
|
||||||
|
|
||||||
|
const child = spawn(cliPath, args, { env, stdio: ["pipe", "pipe", "pipe"] });
|
||||||
|
|
||||||
|
child.stdout.on("data", (chunk: Buffer) => {
|
||||||
|
push({ type: "text", text: chunk.toString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on("data", () => { /* discard stderr */ });
|
||||||
|
|
||||||
|
child.on("error", (err: Error) => {
|
||||||
|
push({ type: "error", error: `claude CLI error: ${err.message}` });
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("close", (code: number | null) => {
|
||||||
|
if (code !== 0 && code !== null && queue.every(c => c.type !== "text")) {
|
||||||
|
push({ type: "error", error: `claude CLI exited with code ${code}` });
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stdin.write(prompt);
|
||||||
|
child.stdin.end();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
while (queue.length) yield queue.shift()!;
|
||||||
|
if (done) break;
|
||||||
|
await new Promise<void>(r => { wakeup = r; });
|
||||||
|
}
|
||||||
|
while (queue.length) yield queue.shift()!;
|
||||||
|
yield { type: "done" };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stream a chat completion via Node.js https + SSE, yielding text chunks as they arrive.
|
* Stream a chat completion via Node.js https + SSE, yielding text chunks as they arrive.
|
||||||
* Uses the Node.js https module (available in Obsidian's Electron renderer via Node integration)
|
* Uses the Node.js https module (available in Obsidian's Electron renderer via Node integration)
|
||||||
@@ -40,6 +122,10 @@ export class ClaudeClient {
|
|||||||
messages: ClaudeMessage[],
|
messages: ClaudeMessage[],
|
||||||
options: ClaudeOptions
|
options: ClaudeOptions
|
||||||
): AsyncGenerator<ClaudeStreamChunk> {
|
): AsyncGenerator<ClaudeStreamChunk> {
|
||||||
|
if (options.useSubscriptionBilling) {
|
||||||
|
yield* this.streamChatCLI(messages, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const queue: ClaudeStreamChunk[] = [];
|
const queue: ClaudeStreamChunk[] = [];
|
||||||
let done = false;
|
let done = false;
|
||||||
let wakeup: (() => void) | null = null;
|
let wakeup: (() => void) | null = null;
|
||||||
|
|||||||
+42
-3
@@ -30,6 +30,8 @@ export interface MemexChatSettings {
|
|||||||
embedExcludeFolders: string[]; // vault folders to skip during embedding
|
embedExcludeFolders: string[]; // vault folders to skip during embedding
|
||||||
useMempalace: boolean; // inject MemPalace search results as additional context
|
useMempalace: boolean; // inject MemPalace search results as additional context
|
||||||
mempalaceResults: number; // number of MemPalace results to include
|
mempalaceResults: number; // number of MemPalace results to include
|
||||||
|
useSubscriptionBilling: boolean; // use claude CLI + OAuth keychain instead of API key
|
||||||
|
claudeCLIPath: string; // path to claude binary
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: MemexChatSettings = {
|
export const DEFAULT_SETTINGS: MemexChatSettings = {
|
||||||
@@ -58,6 +60,8 @@ Wenn du Fragen beantwortest:
|
|||||||
embedExcludeFolders: [],
|
embedExcludeFolders: [],
|
||||||
useMempalace: false,
|
useMempalace: false,
|
||||||
mempalaceResults: 3,
|
mempalaceResults: 3,
|
||||||
|
useSubscriptionBilling: false,
|
||||||
|
claudeCLIPath: "/usr/local/bin/claude",
|
||||||
promptButtons: [
|
promptButtons: [
|
||||||
{
|
{
|
||||||
label: "Draft Check",
|
label: "Draft Check",
|
||||||
@@ -139,7 +143,20 @@ export class MemexChatSettingsTab extends PluginSettingTab {
|
|||||||
// --- API ---
|
// --- API ---
|
||||||
containerEl.createEl("h3", { text: "Claude API" });
|
containerEl.createEl("h3", { text: "Claude API" });
|
||||||
|
|
||||||
|
// Subscription billing toggle
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
|
.setName("Claude Abo verwenden")
|
||||||
|
.setDesc("Claude CLI + OAuth statt API Key nutzen (nur Desktop, benötigt claude im PATH)")
|
||||||
|
.addToggle((toggle) =>
|
||||||
|
toggle.setValue(this.plugin.settings.useSubscriptionBilling).onChange(async (value) => {
|
||||||
|
this.plugin.settings.useSubscriptionBilling = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
this.display(); // re-render to show/hide API key field
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// API key field — hidden when subscription billing is active
|
||||||
|
const apiKeySetting = new Setting(containerEl)
|
||||||
.setName("API Key")
|
.setName("API Key")
|
||||||
.setDesc("Dein Anthropic API Key (sk-ant-...)")
|
.setDesc("Dein Anthropic API Key (sk-ant-...)")
|
||||||
.addText((text) =>
|
.addText((text) =>
|
||||||
@@ -151,11 +168,14 @@ export class MemexChatSettingsTab extends PluginSettingTab {
|
|||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
if (this.plugin.settings.useSubscriptionBilling) {
|
||||||
|
apiKeySetting.settingEl.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
let modelDrop: DropdownComponent;
|
let modelDrop: DropdownComponent;
|
||||||
let refreshBtn: ButtonComponent;
|
let refreshBtn: ButtonComponent;
|
||||||
|
|
||||||
new Setting(containerEl)
|
const modelSetting = new Setting(containerEl)
|
||||||
.setName("Modell")
|
.setName("Modell")
|
||||||
.setDesc("Welches Claude-Modell verwenden? (Aktualisieren zeigt Roh-IDs)")
|
.setDesc("Welches Claude-Modell verwenden? (Aktualisieren zeigt Roh-IDs)")
|
||||||
.addDropdown((drop) => {
|
.addDropdown((drop) => {
|
||||||
@@ -165,8 +185,10 @@ export class MemexChatSettingsTab extends PluginSettingTab {
|
|||||||
this.plugin.settings.model = value;
|
this.plugin.settings.model = value;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
.addButton((btn) => {
|
|
||||||
|
if (!this.plugin.settings.useSubscriptionBilling) {
|
||||||
|
modelSetting.addButton((btn) => {
|
||||||
refreshBtn = btn;
|
refreshBtn = btn;
|
||||||
btn.setButtonText("Aktualisieren").onClick(async () => {
|
btn.setButtonText("Aktualisieren").onClick(async () => {
|
||||||
const prev = modelDrop.getValue();
|
const prev = modelDrop.getValue();
|
||||||
@@ -187,6 +209,23 @@ export class MemexChatSettingsTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLI path — visible only in subscription billing mode
|
||||||
|
if (this.plugin.settings.useSubscriptionBilling) {
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Claude CLI Pfad")
|
||||||
|
.setDesc("Pfad zur claude-Binary (z.B. /usr/local/bin/claude). Nur Desktop.")
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder("/usr/local/bin/claude")
|
||||||
|
.setValue(this.plugin.settings.claudeCLIPath)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.claudeCLIPath = value.trim() || "/usr/local/bin/claude";
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName("Max. Antwort-Tokens")
|
.setName("Max. Antwort-Tokens")
|
||||||
|
|||||||
Reference in New Issue
Block a user