Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions MODIFICATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Claudian 修改记录

## 修改目标

让 Claudian 与 Claude Code CLI 共享配置,避免重复配置。

## 已实现的修改

### 1. Skills 全局共享

**文件**: `src/core/storage/SkillStorage.ts`

**改动**:
- 优先从 `~/.claude/skills/` 读取 skills
- Vault skills 作为后备(同名时全局优先)
- 保存时默认保存到全局位置

**效果**: 在 CC 中配置的 skills,Claudian 可以直接使用

---

### 2. MCP 全局共享

**文件**: `src/core/storage/McpStorage.ts`

**改动**:
- 优先从 `~/.claude/mcp.json` 读取 MCP 配置
- Vault `.claude/mcp.json` 作为后备
- 保存时默认保存到全局位置

**效果**: 在 CC 中配置的 MCP servers,Claudian 可以直接使用

---

### 3. 存储服务配置

**文件**: `src/core/storage/StorageService.ts`

**改动**:
```typescript
// 传递 { preferGlobal: true } 选项给 SkillStorage 和 McpStorage
this.skills = new SkillStorage(this.adapter, { preferGlobal: true });
this.mcp = new McpStorage(this.adapter, { preferGlobal: true });
```

---

### 4. LaTeX 流式渲染优化

**文件**: `src/features/chat/controllers/StreamController.ts`

**改动**:
- 流式输出期间:纯文本显示(无 markdown 渲染,无 MathJax)
- 流式结束时:一次性渲染完整 markdown + LaTeX

**原因**: 每次调用 `renderContent()` 都会触发 MathJax 处理全部内容,导致卡顿

**效果**: 流式输出飞快,结束时格式正确

---

## 未修改(保持原样)

| 功能 | 位置 | 说明 |
|------|------|------|
| **Agents** | `~/.claude/agents/` | 已原生支持 |
| **Hooks** | `~/.claude/settings.json` | SDK 自动处理 |
| **工作目录** | vault 路径 | 保持不变 |
| **历史记录** | `~/.claude/projects/{vault}/` | 按 vault 隔离 |

---

## Fork 维护

**Fork 仓库**: https://github.com/Sophomoresty/claudian

**分支**: `feature/global-cc-config`

**上游仓库**: https://github.com/YishenTu/claudian

**更新方式**:
```bash
cd C:\Users\Sophomores\AppData\Local\Temp\claudian
git checkout main
git pull origin main
git checkout feature/global-cc-config
git merge main
# 解决冲突后
git push fork feature/global-cc-config
```

---

## 构建部署

```bash
# 构建
cd C:\Users\Sophomores\AppData\Local\Temp\claudian
npm run build

# 部署到 Obsidian
cp main.js "E:/obsidian-notes/.obsidian/plugins/claudian/main.js"

# 重载 Obsidian: Ctrl+R
```
43 changes: 43 additions & 0 deletions dev-loop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
VAULT="E:/obsidian-notes"

case "$1" in
read)
if [ -f "$VAULT/.claude/debug/console.log" ]; then
echo "=== 性能日志 ==="
grep "\[Claudian\]" "$VAULT/.claude/debug/console.log" || echo "无日志"

echo ""
echo "=== 统计 ==="
chunks=$(grep -o "\[Claudian\] Chunk [0-9]*" "$VAULT/.claude/debug/console.log" | wc -l)
renders=$(grep -o "\[Claudian\] Render [0-9]*" "$VAULT/.claude/debug/console.log" | wc -l)
echo "Chunks: $chunks, Renders: $renders"

if [ "$chunks" -gt 0 ]; then
echo "减少: $(( 100 * (chunks - renders) / chunks ))%"
fi
else
echo "❌ 日志文件不存在"
echo ""
echo "请在 Obsidian Console 中运行:"
echo ""
echo " console.log((...args) => window._logs.push(args));"
echo " window.copyToVault = () => {"
echo " const vault = app.vault;"
echo " vault.adapter.write('.claude/debug/console.log', _logs.join('\n'));"
echo " console.log('已保存');"
echo " };"
echo ""
fi
;;
build)
npm run build
cp main.js "$VAULT/.obsidian/plugins/claudian/"
cp manifest.json "$VAULT/.obsidian/plugins/claudian/"
cp styles.css "$VAULT/.obsidian/plugins/claudian/"
echo "✅ 已构建并复制"
;;
*)
echo "用法: $0 {read|build}"
;;
esac
28 changes: 28 additions & 0 deletions save-console.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 在 Obsidian Console (Ctrl+Shift+I) 中运行这个脚本,会把日志保存到 vault

const vault = require('obsidian').app.vault;
const adapter = vault.adapter;

// 获取最近的 console 日志
const consoleLogs = [];
const originalLog = console.log;
console.log = function(...args) {
consoleLogs.push(args);
originalLog.apply(console, args);
};

// 等待收集日志...
window._claudianLogs = consoleLogs;
console.log('[Claudian] Console logger ready. Send a message in Claudian, then run: copyToVault()');

// 复制日志到文件
window.copyToVault = function() {
const logContent = consoleLogs.map(args =>
args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')
).join('\n');

adapter.write('.claude/debug/console.log', logContent);
console.log('[Claudian] Logs saved to .claude/debug/console.log');
};

console.log('[Claudian] Script loaded! Usage: copyToVault()');
15 changes: 15 additions & 0 deletions simple-perf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Simple performance tracking to add to StreamController
export const PERF_TRACKING = \`
// Performance fields
private perfLog: any[] = [];
private chunkCount = 0;
private renderCount = 0;
private streamStartTime = 0;

// Log to console with timestamp
private logPerf(type: string, data: any): void {
const entry = { type, time: Date.now(), ...data };
this.perfLog.push(entry);
console.log('[Claudian Perf]', entry);
}
\`;
102 changes: 88 additions & 14 deletions src/core/storage/McpStorage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/**
* McpStorage - Handles .claude/mcp.json read/write
* McpStorage - Handles MCP server configuration read/write
*
* Prioritizes global Claude Code config (~/.claude/mcp.json) over vault config.
*
* MCP server configurations are stored in Claude Code-compatible format
* with optional Claudian-specific metadata in _claudian field.
Expand All @@ -17,6 +19,10 @@
* }
*/

import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

import type {
ClaudianMcpConfigFile,
ClaudianMcpServer,
Expand All @@ -29,16 +35,52 @@ import type { VaultFileAdapter } from './VaultFileAdapter';
/** Path to MCP config file relative to vault root. */
export const MCP_CONFIG_PATH = '.claude/mcp.json';

/** Global MCP config path (shared with Claude Code CLI). */
const GLOBAL_MCP_CONFIG_PATH = path.join(os.homedir(), '.claude', 'mcp.json');

export class McpStorage {
constructor(private adapter: VaultFileAdapter) {}
private useGlobalConfig: boolean;

constructor(
private adapter: VaultFileAdapter,
options?: { preferGlobal?: boolean }
) {
// Default to global config (Claude Code CLI behavior)
this.useGlobalConfig = options?.preferGlobal ?? true;
}

async load(): Promise<ClaudianMcpServer[]> {
// Try global config first (Claude Code CLI compatibility)
if (this.useGlobalConfig) {
const globalServers = await this.loadFromFile(GLOBAL_MCP_CONFIG_PATH);
if (globalServers.length > 0) {
return globalServers;
}
}

// Fallback to vault config
return this.loadFromFile(MCP_CONFIG_PATH, true);
}

private async loadFromFile(
filePath: string,
isVaultPath = false
): Promise<ClaudianMcpServer[]> {
try {
if (!(await this.adapter.exists(MCP_CONFIG_PATH))) {
return [];
let content: string;

if (isVaultPath) {
if (!(await this.adapter.exists(filePath))) {
return [];
}
content = await this.adapter.read(filePath);
} else {
if (!fs.existsSync(filePath)) {
return [];
}
content = fs.readFileSync(filePath, 'utf-8');
}

const content = await this.adapter.read(MCP_CONFIG_PATH);
const file = JSON.parse(content) as ClaudianMcpConfigFile;

if (!file.mcpServers || typeof file.mcpServers !== 'object') {
Expand Down Expand Up @@ -115,16 +157,34 @@ export class McpStorage {
}
}

// Determine target file path
const targetPath = this.useGlobalConfig ? GLOBAL_MCP_CONFIG_PATH : MCP_CONFIG_PATH;

// Load existing file (if any)
let existing: Record<string, unknown> | null = null;
if (await this.adapter.exists(MCP_CONFIG_PATH)) {
try {
const raw = await this.adapter.read(MCP_CONFIG_PATH);
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object') {
existing = parsed as Record<string, unknown>;
if (this.useGlobalConfig) {
if (fs.existsSync(targetPath)) {
try {
const raw = fs.readFileSync(targetPath, 'utf-8');
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object') {
existing = parsed as Record<string, unknown>;
}
} catch {
existing = null;
}
}
} else {
if (await this.adapter.exists(targetPath)) {
try {
const raw = await this.adapter.read(targetPath);
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object') {
existing = parsed as Record<string, unknown>;
}
} catch {
existing = null;
}
} catch {
existing = null;
}
}

Expand All @@ -150,10 +210,24 @@ export class McpStorage {
}

const content = JSON.stringify(file, null, 2);
await this.adapter.write(MCP_CONFIG_PATH, content);

// Write to target location
if (this.useGlobalConfig) {
// Ensure directory exists
const dir = path.dirname(targetPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(targetPath, content, 'utf-8');
} else {
await this.adapter.write(MCP_CONFIG_PATH, content);
}
}

async exists(): Promise<boolean> {
if (this.useGlobalConfig) {
return fs.existsSync(GLOBAL_MCP_CONFIG_PATH);
}
return this.adapter.exists(MCP_CONFIG_PATH);
}

Expand Down
Loading
Loading