codexmate 0.0.47 → 0.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -58,6 +58,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
58
58
  - **Claude Provider Bridge**: Connect Claude Code to OpenAI Chat Completions-compatible providers and Ollama through the built-in local Claude-compatible proxy.
59
59
  - **OpenCode Provider Control**: Manage OpenCode provider/model selection with a CodexMate-owned provider store under `~/.codexmate`, projecting only the active provider into native OpenCode config to avoid polluting or deleting user-owned settings.
60
60
  - **Skills Marketplace**: A local-first market to share and import skills between different agent apps.
61
+ - **Prompt File Editor**: Unified editor for global and project-level `CLAUDE.md` and `AGENTS.md` with auto-detection of project paths.
61
62
  - **Task Orchestrator**: Plan and execute complex tasks with dependency tracking.
62
63
 
63
64
  ---
@@ -76,6 +77,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
76
77
  | **Claude Provider Bridge** | ✅ | Connect Claude Code to OpenAI Chat Completions-compatible providers and Ollama via the built-in Claude-compatible proxy |
77
78
  | **OpenCode Provider Store** | ✅ | Keep multiple OpenCode providers in `~/.codexmate` while projecting only the selected provider to native OpenCode config |
78
79
  | **Prompt Templates** | ✅ | Reusable prompt plugins with variables |
80
+ | **Prompt File Editor** | ✅ | Edit global and project-level CLAUDE.md / AGENTS.md with auto-detect and path switching |
79
81
  | **MCP Integration** | ✅ | Expose local tools and resources via MCP stdio |
80
82
  | **Auto Update** | ✅ | Quick update CLI via `codexmate update` |
81
83
 
package/README.zh.md CHANGED
@@ -58,6 +58,7 @@
58
58
  - **Claude Provider 桥接**:通过内建本地 Claude 兼容代理,让 Claude Code 接入 OpenAI Chat Completions 兼容 provider 与 Ollama。
59
59
  - **OpenCode Provider 控制**:在 `~/.codexmate` 下维护 CodexMate 自有的 OpenCode 多 provider 存储,只将当前选中的 provider 投影到 OpenCode 原生配置,避免污染或误删用户已有配置。
60
60
  - **Skills 市场**:本地优先的市场,支持在不同的智能体应用之间共享和导入 Skills。
61
+ - **提示词文件编辑器**:统一编辑全局和项目级 `CLAUDE.md` 与 `AGENTS.md`,支持项目路径自动检测。
61
62
  - **任务编排器**:支持带有依赖跟踪的复杂任务规划与执行。
62
63
 
63
64
  ---
@@ -76,6 +77,7 @@
76
77
  | **Claude Provider 桥接** | ✅ | 通过内建 Claude 兼容代理,让 Claude Code 接入 OpenAI Chat Completions 兼容 provider 与 Ollama |
77
78
  | **OpenCode Provider 存储** | ✅ | 在 `~/.codexmate` 中保留多个 OpenCode provider,只将当前选中的 provider 投影到 OpenCode 原生配置 |
78
79
  | **提示词模板** | ✅ | 支持变量的可复用提示词插件 |
80
+ | **提示词文件编辑器** | ✅ | 编辑全局和项目级 CLAUDE.md / AGENTS.md,支持自动检测与路径切换 |
79
81
  | **MCP 集成** | ✅ | 通过 MCP stdio 暴露本地工具与资源 |
80
82
  | **自动更新** | ✅ | 通过 `codexmate update` 快速更新 CLI |
81
83
 
@@ -53,57 +53,119 @@ function createAgentsFileController(deps = {}) {
53
53
  return { ok: true, dirPath };
54
54
  }
55
55
 
56
- function resolveClaudeMdFilePath() {
57
- return path.join(CLAUDE_DIR, CLAUDE_MD_FILE_NAME);
56
+ function detectProjectClaudeMdDir(baseDir) {
57
+ if (typeof baseDir !== 'string' || !baseDir.trim()) {
58
+ return { error: 'project path is required' };
59
+ }
60
+ const root = baseDir.trim();
61
+ const rootPath = path.join(root, CLAUDE_MD_FILE_NAME);
62
+ const dotDirPath = path.join(root, '.claude', CLAUDE_MD_FILE_NAME);
63
+ try {
64
+ if (fs.statSync(rootPath).isFile()) {
65
+ return { path: rootPath, source: 'root', dir: root };
66
+ }
67
+ } catch (_) {}
68
+ try {
69
+ if (fs.statSync(dotDirPath).isFile()) {
70
+ return { path: dotDirPath, source: 'dotdir', dir: path.join(root, '.claude') };
71
+ }
72
+ } catch (_) {}
73
+ return { path: rootPath, source: 'root', dir: root };
74
+ }
75
+
76
+ function validateClaudeMdBaseDir(filePath) {
77
+ const dirPath = path.dirname(filePath);
78
+ try {
79
+ const stat = fs.statSync(dirPath);
80
+ if (!stat.isDirectory()) {
81
+ return { error: 'project directory is not a directory: ' + dirPath };
82
+ }
83
+ } catch (e) {
84
+ return { error: 'project directory does not exist: ' + dirPath };
85
+ }
86
+ return { ok: true, dirPath };
87
+ }
88
+
89
+ function resolveClaudeMdFilePath(params = {}) {
90
+ const baseDir = typeof params.baseDir === 'string' && params.baseDir.trim()
91
+ ? params.baseDir.trim()
92
+ : '';
93
+ if (!baseDir) {
94
+ return { filePath: path.join(CLAUDE_DIR, CLAUDE_MD_FILE_NAME), isProject: false };
95
+ }
96
+ var detected = detectProjectClaudeMdDir(baseDir);
97
+ if (detected.error) {
98
+ return { filePath: path.join(CLAUDE_DIR, CLAUDE_MD_FILE_NAME), isProject: false, detectionError: detected.error };
99
+ }
100
+ return { filePath: detected.path, isProject: true, detectionSource: detected.source, projectPath: baseDir };
58
101
  }
59
102
 
60
103
  function readClaudeMdFile(params = {}) {
61
- const filePath = resolveClaudeMdFilePath();
62
- const lineEndingFallback = os.EOL === '\r\n' ? '\r\n' : '\n';
104
+ var resolved = resolveClaudeMdFilePath(params);
105
+ var filePath = resolved.filePath;
106
+ var lineEndingFallback = os.EOL === '\r\n' ? '\r\n' : '\n';
107
+ var base = {
108
+ path: filePath,
109
+ lineEnding: lineEndingFallback
110
+ };
111
+ if (resolved.isProject) {
112
+ base.detectionSource = resolved.detectionSource;
113
+ base.projectPath = resolved.projectPath;
114
+ }
115
+ if (resolved.detectionError) {
116
+ base.detectionError = resolved.detectionError;
117
+ }
118
+ if (resolved.isProject) {
119
+ var dirCheck = validateClaudeMdBaseDir(filePath);
120
+ if (dirCheck.error) {
121
+ return { error: dirCheck.error };
122
+ }
123
+ }
63
124
  if (!fs.existsSync(filePath)) {
64
- return {
65
- exists: false,
66
- path: filePath,
67
- content: '',
68
- lineEnding: lineEndingFallback
69
- };
125
+ return Object.assign({ exists: false, content: '' }, base);
70
126
  }
71
127
  if (params.metaOnly) {
72
- return {
73
- exists: true,
74
- path: filePath,
75
- content: '',
76
- lineEnding: lineEndingFallback
77
- };
128
+ return Object.assign({ exists: true, content: '' }, base);
78
129
  }
79
130
  try {
80
- const raw = fs.readFileSync(filePath, 'utf-8');
81
- return {
131
+ var raw = fs.readFileSync(filePath, 'utf-8');
132
+ var result = {
82
133
  exists: true,
83
134
  path: filePath,
84
135
  content: stripUtf8Bom(raw),
85
136
  lineEnding: detectLineEnding(raw)
86
137
  };
138
+ if (resolved.isProject) {
139
+ result.detectionSource = resolved.detectionSource;
140
+ result.projectPath = resolved.projectPath;
141
+ }
142
+ return result;
87
143
  } catch (e) {
88
- return { error: `读取 CLAUDE.md 失败: ${e.message}` };
144
+ return { error: 'read CLAUDE.md failed: ' + e.message };
89
145
  }
90
146
  }
91
147
 
92
148
  function applyClaudeMdFile(params = {}) {
93
- const filePath = resolveClaudeMdFilePath();
94
- const content = typeof params.content === 'string' ? params.content : '';
149
+ var resolved = resolveClaudeMdFilePath(params);
150
+ var filePath = resolved.filePath;
151
+ var content = typeof params.content === 'string' ? params.content : '';
95
152
  if (content.length > 2 * 1024 * 1024) {
96
- return { error: '内容过大(最大 2MB' };
153
+ return { error: 'content too large (max 2MB)' };
97
154
  }
98
- const lineEnding = params.lineEnding === '\r\n' ? '\r\n' : '\n';
99
- const normalized = normalizeLineEnding(content, lineEnding);
100
- const finalContent = ensureUtf8Bom(normalized);
155
+ var lineEnding = params.lineEnding === '\r\n' ? '\r\n' : '\n';
156
+ var normalized = normalizeLineEnding(content, lineEnding);
157
+ var finalContent = ensureUtf8Bom(normalized);
101
158
  try {
102
- ensureDir(CLAUDE_DIR);
159
+ ensureDir(path.dirname(filePath));
103
160
  fs.writeFileSync(filePath, finalContent, 'utf-8');
104
- return { success: true, path: filePath };
161
+ var result = { success: true, path: filePath };
162
+ if (resolved.isProject) {
163
+ result.projectPath = resolved.projectPath;
164
+ result.detectionSource = resolved.detectionSource;
165
+ }
166
+ return result;
105
167
  } catch (e) {
106
- return { error: `写入 CLAUDE.md 失败: ${e.message}` };
168
+ return { error: 'write CLAUDE.md failed: ' + e.message };
107
169
  }
108
170
  }
109
171
 
@@ -181,6 +243,11 @@ function createAgentsFileController(deps = {}) {
181
243
  let readResult;
182
244
  if (context === 'claude-md') {
183
245
  readResult = readClaudeMdFile({ metaOnly });
246
+ } else if (context === 'claude-project') {
247
+ if (!params.baseDir || !String(params.baseDir).trim()) {
248
+ return { error: 'project path is required for claude-project context' };
249
+ }
250
+ readResult = readClaudeMdFile({ ...params, metaOnly });
184
251
  } else if (context === 'openclaw') {
185
252
  readResult = readOpenclawAgentsFile({ metaOnly });
186
253
  } else if (context === 'openclaw-workspace') {
@@ -215,6 +282,8 @@ function createAgentsFileController(deps = {}) {
215
282
  return {
216
283
  resolveAgentsFilePath,
217
284
  validateAgentsBaseDir,
285
+ detectProjectClaudeMdDir,
286
+ validateClaudeMdBaseDir,
218
287
  resolveClaudeMdFilePath,
219
288
  readClaudeMdFile,
220
289
  applyClaudeMdFile,
package/cli.js CHANGED
@@ -1828,6 +1828,8 @@ async function fetchProviderModels(providerName, overrides = {}) {
1828
1828
  const {
1829
1829
  resolveAgentsFilePath,
1830
1830
  validateAgentsBaseDir,
1831
+ detectProjectClaudeMdDir,
1832
+ validateClaudeMdBaseDir,
1831
1833
  resolveClaudeMdFilePath,
1832
1834
  readClaudeMdFile,
1833
1835
  applyClaudeMdFile,
@@ -2319,6 +2321,42 @@ function buildConfigTemplateDiff(params = {}) {
2319
2321
  };
2320
2322
  }
2321
2323
 
2324
+ function buildClaudeSettingsDiff(params = {}) {
2325
+ const content = typeof params.content === 'string' ? params.content : '';
2326
+ if (!content.trim()) {
2327
+ return { error: 'JSON 内容不能为空' };
2328
+ }
2329
+ if (content.length > 1024 * 1024) {
2330
+ return { error: '内容过大(最大 1MB)' };
2331
+ }
2332
+ let parsed;
2333
+ try {
2334
+ parsed = JSON.parse(content);
2335
+ } catch (e) {
2336
+ return { error: `JSON 解析失败: ${e.message}` };
2337
+ }
2338
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
2339
+ return { error: 'JSON 内容必须是一个对象' };
2340
+ }
2341
+ let beforeText = '';
2342
+ if (fs.existsSync(CLAUDE_SETTINGS_FILE)) {
2343
+ try {
2344
+ beforeText = fs.readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8');
2345
+ } catch (e) {
2346
+ return { error: `读取 settings.json 失败: ${e.message}` };
2347
+ }
2348
+ }
2349
+ const afterText = JSON.stringify(parsed, null, 2) + '\n';
2350
+ const diff = buildLineDiff(beforeText, afterText);
2351
+ const hasChanges = (diff.stats.added || 0) + (diff.stats.removed || 0) > 0;
2352
+ return {
2353
+ diff: {
2354
+ ...diff,
2355
+ hasChanges
2356
+ }
2357
+ };
2358
+ }
2359
+
2322
2360
  function addProviderToConfig(params = {}) {
2323
2361
  const name = typeof params.name === 'string' ? params.name.trim() : '';
2324
2362
  const url = typeof params.url === 'string' ? params.url.trim() : '';
@@ -9528,6 +9566,12 @@ async function applyToClaudeSettings(config = {}) {
9528
9566
  };
9529
9567
  delete nextEnv.ANTHROPIC_AUTH_TOKEN;
9530
9568
  delete nextEnv.CLAUDE_CODE_USE_KEY;
9569
+ const subModels = {
9570
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: model,
9571
+ ANTHROPIC_DEFAULT_SONNET_MODEL: model,
9572
+ ANTHROPIC_DEFAULT_OPUS_MODEL: model
9573
+ };
9574
+ Object.assign(nextEnv, subModels);
9531
9575
 
9532
9576
  const nextSettings = {
9533
9577
  ...currentSettings,
@@ -9546,7 +9590,10 @@ async function applyToClaudeSettings(config = {}) {
9546
9590
  updatedKeys: [
9547
9591
  'env.ANTHROPIC_API_KEY',
9548
9592
  'env.ANTHROPIC_BASE_URL',
9549
- 'env.ANTHROPIC_MODEL'
9593
+ 'env.ANTHROPIC_MODEL',
9594
+ 'env.ANTHROPIC_DEFAULT_HAIKU_MODEL',
9595
+ 'env.ANTHROPIC_DEFAULT_SONNET_MODEL',
9596
+ 'env.ANTHROPIC_DEFAULT_OPUS_MODEL'
9550
9597
  ]
9551
9598
  };
9552
9599
  if (proxyResult) {
@@ -11831,10 +11878,16 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
11831
11878
  case 'apply-claude-md-file':
11832
11879
  result = applyClaudeMdFile(params || {});
11833
11880
  if (result && !result.error) {
11834
- const mdTarget = (params && params.targetPath) ? String(params.targetPath) : 'CLAUDE.md';
11835
- notifyWebhook('claude-md-edit', 'CLAUDE.md modified: ' + mdTarget, { targetPath: mdTarget }).catch(function () { });
11881
+ const mdBaseDir = params && params.baseDir ? String(params.baseDir).trim() : '';
11882
+ const mdTarget = mdBaseDir
11883
+ ? path.join(mdBaseDir, 'CLAUDE.md')
11884
+ : ((params && params.targetPath) ? String(params.targetPath) : 'CLAUDE.md');
11885
+ notifyWebhook('claude-md-edit', 'CLAUDE.md modified: ' + mdTarget, { targetPath: mdTarget, projectPath: mdBaseDir }).catch(function () { });
11836
11886
  }
11837
11887
  break;
11888
+ case 'detect-project-claude-md':
11889
+ result = detectProjectClaudeMdDir((params && params.baseDir) || '');
11890
+ break;
11838
11891
  case 'preview-agents-diff':
11839
11892
  result = buildAgentsDiff(params || {});
11840
11893
  break;
@@ -11910,6 +11963,9 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
11910
11963
  case 'get-claude-settings-raw':
11911
11964
  result = readClaudeSettingsRaw();
11912
11965
  break;
11966
+ case 'preview-claude-settings-diff':
11967
+ result = buildClaudeSettingsDiff(params || {});
11968
+ break;
11913
11969
  case 'apply-claude-settings-raw':
11914
11970
  result = applyClaudeSettingsRaw(params || {});
11915
11971
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.0.47",
3
+ "version": "0.0.49",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/web-ui/app.js CHANGED
@@ -74,6 +74,9 @@ document.addEventListener('DOMContentLoaded', () => {
74
74
  showConfigTemplateModal: false,
75
75
  showAgentsModal: false,
76
76
  promptsSubTab: 'codex',
77
+ projectClaudeMdPath: '',
78
+ projectPathOptions: [],
79
+ projectPathOptionsLoading: false,
77
80
  showSkillsModal: false,
78
81
  showHealthCheckModal: false,
79
82
  showCodexBridgePoolModal: false,
@@ -574,6 +577,18 @@ document.addEventListener('DOMContentLoaded', () => {
574
577
  this.sessionTrashEnabled = this.normalizeSessionTrashEnabled(localStorage.getItem('codexmateSessionTrashEnabled'));
575
578
  this.sessionTrashRetentionDays = this.normalizeSessionTrashRetentionDays(localStorage.getItem('codexmateSessionTrashRetentionDays'));
576
579
  this.configTemplateDiffConfirmEnabled = loadConfigTemplateDiffConfirmEnabledFromStorage(localStorage);
580
+ try {
581
+ var savedProjectPath = localStorage.getItem('codexmate_project_claude_md_path');
582
+ if (savedProjectPath) {
583
+ this.projectClaudeMdPath = savedProjectPath;
584
+ }
585
+ } catch (_) {}
586
+ try {
587
+ var savedSubTab = localStorage.getItem('codexmate_prompts_sub_tab');
588
+ if (savedSubTab === 'codex' || savedSubTab === 'claude-project') {
589
+ this.promptsSubTab = savedSubTab;
590
+ }
591
+ } catch (_) {}
577
592
  window.addEventListener('resize', this.onWindowResize);
578
593
  window.addEventListener('keydown', this.handleGlobalKeydown);
579
594
  window.addEventListener('beforeunload', this.handleBeforeUnload);
@@ -740,10 +755,22 @@ document.addEventListener('DOMContentLoaded', () => {
740
755
  this.loadPromptsContent();
741
756
  }
742
757
  },
743
- promptsSubTab() {
758
+ promptsSubTab(newVal) {
759
+ try {
760
+ localStorage.setItem('codexmate_prompts_sub_tab', newVal);
761
+ } catch (_) {}
744
762
  if (this.mainTab === 'prompts' && typeof this.loadPromptsContent === 'function') {
745
763
  this.loadPromptsContent();
746
764
  }
765
+ },
766
+ projectClaudeMdPath(newPath) {
767
+ try {
768
+ if (newPath) {
769
+ localStorage.setItem('codexmate_project_claude_md_path', newPath);
770
+ } else {
771
+ localStorage.removeItem('codexmate_project_claude_md_path');
772
+ }
773
+ } catch (_) {}
747
774
  }
748
775
  },
749
776
 
@@ -336,6 +336,12 @@ export function buildAgentsDiffPreviewRequest(options = {}) {
336
336
  params.fileName = fileName;
337
337
  }
338
338
  }
339
+ if (context === 'claude-project') {
340
+ const baseDir = typeof options.baseDir === 'string' ? options.baseDir.trim() : '';
341
+ if (baseDir) {
342
+ params.baseDir = baseDir;
343
+ }
344
+ }
339
345
 
340
346
  const maxRequestBytes = Number.isFinite(options.maxRequestBytes)
341
347
  ? Math.max(1024, Math.floor(options.maxRequestBytes))
@@ -37,11 +37,16 @@ export function createAgentsMethods(options = {}) {
37
37
 
38
38
  return {
39
39
  async openClaudeMdEditor() {
40
- this.setAgentsModalContext('claude-md');
40
+ this.setAgentsModalContext('claude-project');
41
41
  const requestToken = issueLatestRequestToken(this, '_agentsOpenRequestToken');
42
42
  this.agentsLoading = true;
43
43
  try {
44
- const res = await api('get-claude-md-file');
44
+ const rpcParams = {};
45
+ var projectPath = (this.projectClaudeMdPath || '').trim();
46
+ if (projectPath) {
47
+ rpcParams.baseDir = projectPath;
48
+ }
49
+ const res = await api('get-claude-md-file', rpcParams);
45
50
  if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
46
51
  return;
47
52
  }
@@ -182,11 +187,18 @@ export function createAgentsMethods(options = {}) {
182
187
  setAgentsModalContext(context, options = {}) {
183
188
  const t = typeof this.t === 'function' ? this.t : null;
184
189
  const tr = (key, fallback, params = null) => (t ? t(key, params) : fallback);
185
- if (context === 'claude-md') {
186
- this.agentsContext = 'claude-md';
190
+ if (context === 'claude-project') {
191
+ var projectPath = (options.projectPath || this.projectClaudeMdPath || '').trim();
192
+ this.agentsContext = 'claude-project';
187
193
  this.agentsWorkspaceFileName = '';
188
- this.agentsModalTitle = tr('modal.agents.title.claudeMd', 'CLAUDE.md 编辑器');
189
- this.agentsModalHint = tr('modal.agents.hint.claudeMd', '保存后会写入 ~/.claude/CLAUDE.md。');
194
+ this.projectClaudeMdPath = projectPath;
195
+ if (projectPath) {
196
+ this.agentsModalTitle = tr('modal.agents.title.claudeProject', 'Project CLAUDE.md: ' + projectPath, { path: projectPath });
197
+ this.agentsModalHint = tr('modal.agents.hint.claudeProject', 'Saved content will be written to CLAUDE.md in: ' + projectPath, { path: projectPath });
198
+ } else {
199
+ this.agentsModalTitle = tr('modal.agents.title.claudeProjectGlobal', 'Global CLAUDE.md');
200
+ this.agentsModalHint = tr('modal.agents.hint.claudeProjectGlobal', 'Saved content will be written to ~/.claude/CLAUDE.md.');
201
+ }
190
202
  return;
191
203
  }
192
204
  if (context === 'openclaw-workspace') {
@@ -420,6 +432,41 @@ export function createAgentsMethods(options = {}) {
420
432
  this.resetAgentsDiffState();
421
433
  }
422
434
  },
435
+ selectProjectClaudeMdPath(pathValue) {
436
+ this.projectClaudeMdPath = (pathValue || '').trim();
437
+ if (this.promptsSubTab === 'claude-project' && this.mainTab === 'prompts') {
438
+ this.loadPromptsContent();
439
+ }
440
+ },
441
+ setProjectClaudeMdPathManual(pathValue) {
442
+ var trimmed = (pathValue || '').trim();
443
+ if (trimmed === (this.projectClaudeMdPath || '').trim()) {
444
+ return;
445
+ }
446
+ this.projectClaudeMdPath = trimmed;
447
+ if (this.promptsSubTab === 'claude-project' && this.mainTab === 'prompts') {
448
+ this.loadPromptsContent();
449
+ }
450
+ },
451
+ async loadProjectPathOptions() {
452
+ if (this.projectPathOptionsLoading) {
453
+ return;
454
+ }
455
+ this.projectPathOptionsLoading = true;
456
+ try {
457
+ const res = await api('list-session-paths', { source: 'claude', limit: 100 });
458
+ if (res && Array.isArray(res.paths)) {
459
+ this.projectPathOptions = res.paths
460
+ .map(function (p) { return (p && p.cwd) || p || ''; })
461
+ .filter(Boolean)
462
+ .filter(function (v, i, a) { return a.indexOf(v) === i; });
463
+ }
464
+ } catch (_) {
465
+ // silent
466
+ } finally {
467
+ this.projectPathOptionsLoading = false;
468
+ }
469
+ },
423
470
  async pasteAgentsContent() {
424
471
  if (this.agentsLoading || this.agentsSaving || this.agentsDiffVisible) {
425
472
  return;
@@ -444,10 +491,11 @@ export function createAgentsMethods(options = {}) {
444
491
  const fileName = context === 'openclaw-workspace'
445
492
  ? (this.agentsWorkspaceFileName || '')
446
493
  : '';
494
+ const projectPath = context === 'claude-project' ? (this.projectClaudeMdPath || '') : '';
447
495
  const lineEnding = this.agentsLineEnding || '\n';
448
496
  const content = typeof this.agentsContent === 'string' ? this.agentsContent : '';
449
497
  const original = typeof this.agentsOriginalContent === 'string' ? this.agentsOriginalContent : '';
450
- return `${context}::${fileName}::${lineEnding}::${content.length}::${content}::${original.length}::${original}`;
498
+ return `${context}::${fileName}::${projectPath}::${lineEnding}::${content.length}::${content}::${original.length}::${original}`;
451
499
  },
452
500
  async prepareAgentsDiff() {
453
501
  const requestFingerprint = this.buildAgentsDiffFingerprint();
@@ -504,7 +552,8 @@ export function createAgentsMethods(options = {}) {
504
552
  content: this.agentsContent,
505
553
  lineEnding: this.agentsLineEnding,
506
554
  context: this.agentsContext,
507
- fileName: this.agentsWorkspaceFileName
555
+ fileName: this.agentsWorkspaceFileName,
556
+ baseDir: this.agentsContext === 'claude-project' ? this.projectClaudeMdPath : undefined
508
557
  });
509
558
  if (previewRequest.exceedsBodyLimit) {
510
559
  applyPreviewState(buildAgentsDiffPreview({
@@ -621,8 +670,12 @@ export function createAgentsMethods(options = {}) {
621
670
  content: this.agentsContent,
622
671
  lineEnding: this.agentsLineEnding
623
672
  };
624
- if (this.agentsContext === 'claude-md') {
673
+ if (this.agentsContext === 'claude-project') {
625
674
  action = 'apply-claude-md-file';
675
+ const projectPath = (this.projectClaudeMdPath || '').trim();
676
+ if (projectPath) {
677
+ params.baseDir = projectPath;
678
+ }
626
679
  } else if (this.agentsContext === 'openclaw') {
627
680
  action = 'apply-openclaw-agents-file';
628
681
  } else if (this.agentsContext === 'openclaw-workspace') {
@@ -636,8 +689,8 @@ export function createAgentsMethods(options = {}) {
636
689
  }
637
690
  const successLabel = this.agentsContext === 'openclaw-workspace'
638
691
  ? this.t('toast.agents.saved.workspace', { name: this.agentsWorkspaceFileName || '' }).replace(/:\s*$/, '')
639
- : (this.agentsContext === 'claude-md'
640
- ? this.t('toast.agents.saved.claudeMd')
692
+ : (this.agentsContext === 'claude-project'
693
+ ? this.t('toast.agents.saved.claudeProject')
641
694
  : (this.agentsContext === 'openclaw' ? this.t('toast.agents.saved.openclaw') : this.t('toast.agents.saved.agents')));
642
695
  this.showMessage(successLabel, 'success');
643
696
  if (this.mainTab === 'prompts') {
@@ -653,7 +706,10 @@ export function createAgentsMethods(options = {}) {
653
706
  },
654
707
 
655
708
  switchPromptsSubTab(subTab) {
656
- const normalized = subTab === 'claude-md' ? 'claude-md' : 'codex';
709
+ const normalized = subTab === 'claude-project' ? 'claude-project' : 'codex';
710
+ if (normalized === 'claude-project' && !this.projectPathOptions.length && !this.projectPathOptionsLoading) {
711
+ this.loadProjectPathOptions();
712
+ }
657
713
  if (this.promptsSubTab === normalized) {
658
714
  this.loadPromptsContent();
659
715
  return;
@@ -666,9 +722,19 @@ export function createAgentsMethods(options = {}) {
666
722
  this.agentsLoading = true;
667
723
  this.resetAgentsDiffState();
668
724
  try {
669
- const isClaude = this.promptsSubTab === 'claude-md';
670
- const action = isClaude ? 'get-claude-md-file' : 'get-agents-file';
671
- const res = await api(action);
725
+ const subTab = this.promptsSubTab;
726
+ let action;
727
+ const rpcParams = {};
728
+ if (subTab === 'claude-project') {
729
+ action = 'get-claude-md-file';
730
+ const projectPath = (this.projectClaudeMdPath || '').trim();
731
+ if (projectPath) {
732
+ rpcParams.baseDir = projectPath;
733
+ }
734
+ } else {
735
+ action = 'get-agents-file';
736
+ }
737
+ const res = await api(action, rpcParams);
672
738
  if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
673
739
  return;
674
740
  }
@@ -681,7 +747,7 @@ export function createAgentsMethods(options = {}) {
681
747
  this.agentsPath = res.path || '';
682
748
  this.agentsExists = !!res.exists;
683
749
  this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
684
- this.agentsContext = isClaude ? 'claude-md' : 'codex';
750
+ this.agentsContext = subTab === 'claude-project' ? 'claude-project' : 'codex';
685
751
  } catch (e) {
686
752
  if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
687
753
  return;
@@ -750,9 +750,13 @@ export function createCodexConfigMethods(options = {}) {
750
750
  && this._configTemplateDiffPreviewRequestToken === requestToken
751
751
  && this.buildConfigTemplateDiffFingerprint() === requestFingerprint
752
752
  );
753
- const res = await api('preview-config-template-diff', {
754
- template: this.configTemplateContent
755
- });
753
+ const res = this.configTemplateContext === 'claude'
754
+ ? await api('preview-claude-settings-diff', {
755
+ content: this.configTemplateContent
756
+ })
757
+ : await api('preview-config-template-diff', {
758
+ template: this.configTemplateContent
759
+ });
756
760
  if (!shouldApply()) {
757
761
  return;
758
762
  }
@@ -145,9 +145,9 @@ export function createStartupClaudeMethods(options = {}) {
145
145
  this.maybeShowStarPrompt();
146
146
  return true;
147
147
  } catch (e) {
148
- this.initError = e && e.message === 'timeout'
149
- ? '读取配置超时'
150
- : '连接失败: ' + (e && e.message ? e.message : '');
148
+ if (e && e.message !== 'timeout') {
149
+ this.initError = '连接失败: ' + (e.message || '');
150
+ }
151
151
  return false;
152
152
  } finally {
153
153
  if (!preserveLoading) {
@@ -220,7 +220,11 @@ const en = Object.freeze({
220
220
  'subtitle.settings': 'Manage downloads, directories, and trash.',
221
221
  'subtitle.prompts': 'Edit AGENTS.md and CLAUDE.md.',
222
222
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
223
- 'prompts.subTab.claude': 'CLAUDE.md (Claude)',
223
+ 'prompts.subTab.project': 'CLAUDE.md',
224
+ 'prompts.project.pathLabel': 'Project path (optional)',
225
+ 'prompts.project.selectPlaceholder': '-- Global (~/.claude/CLAUDE.md) --',
226
+ 'prompts.project.manualPlaceholder': 'Or enter a project root path...',
227
+ 'prompts.project.detected': 'Editing CLAUDE.md in: {path}',
224
228
  'dashboard.doctor.title': 'Doctor',
225
229
  'dashboard.doctor.runChecks': 'Run checks',
226
230
  'dashboard.doctor.checking': 'Checking...',
@@ -401,7 +405,7 @@ const en = Object.freeze({
401
405
  'toast.save.ok': 'Saved',
402
406
  'toast.save.fail': 'Save failed',
403
407
  'toast.agents.saved.agents': 'AGENTS.md saved',
404
- 'toast.agents.saved.claudeMd': 'CLAUDE.md saved',
408
+ 'toast.agents.saved.claudeProject': 'Project CLAUDE.md saved',
405
409
  'toast.agents.saved.openclaw': 'OpenClaw AGENTS.md saved',
406
410
  'toast.agents.saved.workspace': 'Workspace file saved: {name}',
407
411
  'toast.delete.ok': 'Deleted',
@@ -486,6 +490,7 @@ const en = Object.freeze({
486
490
  'modal.configTemplate.mode.twoStep': 'Two-step confirm: preview diff, then apply.',
487
491
  'modal.configTemplate.mode.oneStep': 'One-step apply: write immediately.',
488
492
  'diff.title.configTemplate': 'Diff preview (config.toml)',
493
+ 'diff.title.claudeSettings': 'Diff preview (settings.json)',
489
494
  'diff.generating': 'Generating...',
490
495
  'diff.failed': 'Failed',
491
496
  'diff.noChanges': 'No changes detected',
@@ -871,12 +876,14 @@ const en = Object.freeze({
871
876
  'config.autoCompact.hint': 'Auto-compaction threshold (default 185000).',
872
877
  'config.agents.open': 'Open AGENTS.md',
873
878
  'modal.agents.title.default': 'AGENTS.md editor',
874
- 'modal.agents.title.claudeMd': 'CLAUDE.md editor',
875
879
  'modal.agents.title.openclaw': 'OpenClaw AGENTS.md editor',
876
880
  'modal.agents.hint.default': 'Saved content will be written to AGENTS.md (next to config.toml).',
877
- 'modal.agents.hint.claudeMd': 'Saved content will be written to ~/.claude/CLAUDE.md.',
878
- 'modal.agents.contentLabel.claudeMd': 'CLAUDE.md content',
879
- 'modal.agents.placeholder.claudeMd': 'Edit CLAUDE.md here',
881
+ 'modal.agents.title.claudeProject': 'Project CLAUDE.md: {path}',
882
+ 'modal.agents.hint.claudeProject': 'Saved content will be written to CLAUDE.md in: {path}',
883
+ 'modal.agents.title.claudeProjectGlobal': 'Global CLAUDE.md',
884
+ 'modal.agents.hint.claudeProjectGlobal': 'Saved content will be written to ~/.claude/CLAUDE.md.',
885
+ 'modal.agents.contentLabel.claudeProject': 'CLAUDE.md content',
886
+ 'modal.agents.placeholder.claudeProject': 'Edit CLAUDE.md here',
880
887
  'modal.agents.hint.openclaw': 'Saved content will be written to OpenClaw workspace AGENTS.md.',
881
888
  'modal.agents.title.openclawWorkspaceFile': 'OpenClaw workspace file: {fileName}',
882
889
  'modal.agents.hint.openclawWorkspaceFile': 'Saved content will be written to OpenClaw workspace {fileName}.',
@@ -1210,6 +1217,10 @@ const en = Object.freeze({
1210
1217
  'claude.model': 'Model',
1211
1218
  'claude.model.placeholder': 'e.g. claude-3-7-sonnet',
1212
1219
  'claude.model.hint': 'Model changes are saved and applied to the current config automatically.',
1220
+ 'claude.model.haiku': 'Haiku Model',
1221
+ 'claude.model.sonnet': 'Sonnet Model',
1222
+ 'claude.model.opus': 'Opus Model',
1223
+ 'claude.model.sub.placeholder': 'Defaults to the main model if left empty',
1213
1224
  'claude.targetApi.label': 'Target API',
1214
1225
  'claude.targetApi.responses': 'Anthropic',
1215
1226
  'claude.targetApi.chatCompletions': 'OpenAI Chat Completions (/v1/chat/completions)',
@@ -221,7 +221,11 @@ const ja = Object.freeze({
221
221
  'subtitle.settings': 'ダウンロード・ディレクトリ・ゴミ箱の管理。',
222
222
  'subtitle.prompts': 'AGENTS.md と CLAUDE.md を編集。',
223
223
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
224
- 'prompts.subTab.claude': 'CLAUDE.md (Claude)',
224
+ 'prompts.subTab.project': 'CLAUDE.md',
225
+ 'prompts.project.pathLabel': 'プロジェクトパス(任意)',
226
+ 'prompts.project.selectPlaceholder': '-- グローバル (~/.claude/CLAUDE.md) --',
227
+ 'prompts.project.manualPlaceholder': 'またはプロジェクトルートパスを入力...',
228
+ 'prompts.project.detected': '編集中: {path}',
225
229
 
226
230
  'dashboard.doctor.title': 'Doctor',
227
231
  'dashboard.doctor.runChecks': 'チェックを実行',
@@ -403,7 +407,7 @@ const ja = Object.freeze({
403
407
  'toast.save.ok': '保存しました',
404
408
  'toast.save.fail': '保存に失敗しました',
405
409
  'toast.agents.saved.agents': 'AGENTS.md 保存しました',
406
- 'toast.agents.saved.claudeMd': 'CLAUDE.md 保存しました',
410
+ 'toast.agents.saved.claudeProject': 'プロジェクト CLAUDE.md 保存しました',
407
411
  'toast.agents.saved.openclaw': 'OpenClaw AGENTS.md 保存しました',
408
412
  'toast.agents.saved.workspace': 'ワークスペースファイル保存済み: {name}',
409
413
  'toast.delete.ok': '削除しました',
@@ -488,6 +492,7 @@ const ja = Object.freeze({
488
492
  'modal.configTemplate.mode.twoStep': '二段階確認:先に差分をプレビューし、その後適用します。',
489
493
  'modal.configTemplate.mode.oneStep': '一段階適用:「適用」をクリックすると直接書き込みます。',
490
494
  'diff.title.configTemplate': '差分プレビュー(config.toml)',
495
+ 'diff.title.claudeSettings': '差分プレビュー(settings.json)',
491
496
  'diff.generating': '生成中...',
492
497
  'diff.failed': '生成失敗',
493
498
  'diff.noChanges': '変更が検出されませんでした',
@@ -860,12 +865,14 @@ const ja = Object.freeze({
860
865
  'config.autoCompact.hint': '自動圧縮閾値、デフォルト 185000。',
861
866
  'config.agents.open': 'AGENTS.md を開く',
862
867
  'modal.agents.title.default': 'AGENTS.md エディタ',
863
- 'modal.agents.title.claudeMd': 'CLAUDE.md エディタ',
864
868
  'modal.agents.title.openclaw': 'OpenClaw AGENTS.md エディタ',
865
869
  'modal.agents.hint.default': '保存すると対象の AGENTS.md(config.toml と同階層)に書き込まれます。',
866
- 'modal.agents.hint.claudeMd': '保存すると ~/.claude/CLAUDE.md に書き込まれます。',
867
- 'modal.agents.contentLabel.claudeMd': 'CLAUDE.md 内容',
868
- 'modal.agents.placeholder.claudeMd': 'ここに CLAUDE.md の内容を編集してください',
870
+ 'modal.agents.title.claudeProject': 'プロジェクト CLAUDE.md: {path}',
871
+ 'modal.agents.hint.claudeProject': '保存するとプロジェクトの CLAUDE.md に書き込まれます: {path}',
872
+ 'modal.agents.title.claudeProjectGlobal': 'グローバル CLAUDE.md',
873
+ 'modal.agents.hint.claudeProjectGlobal': '保存すると ~/.claude/CLAUDE.md に書き込まれます。',
874
+ 'modal.agents.contentLabel.claudeProject': 'CLAUDE.md 内容',
875
+ 'modal.agents.placeholder.claudeProject': 'CLAUDE.md を編集',
869
876
  'modal.agents.hint.openclaw': '保存すると OpenClaw Workspace の AGENTS.md に書き込まれます。',
870
877
  'modal.agents.title.openclawWorkspaceFile': 'OpenClaw ワークスペースファイル: {fileName}',
871
878
  'modal.agents.hint.openclawWorkspaceFile': '保存すると OpenClaw Workspace の {fileName} に書き込まれます。',
@@ -1203,6 +1210,10 @@ const ja = Object.freeze({
1203
1210
  'claude.model': 'モデル',
1204
1211
  'claude.model.placeholder': '例: claude-3-7-sonnet',
1205
1212
  'claude.model.hint': 'モデル変更後は自動保存され、現在の設定に適用されます。',
1213
+ 'claude.model.haiku': 'Haiku モデル',
1214
+ 'claude.model.sonnet': 'Sonnet モデル',
1215
+ 'claude.model.opus': 'Opus モデル',
1216
+ 'claude.model.sub.placeholder': '空欄の場合、メインモデルに従います',
1206
1217
  'claude.targetApi.label': 'ターゲット API',
1207
1218
  'claude.targetApi.responses': 'Anthropic',
1208
1219
  'claude.targetApi.chatCompletions': 'OpenAI Chat Completions (/v1/chat/completions)',
@@ -234,7 +234,11 @@ const vi = Object.freeze({
234
234
  'subtitle.settings': 'Quản lý tải xuống, thư mục và dữ liệu.',
235
235
  'subtitle.prompts': 'Chỉnh sửa AGENTS.md và CLAUDE.md.',
236
236
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
237
- 'prompts.subTab.claude': 'CLAUDE.md (Claude)',
237
+ 'prompts.subTab.project': 'CLAUDE.md',
238
+ 'prompts.project.pathLabel': 'Đường dẫn dự án (tùy chọn)',
239
+ 'prompts.project.selectPlaceholder': '-- Toàn cục (~/.claude/CLAUDE.md) --',
240
+ 'prompts.project.manualPlaceholder': 'Hoặc nhập đường dẫn gốc dự án...',
241
+ 'prompts.project.detected': 'Đang chỉnh sửa CLAUDE.md trong: {path}',
238
242
 
239
243
 
240
244
  // Task orchestration readiness
@@ -317,7 +321,7 @@ const vi = Object.freeze({
317
321
  'toast.save.ok': 'Đã lưu',
318
322
  'toast.save.fail': 'Lưu thất bại',
319
323
  'toast.agents.saved.agents': 'AGENTS.md đã lưu',
320
- 'toast.agents.saved.claudeMd': 'CLAUDE.md đã lưu',
324
+ 'toast.agents.saved.claudeProject': 'CLAUDE.md dự án đã lưu',
321
325
  'toast.agents.saved.openclaw': 'OpenClaw AGENTS.md đã lưu',
322
326
  'toast.agents.saved.workspace': 'Tệp workspace đã lưu: {name}',
323
327
  'toast.delete.ok': 'Đã xóa',
@@ -362,6 +366,10 @@ const vi = Object.freeze({
362
366
  'validation.claude.baseUrlRequired': 'Base URL là bắt buộc',
363
367
  'validation.claude.baseUrlHttpOnly': 'Base URL chỉ hỗ trợ http/https',
364
368
  'validation.claude.modelRequired': 'Tên mô hình là bắt buộc',
369
+ 'claude.model.haiku': 'Mô hình Haiku',
370
+ 'claude.model.sonnet': 'Mô hình Sonnet',
371
+ 'claude.model.opus': 'Mô hình Opus',
372
+ 'claude.model.sub.placeholder': 'Để trống sẽ dùng mô hình chính',
365
373
  'modal.claudeDelete.title': 'Xóa cấu hình Claude',
366
374
  'modal.claudeDelete.message': 'Xóa cấu hình "{name}"?',
367
375
  'modal.claudeDelete.confirm': 'Xóa',
@@ -381,6 +389,12 @@ const vi = Object.freeze({
381
389
  'diff.hint.previewMode': 'Đang xem trước. Nhấp "Lưu" để ghi hoặc "Quay lại chỉnh sửa" để tiếp tục.',
382
390
  'modal.agents.unsaved.detectedHint': 'Phát hiện thay đổi chưa lưu: lưu trước khi đóng hoặc áp dụng.',
383
391
  'modal.agents.hint.twoStepSave': 'Lưu hai bước: "Xem trước" để kiểm tra diff, rồi "Lưu" để ghi.',
392
+ 'modal.agents.title.claudeProject': 'CLAUDE.md dự án: {path}',
393
+ 'modal.agents.hint.claudeProject': 'Nội dung sẽ được ghi vào CLAUDE.md trong dự án: {path}',
394
+ 'modal.agents.title.claudeProjectGlobal': 'CLAUDE.md toàn cục',
395
+ 'modal.agents.hint.claudeProjectGlobal': 'Nội dung sẽ được ghi vào ~/.claude/CLAUDE.md.',
396
+ 'modal.agents.contentLabel.claudeProject': 'Nội dung CLAUDE.md',
397
+ 'modal.agents.placeholder.claudeProject': 'Chỉnh sửa CLAUDE.md tại đây',
384
398
  'diff.viewHint.preview': 'Đang xem trước. Nhấp "Lưu" để ghi hoặc "Quay lại chỉnh sửa" để tiếp tục.',
385
399
  'diff.viewHint.truncated': 'Bỏ qua xem trước do nội dung quá lớn. Nhấp "Lưu" để ghi hoặc "Quay lại chỉnh sửa" để tiếp tục.',
386
400
  });
@@ -220,7 +220,11 @@ const zhTw = Object.freeze({
220
220
  'subtitle.settings': '管理下載、目錄與回收站。',
221
221
  'subtitle.prompts': '編輯 AGENTS.md 與 CLAUDE.md。',
222
222
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
223
- 'prompts.subTab.claude': 'CLAUDE.md (Claude)',
223
+ 'prompts.subTab.project': 'CLAUDE.md',
224
+ 'prompts.project.pathLabel': '專案路徑(可選)',
225
+ 'prompts.project.selectPlaceholder': '-- 全域 (~/.claude/CLAUDE.md) --',
226
+ 'prompts.project.manualPlaceholder': '或手動輸入專案根路徑...',
227
+ 'prompts.project.detected': '正在編輯: {path}',
224
228
  'dashboard.doctor.title': 'Doctor',
225
229
  'dashboard.doctor.runChecks': '運行檢查',
226
230
  'dashboard.doctor.checking': '檢查中...',
@@ -401,7 +405,7 @@ const zhTw = Object.freeze({
401
405
  'toast.save.ok': '已保存',
402
406
  'toast.save.fail': '保存失敗',
403
407
  'toast.agents.saved.agents': 'AGENTS.md 已保存',
404
- 'toast.agents.saved.claudeMd': 'CLAUDE.md 已保存',
408
+ 'toast.agents.saved.claudeProject': '專案 CLAUDE.md 已保存',
405
409
  'toast.agents.saved.openclaw': 'OpenClaw AGENTS.md 已保存',
406
410
  'toast.agents.saved.workspace': '工作區文件已保存: {name}',
407
411
  'toast.delete.ok': '已刪除',
@@ -486,6 +490,7 @@ const zhTw = Object.freeze({
486
490
  'modal.configTemplate.mode.twoStep': '兩步確認:先預覽差異,再應用寫入。',
487
491
  'modal.configTemplate.mode.oneStep': '一步應用:點擊“應用”直接寫入。',
488
492
  'diff.title.configTemplate': '差異預覽(config.toml)',
493
+ 'diff.title.claudeSettings': '差異預覽(settings.json)',
489
494
  'diff.generating': '生成中...',
490
495
  'diff.failed': '生成失敗',
491
496
  'diff.noChanges': '未檢測到改動',
@@ -870,12 +875,14 @@ const zhTw = Object.freeze({
870
875
  'config.autoCompact.hint': '自動壓縮閾值,預設 185000。',
871
876
  'config.agents.open': '打開 AGENTS.md',
872
877
  'modal.agents.title.default': 'AGENTS.md 編輯器',
873
- 'modal.agents.title.claudeMd': 'CLAUDE.md 編輯器',
874
878
  'modal.agents.title.openclaw': 'OpenClaw AGENTS.md 編輯器',
875
879
  'modal.agents.hint.default': '保存後會寫入目標 AGENTS.md(與 config.toml 同級)。',
876
- 'modal.agents.hint.claudeMd': '保存後會寫入 ~/.claude/CLAUDE.md',
877
- 'modal.agents.contentLabel.claudeMd': 'CLAUDE.md 內容',
878
- 'modal.agents.placeholder.claudeMd': '在這裡編輯 CLAUDE.md 內容',
880
+ 'modal.agents.title.claudeProject': '專案 CLAUDE.md: {path}',
881
+ 'modal.agents.hint.claudeProject': '保存後會寫入專案目錄下的 CLAUDE.md: {path}',
882
+ 'modal.agents.title.claudeProjectGlobal': '全域 CLAUDE.md',
883
+ 'modal.agents.hint.claudeProjectGlobal': '保存後會寫入 ~/.claude/CLAUDE.md。',
884
+ 'modal.agents.contentLabel.claudeProject': 'CLAUDE.md 內容',
885
+ 'modal.agents.placeholder.claudeProject': '在這裡編輯 CLAUDE.md',
879
886
  'modal.agents.hint.openclaw': '保存後會寫入 OpenClaw Workspace 下的 AGENTS.md。',
880
887
  'modal.agents.title.openclawWorkspaceFile': 'OpenClaw 工作區文件: {fileName}',
881
888
  'modal.agents.hint.openclawWorkspaceFile': '保存後會寫入 OpenClaw Workspace 下的 {fileName}。',
@@ -1213,6 +1220,10 @@ const zhTw = Object.freeze({
1213
1220
  'claude.model': '模型',
1214
1221
  'claude.model.placeholder': '例如: claude-3-7-sonnet',
1215
1222
  'claude.model.hint': '模型修改後會自動保存並應用到目前設定。',
1223
+ 'claude.model.haiku': 'Haiku 模型',
1224
+ 'claude.model.sonnet': 'Sonnet 模型',
1225
+ 'claude.model.opus': 'Opus 模型',
1226
+ 'claude.model.sub.placeholder': '留空則跟隨主模型',
1216
1227
  'claude.targetApi.label': '目標 API',
1217
1228
  'claude.targetApi.responses': 'Anthropic',
1218
1229
  'claude.targetApi.chatCompletions': 'OpenAI Chat Completions (/v1/chat/completions)',
@@ -220,7 +220,11 @@ const zh = Object.freeze({
220
220
  'subtitle.settings': '管理下载、目录与回收站。',
221
221
  'subtitle.prompts': '编辑 AGENTS.md 与 CLAUDE.md。',
222
222
  'prompts.subTab.codex': 'AGENTS.md (Codex)',
223
- 'prompts.subTab.claude': 'CLAUDE.md (Claude)',
223
+ 'prompts.subTab.project': 'CLAUDE.md',
224
+ 'prompts.project.pathLabel': '项目路径(可选)',
225
+ 'prompts.project.selectPlaceholder': '-- 全局 (~/.claude/CLAUDE.md) --',
226
+ 'prompts.project.manualPlaceholder': '或手动输入项目根路径...',
227
+ 'prompts.project.detected': '正在编辑: {path}',
224
228
  'dashboard.doctor.title': 'Doctor',
225
229
  'dashboard.doctor.runChecks': '运行检查',
226
230
  'dashboard.doctor.checking': '检查中...',
@@ -401,7 +405,7 @@ const zh = Object.freeze({
401
405
  'toast.save.ok': '已保存',
402
406
  'toast.save.fail': '保存失败',
403
407
  'toast.agents.saved.agents': 'AGENTS.md 已保存',
404
- 'toast.agents.saved.claudeMd': 'CLAUDE.md 已保存',
408
+ 'toast.agents.saved.claudeProject': '项目 CLAUDE.md 已保存',
405
409
  'toast.agents.saved.openclaw': 'OpenClaw AGENTS.md 已保存',
406
410
  'toast.agents.saved.workspace': '工作区文件已保存: {name}',
407
411
  'toast.delete.ok': '已删除',
@@ -486,6 +490,7 @@ const zh = Object.freeze({
486
490
  'modal.configTemplate.mode.twoStep': '两步确认:先预览差异,再应用写入。',
487
491
  'modal.configTemplate.mode.oneStep': '一步应用:点击“应用”直接写入。',
488
492
  'diff.title.configTemplate': '差异预览(config.toml)',
493
+ 'diff.title.claudeSettings': '差异预览(settings.json)',
489
494
  'diff.generating': '生成中...',
490
495
  'diff.failed': '生成失败',
491
496
  'diff.noChanges': '未检测到改动',
@@ -870,12 +875,14 @@ const zh = Object.freeze({
870
875
  'config.autoCompact.hint': '自动压缩阈值,默认 185000。',
871
876
  'config.agents.open': '打开 AGENTS.md',
872
877
  'modal.agents.title.default': 'AGENTS.md 编辑器',
873
- 'modal.agents.title.claudeMd': 'CLAUDE.md 编辑器',
874
878
  'modal.agents.title.openclaw': 'OpenClaw AGENTS.md 编辑器',
875
879
  'modal.agents.hint.default': '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
876
- 'modal.agents.hint.claudeMd': '保存后会写入 ~/.claude/CLAUDE.md',
877
- 'modal.agents.contentLabel.claudeMd': 'CLAUDE.md 内容',
878
- 'modal.agents.placeholder.claudeMd': '在这里编辑 CLAUDE.md 内容',
880
+ 'modal.agents.title.claudeProject': '项目 CLAUDE.md: {path}',
881
+ 'modal.agents.hint.claudeProject': '保存后会写入项目目录下的 CLAUDE.md: {path}',
882
+ 'modal.agents.title.claudeProjectGlobal': '全局 CLAUDE.md',
883
+ 'modal.agents.hint.claudeProjectGlobal': '保存后会写入 ~/.claude/CLAUDE.md。',
884
+ 'modal.agents.contentLabel.claudeProject': 'CLAUDE.md 内容',
885
+ 'modal.agents.placeholder.claudeProject': '在这里编辑 CLAUDE.md',
879
886
  'modal.agents.hint.openclaw': '保存后会写入 OpenClaw Workspace 下的 AGENTS.md。',
880
887
  'modal.agents.title.openclawWorkspaceFile': 'OpenClaw 工作区文件: {fileName}',
881
888
  'modal.agents.hint.openclawWorkspaceFile': '保存后会写入 OpenClaw Workspace 下的 {fileName}。',
@@ -1213,6 +1220,10 @@ const zh = Object.freeze({
1213
1220
  'claude.model': '模型',
1214
1221
  'claude.model.placeholder': '例如: claude-3-7-sonnet',
1215
1222
  'claude.model.hint': '模型修改后会自动保存并应用到当前配置。',
1223
+ 'claude.model.haiku': 'Haiku 模型',
1224
+ 'claude.model.sonnet': 'Sonnet 模型',
1225
+ 'claude.model.opus': 'Opus 模型',
1226
+ 'claude.model.sub.placeholder': '留空则跟随主模型',
1216
1227
  'claude.targetApi.label': '目标 API',
1217
1228
  'claude.targetApi.responses': 'Anthropic',
1218
1229
  'claude.targetApi.chatCompletions': 'OpenAI Chat Completions (/v1/chat/completions)',
@@ -255,13 +255,13 @@
255
255
  </div>
256
256
  </button>
257
257
  <button
258
- id="side-tab-prompts-claude"
258
+ id="side-tab-prompts-project"
259
259
  data-main-tab="prompts"
260
- data-prompts-sub-tab="claude-md"
261
- :aria-current="mainTab === 'prompts' && promptsSubTab === 'claude-md' ? 'page' : null"
262
- :class="['side-item', { active: isMainTabNavActive('prompts') && promptsSubTab === 'claude-md' }]"
260
+ data-prompts-sub-tab="claude-project"
261
+ :aria-current="mainTab === 'prompts' && promptsSubTab === 'claude-project' ? 'page' : null"
262
+ :class="['side-item', { active: isMainTabNavActive('prompts') && promptsSubTab === 'claude-project' }]"
263
263
  @pointerdown="onMainTabPointerDown('prompts', $event)"
264
- @click="switchPromptsSubTab('claude-md'); onMainTabClick('prompts')">
264
+ @click="switchPromptsSubTab('claude-project'); onMainTabClick('prompts')">
265
265
  <div class="side-item-title">{{ t('side.prompts.claude') }}</div>
266
266
  <div class="side-item-meta">
267
267
  <span>{{ t('side.prompts.claude.meta') }}</span>
@@ -12,7 +12,7 @@
12
12
  <div v-if="configTemplateDiffVisible" class="agents-diff-container">
13
13
  <div class="agents-diff-header">
14
14
  <div class="agents-diff-title">
15
- {{ t('diff.title.configTemplate') }}
15
+ {{ configTemplateContext === 'claude' ? t('diff.title.claudeSettings') : t('diff.title.configTemplate') }}
16
16
  <span v-if="configTemplateDiffLoading" class="agents-diff-subtitle">{{ t('diff.generating') }}</span>
17
17
  <span v-else-if="configTemplateDiffError" class="agents-diff-subtitle">{{ t('diff.failed') }}</span>
18
18
  <span v-else-if="!configTemplateDiffHasChanges" class="agents-diff-subtitle">{{ t('diff.noChanges') }}</span>
@@ -130,7 +130,7 @@
130
130
 
131
131
 
132
132
  <div class="form-group">
133
- <label class="form-label">{{ t(agentsContext === 'claude-md' ? 'modal.agents.contentLabel.claudeMd' : 'modal.agents.contentLabel') }}</label>
133
+ <label class="form-label">{{ t(agentsContext === 'claude-project' ? 'modal.agents.contentLabel.claudeProject' : 'modal.agents.contentLabel') }}</label>
134
134
  <div
135
135
  v-if="!agentsLoading && (hasAgentsContentChanged() || agentsDiffVisible)"
136
136
  class="agents-diff-save-alert">
@@ -165,7 +165,7 @@
165
165
  spellcheck="false"
166
166
  :readonly="agentsLoading || agentsSaving || agentsDiffVisible"
167
167
  @input="onAgentsContentInput"
168
- :placeholder="t(agentsContext === 'claude-md' ? 'modal.agents.placeholder.claudeMd' : 'modal.agents.placeholder')"></textarea>
168
+ :placeholder="t(agentsContext === 'claude-project' ? 'modal.agents.placeholder.claudeProject' : 'modal.agents.placeholder')"></textarea>
169
169
  <div class="template-editor-warning">
170
170
  {{ agentsModalHint }}
171
171
  <div class="agents-diff-hint">{{ t('modal.agents.hint.shortcuts') }}</div>
@@ -7,7 +7,29 @@
7
7
  aria-labelledby="tab-prompts">
8
8
  <div class="segmented-control">
9
9
  <button type="button" :class="['segment', { active: promptsSubTab === 'codex' }]" @click="switchPromptsSubTab('codex')">{{ t('prompts.subTab.codex') }}</button>
10
- <button type="button" :class="['segment', { active: promptsSubTab === 'claude-md' }]" @click="switchPromptsSubTab('claude-md')">{{ t('prompts.subTab.claude') }}</button>
10
+ <button type="button" :class="['segment', { active: promptsSubTab === 'claude-project' }]" @click="switchPromptsSubTab('claude-project')">{{ t('prompts.subTab.project') }}</button>
11
+ </div>
12
+
13
+ <div v-if="promptsSubTab === 'claude-project'" class="project-path-selector">
14
+ <label class="project-path-label">{{ t('prompts.project.pathLabel') }}</label>
15
+ <div class="project-path-row">
16
+ <select
17
+ class="form-input project-path-select"
18
+ :value="projectClaudeMdPath"
19
+ @change="selectProjectClaudeMdPath($event.target.value)">
20
+ <option value="">{{ t('prompts.project.selectPlaceholder') }}</option>
21
+ <option v-for="p in projectPathOptions" :key="p" :value="p">{{ p }}</option>
22
+ </select>
23
+ <input
24
+ type="text"
25
+ class="form-input project-path-manual"
26
+ :placeholder="t('prompts.project.manualPlaceholder')"
27
+ :value="projectClaudeMdPath"
28
+ @change="setProjectClaudeMdPathManual($event.target.value)">
29
+ </div>
30
+ <div v-if="projectClaudeMdPath" class="form-hint project-path-detected">
31
+ {{ t('prompts.project.detected', { path: projectClaudeMdPath }) }}
32
+ </div>
11
33
  </div>
12
34
 
13
35
  <div class="prompts-editor">
@@ -89,7 +111,7 @@
89
111
  spellcheck="false"
90
112
  :readonly="agentsLoading || agentsSaving || agentsDiffVisible"
91
113
  @input="onAgentsContentInput"
92
- :placeholder="t(promptsSubTab === 'claude-md' ? 'modal.agents.placeholder.claudeMd' : 'modal.agents.placeholder')"></textarea>
114
+ :placeholder="t(promptsSubTab === 'claude-project' ? 'modal.agents.placeholder.claudeProject' : 'modal.agents.placeholder')"></textarea>
93
115
  </div>
94
116
  <div v-if="promptsContextHint" :class="['prompts-context-hint', { 'prompts-context-hint--warn': promptsContextHint.warn }]">
95
117
  {{ promptsContextHint.text }}
@@ -351,13 +351,13 @@ return function render(_ctx, _cache) {
351
351
  ])
352
352
  ], 42 /* CLASS, PROPS, NEED_HYDRATION */, ["aria-current", "onPointerdown", "onClick"]),
353
353
  _createElementVNode("button", {
354
- id: "side-tab-prompts-claude",
354
+ id: "side-tab-prompts-project",
355
355
  "data-main-tab": "prompts",
356
- "data-prompts-sub-tab": "claude-md",
357
- "aria-current": _ctx.mainTab === 'prompts' && _ctx.promptsSubTab === 'claude-md' ? 'page' : null,
358
- class: _normalizeClass(['side-item', { active: _ctx.isMainTabNavActive('prompts') && _ctx.promptsSubTab === 'claude-md' }]),
356
+ "data-prompts-sub-tab": "claude-project",
357
+ "aria-current": _ctx.mainTab === 'prompts' && _ctx.promptsSubTab === 'claude-project' ? 'page' : null,
358
+ class: _normalizeClass(['side-item', { active: _ctx.isMainTabNavActive('prompts') && _ctx.promptsSubTab === 'claude-project' }]),
359
359
  onPointerdown: $event => (_ctx.onMainTabPointerDown('prompts', $event)),
360
- onClick: $event => {_ctx.switchPromptsSubTab('claude-md'); _ctx.onMainTabClick('prompts')}
360
+ onClick: $event => {_ctx.switchPromptsSubTab('claude-project'); _ctx.onMainTabClick('prompts')}
361
361
  }, [
362
362
  _createElementVNode("div", { class: "side-item-title" }, _toDisplayString(_ctx.t('side.prompts.claude')), 1 /* TEXT */),
363
363
  _createElementVNode("div", { class: "side-item-meta" }, [
@@ -5977,10 +5977,46 @@ return function render(_ctx, _cache) {
5977
5977
  }, _toDisplayString(_ctx.t('prompts.subTab.codex')), 11 /* TEXT, CLASS, PROPS */, ["onClick"]),
5978
5978
  _createElementVNode("button", {
5979
5979
  type: "button",
5980
- class: _normalizeClass(['segment', { active: _ctx.promptsSubTab === 'claude-md' }]),
5981
- onClick: $event => (_ctx.switchPromptsSubTab('claude-md'))
5982
- }, _toDisplayString(_ctx.t('prompts.subTab.claude')), 11 /* TEXT, CLASS, PROPS */, ["onClick"])
5980
+ class: _normalizeClass(['segment', { active: _ctx.promptsSubTab === 'claude-project' }]),
5981
+ onClick: $event => (_ctx.switchPromptsSubTab('claude-project'))
5982
+ }, _toDisplayString(_ctx.t('prompts.subTab.project')), 11 /* TEXT, CLASS, PROPS */, ["onClick"])
5983
5983
  ]),
5984
+ (_ctx.promptsSubTab === 'claude-project')
5985
+ ? (_openBlock(), _createElementBlock("div", {
5986
+ key: 0,
5987
+ class: "project-path-selector"
5988
+ }, [
5989
+ _createElementVNode("label", { class: "project-path-label" }, _toDisplayString(_ctx.t('prompts.project.pathLabel')), 1 /* TEXT */),
5990
+ _createElementVNode("div", { class: "project-path-row" }, [
5991
+ _createElementVNode("select", {
5992
+ class: "form-input project-path-select",
5993
+ value: _ctx.projectClaudeMdPath,
5994
+ onChange: $event => (_ctx.selectProjectClaudeMdPath($event.target.value))
5995
+ }, [
5996
+ _createElementVNode("option", { value: "" }, _toDisplayString(_ctx.t('prompts.project.selectPlaceholder')), 1 /* TEXT */),
5997
+ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.projectPathOptions, (p) => {
5998
+ return (_openBlock(), _createElementBlock("option", {
5999
+ key: p,
6000
+ value: p
6001
+ }, _toDisplayString(p), 9 /* TEXT, PROPS */, ["value"]))
6002
+ }), 128 /* KEYED_FRAGMENT */))
6003
+ ], 40 /* PROPS, NEED_HYDRATION */, ["value", "onChange"]),
6004
+ _createElementVNode("input", {
6005
+ type: "text",
6006
+ class: "form-input project-path-manual",
6007
+ placeholder: _ctx.t('prompts.project.manualPlaceholder'),
6008
+ value: _ctx.projectClaudeMdPath,
6009
+ onChange: $event => (_ctx.setProjectClaudeMdPathManual($event.target.value))
6010
+ }, null, 40 /* PROPS, NEED_HYDRATION */, ["placeholder", "value", "onChange"])
6011
+ ]),
6012
+ (_ctx.projectClaudeMdPath)
6013
+ ? (_openBlock(), _createElementBlock("div", {
6014
+ key: 0,
6015
+ class: "form-hint project-path-detected"
6016
+ }, _toDisplayString(_ctx.t('prompts.project.detected', { path: _ctx.projectClaudeMdPath })), 1 /* TEXT */))
6017
+ : _createCommentVNode("v-if", true)
6018
+ ]))
6019
+ : _createCommentVNode("v-if", true),
5984
6020
  _createElementVNode("div", { class: "prompts-editor" }, [
5985
6021
  _createElementVNode("div", { class: "prompts-editor-toolbar" }, [
5986
6022
  _createElementVNode("div", { class: "form-hint" }, [
@@ -6099,7 +6135,7 @@ return function render(_ctx, _cache) {
6099
6135
  spellcheck: "false",
6100
6136
  readonly: _ctx.agentsLoading || _ctx.agentsSaving || _ctx.agentsDiffVisible,
6101
6137
  onInput: _ctx.onAgentsContentInput,
6102
- placeholder: _ctx.t(_ctx.promptsSubTab === 'claude-md' ? 'modal.agents.placeholder.claudeMd' : 'modal.agents.placeholder')
6138
+ placeholder: _ctx.t(_ctx.promptsSubTab === 'claude-project' ? 'modal.agents.placeholder.claudeProject' : 'modal.agents.placeholder')
6103
6139
  }, null, 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "readonly", "onInput", "placeholder"]), [
6104
6140
  [_vModelText, _ctx.agentsContent]
6105
6141
  ])
@@ -7794,7 +7830,7 @@ return function render(_ctx, _cache) {
7794
7830
  }, [
7795
7831
  _createElementVNode("div", { class: "agents-diff-header" }, [
7796
7832
  _createElementVNode("div", { class: "agents-diff-title" }, [
7797
- _createTextVNode(_toDisplayString(_ctx.t('diff.title.configTemplate')) + " ", 1 /* TEXT */),
7833
+ _createTextVNode(_toDisplayString(_ctx.configTemplateContext === 'claude' ? _ctx.t('diff.title.claudeSettings') : _ctx.t('diff.title.configTemplate')) + " ", 1 /* TEXT */),
7798
7834
  (_ctx.configTemplateDiffLoading)
7799
7835
  ? (_openBlock(), _createElementBlock("span", {
7800
7836
  key: 0,
@@ -7970,7 +8006,7 @@ return function render(_ctx, _cache) {
7970
8006
  ])
7971
8007
  ]),
7972
8008
  _createElementVNode("div", { class: "form-group" }, [
7973
- _createElementVNode("label", { class: "form-label" }, _toDisplayString(_ctx.t(_ctx.agentsContext === 'claude-md' ? 'modal.agents.contentLabel.claudeMd' : 'modal.agents.contentLabel')), 1 /* TEXT */),
8009
+ _createElementVNode("label", { class: "form-label" }, _toDisplayString(_ctx.t(_ctx.agentsContext === 'claude-project' ? 'modal.agents.contentLabel.claudeProject' : 'modal.agents.contentLabel')), 1 /* TEXT */),
7974
8010
  (!_ctx.agentsLoading && (_ctx.hasAgentsContentChanged() || _ctx.agentsDiffVisible))
7975
8011
  ? (_openBlock(), _createElementBlock("div", {
7976
8012
  key: 0,
@@ -8030,7 +8066,7 @@ return function render(_ctx, _cache) {
8030
8066
  spellcheck: "false",
8031
8067
  readonly: _ctx.agentsLoading || _ctx.agentsSaving || _ctx.agentsDiffVisible,
8032
8068
  onInput: _ctx.onAgentsContentInput,
8033
- placeholder: _ctx.t(_ctx.agentsContext === 'claude-md' ? 'modal.agents.placeholder.claudeMd' : 'modal.agents.placeholder')
8069
+ placeholder: _ctx.t(_ctx.agentsContext === 'claude-project' ? 'modal.agents.placeholder.claudeProject' : 'modal.agents.placeholder')
8034
8070
  }, null, 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "readonly", "onInput", "placeholder"]), [
8035
8071
  [_vModelText, _ctx.agentsContent]
8036
8072
  ]),
@@ -580,6 +580,39 @@
580
580
  border-left: 1px solid var(--color-border-soft);
581
581
  }
582
582
 
583
+ .project-path-selector {
584
+ margin-top: 8px;
585
+ margin-bottom: 4px;
586
+ }
587
+
588
+ .project-path-label {
589
+ display: block;
590
+ font-size: 0.85em;
591
+ color: var(--color-text-secondary);
592
+ margin-bottom: 4px;
593
+ }
594
+
595
+ .project-path-row {
596
+ display: flex;
597
+ gap: 8px;
598
+ align-items: center;
599
+ }
600
+
601
+ .project-path-select {
602
+ flex: 1;
603
+ min-width: 0;
604
+ }
605
+
606
+ .project-path-manual {
607
+ flex: 1;
608
+ min-width: 0;
609
+ }
610
+
611
+ .project-path-detected {
612
+ margin-top: 4px;
613
+ font-size: 0.82em;
614
+ }
615
+
583
616
  .editor-frame {
584
617
  position: relative;
585
618
  border-radius: var(--radius-md);
@@ -55,14 +55,14 @@
55
55
  color: var(--color-text-secondary);
56
56
  cursor: pointer;
57
57
  border-radius: var(--radius-sm);
58
- transition: color var(--transition-fast) var(--ease-smooth), background-color var(--transition-fast) var(--ease-smooth);
59
58
  position: relative;
60
59
  z-index: 2;
61
60
  letter-spacing: 0;
62
61
  }
63
62
 
64
- .segment:hover {
63
+ .segment:not(.active):hover {
65
64
  color: var(--color-text-primary);
65
+ transition: color var(--transition-fast) var(--ease-smooth), background-color var(--transition-fast) var(--ease-smooth);
66
66
  }
67
67
 
68
68
  .segment.active {