cc-viewer 1.7.5 → 1.7.7

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.
Files changed (88) hide show
  1. package/README.md +17 -0
  2. package/cli.js +41 -16
  3. package/dist/assets/App-BfzeNwOB.css +1 -0
  4. package/dist/assets/App-Bqz6Y5h4.js +2 -0
  5. package/dist/assets/{MdxEditorPanel-CAqHHy1X.js → MdxEditorPanel-BAE_5ssH.js} +1 -1
  6. package/dist/assets/{Mobile-BxRd4DkS.js → Mobile-2LDWfaUe.js} +1 -1
  7. package/dist/assets/{UnifiedProxyRetryPage-DjL3NQXO.js → UnifiedProxyRetryPage-DFyS9doh.js} +1 -1
  8. package/dist/assets/{_baseUniq-yiNE5Ckw.js → _baseUniq-BkQkqbX3.js} +1 -1
  9. package/dist/assets/{arc-M-eBYYbN.js → arc-BKY9CS7c.js} +1 -1
  10. package/dist/assets/{architectureDiagram-Q4EWVU46-CtweeLKi.js → architectureDiagram-Q4EWVU46-f8VVp6F9.js} +1 -1
  11. package/dist/assets/{blockDiagram-DXYQGD6D-DCVQ-go_.js → blockDiagram-DXYQGD6D-3h2PkZUH.js} +1 -1
  12. package/dist/assets/{c4Diagram-AHTNJAMY-C7JB9PqP.js → c4Diagram-AHTNJAMY-DigIDzhn.js} +1 -1
  13. package/dist/assets/{channel-BnzX9zBS.js → channel-5ajs7xDK.js} +1 -1
  14. package/dist/assets/{chunk-4BX2VUAB-dVKiUtqe.js → chunk-4BX2VUAB-BvwhA1RA.js} +1 -1
  15. package/dist/assets/{chunk-4TB4RGXK-CQjD9-fD.js → chunk-4TB4RGXK-GHcSNG2k.js} +1 -1
  16. package/dist/assets/{chunk-55IACEB6-BZuKI6JQ.js → chunk-55IACEB6-DWO0TwNe.js} +1 -1
  17. package/dist/assets/{chunk-EDXVE4YY-sk9g7szp.js → chunk-EDXVE4YY-0ycrM2oE.js} +1 -1
  18. package/dist/assets/{chunk-FMBD7UC4-CGL9Lo51.js → chunk-FMBD7UC4-DmdCml-0.js} +1 -1
  19. package/dist/assets/{chunk-OYMX7WX6-DaN0AdQD.js → chunk-OYMX7WX6-fg2n9B_W.js} +1 -1
  20. package/dist/assets/{chunk-QZHKN3VN-CNEAHnZV.js → chunk-QZHKN3VN-Bg3VPGcJ.js} +1 -1
  21. package/dist/assets/{chunk-YZCP3GAM-BOlts_Dm.js → chunk-YZCP3GAM-BV1RGRqK.js} +1 -1
  22. package/dist/assets/classDiagram-6PBFFD2Q-CnUCTEpg.js +1 -0
  23. package/dist/assets/classDiagram-v2-HSJHXN6E-CnUCTEpg.js +1 -0
  24. package/dist/assets/clone-D0NlqTo0.js +1 -0
  25. package/dist/assets/{cose-bilkent-S5V4N54A-DH4_YiGi.js → cose-bilkent-S5V4N54A-CjAVlyw6.js} +1 -1
  26. package/dist/assets/{dagre-KV5264BT-RqusStYI.js → dagre-KV5264BT-DZV42aOy.js} +1 -1
  27. package/dist/assets/{diagram-5BDNPKRD-8haVbBnt.js → diagram-5BDNPKRD-DpDA9ZeE.js} +1 -1
  28. package/dist/assets/{diagram-G4DWMVQ6-CsrJNUhT.js → diagram-G4DWMVQ6-86vmHGG_.js} +1 -1
  29. package/dist/assets/{diagram-MMDJMWI5-1luPOM38.js → diagram-MMDJMWI5-CzLVfy9C.js} +1 -1
  30. package/dist/assets/{diagram-TYMM5635-Dad1XR3W.js → diagram-TYMM5635-C1n6QVNv.js} +1 -1
  31. package/dist/assets/{erDiagram-SMLLAGMA-Br6-kbp9.js → erDiagram-SMLLAGMA-BWST8PqT.js} +1 -1
  32. package/dist/assets/{flowDiagram-DWJPFMVM-DuX1mbA7.js → flowDiagram-DWJPFMVM-zfku-qzI.js} +1 -1
  33. package/dist/assets/{ganttDiagram-T4ZO3ILL-BL_XQ958.js → ganttDiagram-T4ZO3ILL-BIxQMxEL.js} +1 -1
  34. package/dist/assets/{gitGraphDiagram-UUTBAWPF-DzvW5FEs.js → gitGraphDiagram-UUTBAWPF-E7gXMGl0.js} +1 -1
  35. package/dist/assets/{graph-CbUmu0Oe.js → graph-Chi_Do3A.js} +1 -1
  36. package/dist/assets/{index-B3-JUU7J.js → index-BVkdlwjO.js} +1 -1
  37. package/dist/assets/{index-CzPHj3iF.js → index-Batfx707.js} +1 -1
  38. package/dist/assets/{index-BgW_nP_0.js → index-BbuqKcgW.js} +1 -1
  39. package/dist/assets/index-Bp83Qa3f.js +2 -0
  40. package/dist/assets/{index-vImWXiMM.js → index-BrmFDbRb.js} +1 -1
  41. package/dist/assets/{index-DDlAMOiV.js → index-BsRFLaCT.js} +1 -1
  42. package/dist/assets/{index-Do5xNTOT.js → index-CaTBBfzw.js} +1 -1
  43. package/dist/assets/{index-LjVy-Pzg.js → index-CyMcZjWq.js} +1 -1
  44. package/dist/assets/{infoDiagram-42DDH7IO-DGsgRji7.js → infoDiagram-42DDH7IO-CJSIzH2A.js} +1 -1
  45. package/dist/assets/{ishikawaDiagram-UXIWVN3A-D3odpv9_.js → ishikawaDiagram-UXIWVN3A-B631W1wW.js} +1 -1
  46. package/dist/assets/{journeyDiagram-VCZTEJTY-Dj3NaOWF.js → journeyDiagram-VCZTEJTY-ChtGieBW.js} +1 -1
  47. package/dist/assets/{jszip.min-GmVScWAV.js → jszip.min-MLKd3flP.js} +1 -1
  48. package/dist/assets/{kanban-definition-6JOO6SKY-GKjJaLwD.js → kanban-definition-6JOO6SKY-hLAy-WRt.js} +1 -1
  49. package/dist/assets/{layout-Bhk66H0F.js → layout-cxTukSs_.js} +1 -1
  50. package/dist/assets/{linear-CLk9nvqn.js → linear-lKN6YVn3.js} +1 -1
  51. package/dist/assets/{mermaid.core-DH-GfeNS.js → mermaid.core-BYpzUgET.js} +2 -2
  52. package/dist/assets/{min-DQftOGfo.js → min-BvhzyJ7G.js} +1 -1
  53. package/dist/assets/{mindmap-definition-QFDTVHPH-DgDD6Jbu.js → mindmap-definition-QFDTVHPH-DLmLjuqG.js} +1 -1
  54. package/dist/assets/{pieDiagram-DEJITSTG-jy1yFaxA.js → pieDiagram-DEJITSTG-CpQId_ss.js} +1 -1
  55. package/dist/assets/{quadrantDiagram-34T5L4WZ-BdvMwrSD.js → quadrantDiagram-34T5L4WZ-B2xWOHsh.js} +1 -1
  56. package/dist/assets/{requirementDiagram-MS252O5E-CtBOCtbk.js → requirementDiagram-MS252O5E-CfCJaJBw.js} +1 -1
  57. package/dist/assets/{sankeyDiagram-XADWPNL6-DEFlQp-h.js → sankeyDiagram-XADWPNL6-CSClMzTh.js} +1 -1
  58. package/dist/assets/seqResourceLoaders-DIUJh1ym.js +2 -0
  59. package/dist/assets/{sequenceDiagram-FGHM5R23-0F2R8Ky0.js → sequenceDiagram-FGHM5R23-D5WoARR1.js} +1 -1
  60. package/dist/assets/{stateDiagram-FHFEXIEX-D4xOwcSW.js → stateDiagram-FHFEXIEX-DC1_JCl1.js} +1 -1
  61. package/dist/assets/{stateDiagram-v2-QKLJ7IA2-DSf_BSF8.js → stateDiagram-v2-QKLJ7IA2-BOndcGRp.js} +1 -1
  62. package/dist/assets/{timeline-definition-GMOUNBTQ-B5cPasy_.js → timeline-definition-GMOUNBTQ-Bm3Lk96s.js} +1 -1
  63. package/dist/assets/vendor-antd-CyudKxuD.js +17 -0
  64. package/dist/assets/{vendor-codemirror-_NbrtdQc.js → vendor-codemirror-BD02EJx5.js} +1 -1
  65. package/dist/assets/{vendor-mdxeditor-DG0Nyxw5.js → vendor-mdxeditor-C3Q4i3LX.js} +2 -2
  66. package/dist/assets/{vendor-qrcode-B3HN--HO.js → vendor-qrcode-DK9W5Ghb.js} +1 -1
  67. package/dist/assets/{vendor-virtuoso-BvrezPQx.js → vendor-virtuoso-C82iSAZx.js} +1 -1
  68. package/dist/assets/{vennDiagram-DHZGUBPP-a_ybZDST.js → vennDiagram-DHZGUBPP-CA8MWsbD.js} +1 -1
  69. package/dist/assets/{wardley-RL74JXVD-CKla3KF8.js → wardley-RL74JXVD-Dw7GlJWj.js} +1 -1
  70. package/dist/assets/{wardleyDiagram-NUSXRM2D-BKke2aih.js → wardleyDiagram-NUSXRM2D-woq7iHEC.js} +1 -1
  71. package/dist/assets/{xychartDiagram-5P7HB3ND-CozWtcdf.js → xychartDiagram-5P7HB3ND-B4JCiFF4.js} +1 -1
  72. package/dist/index.html +4 -4
  73. package/findcc.js +246 -4
  74. package/package.json +1 -1
  75. package/server/lib/interceptor-core.js +26 -15
  76. package/server/pty-manager.js +3 -0
  77. package/server/routes/preferences.js +71 -1
  78. package/server/system-prompt-templates/presets/index.json +8 -0
  79. package/server/system-prompt-templates/presets/kimi-k3.md +68 -0
  80. package/src/utils/helpers.js +6 -1
  81. package/dist/assets/App-CCDFxj11.css +0 -1
  82. package/dist/assets/App-CcLQlV-O.js +0 -2
  83. package/dist/assets/classDiagram-6PBFFD2Q-CumOMdO7.js +0 -1
  84. package/dist/assets/classDiagram-v2-HSJHXN6E-CumOMdO7.js +0 -1
  85. package/dist/assets/clone-C3SZDzs1.js +0 -1
  86. package/dist/assets/index-DJ0AQwus.js +0 -2
  87. package/dist/assets/seqResourceLoaders-D1y8V4z5.js +0 -2
  88. package/dist/assets/vendor-antd-DeqwrDxf.js +0 -17
package/findcc.js CHANGED
@@ -1,8 +1,8 @@
1
- import { resolve, join, sep } from 'node:path';
1
+ import { resolve, join, sep, delimiter, isAbsolute, win32 } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
- import { existsSync, realpathSync, readFileSync } from 'node:fs';
3
+ import { existsSync, realpathSync, readFileSync, readdirSync, statSync, accessSync, constants as fsConstants } from 'node:fs';
4
4
  import { homedir, tmpdir, arch } from 'node:os';
5
- import { execSync, spawnSync } from 'node:child_process';
5
+ import { execSync, execFileSync, spawnSync } from 'node:child_process';
6
6
  import { threadId } from 'node:worker_threads';
7
7
 
8
8
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
@@ -172,6 +172,92 @@ const NATIVE_CANDIDATES = [
172
172
  // Command names used for which/command -v lookup
173
173
  export const BINARY_NAME = 'claude';
174
174
 
175
+ export const CLAUDE_EXECUTABLE_PREF_KEY = 'claudeExecutablePath';
176
+
177
+ export function expandClaudeExecutablePath(value, platform = process.platform) {
178
+ if (typeof value !== 'string' || !value.trim()) return null;
179
+ const raw = value.trim();
180
+ const homeRelative = raw.startsWith('~/') || raw.startsWith('~\\');
181
+ const expanded = homeRelative ? join(homedir(), raw.slice(2)) : raw;
182
+ const absolute = platform === 'win32' ? win32.isAbsolute(expanded) : isAbsolute(expanded);
183
+ if (!absolute) return null;
184
+ return platform === 'win32' ? win32.normalize(expanded) : resolve(expanded);
185
+ }
186
+
187
+ export function resolveExplicitClaudePath(value, platform = process.platform) {
188
+ const expanded = expandClaudeExecutablePath(value, platform);
189
+ if (!expanded || !existsSync(expanded)) return null;
190
+ try {
191
+ if (!statSync(expanded).isFile()) return null;
192
+ } catch { return null; }
193
+ let real = expanded;
194
+ try { real = realpathSync(expanded); } catch { }
195
+ const isJs = real.toLowerCase().endsWith('.js');
196
+ try {
197
+ if (isJs) {
198
+ accessSync(real, fsConstants.R_OK);
199
+ } else if (platform === 'win32') {
200
+ if (!real.toLowerCase().endsWith('.exe')) return null;
201
+ } else {
202
+ accessSync(expanded, fsConstants.X_OK);
203
+ }
204
+ } catch {
205
+ return null;
206
+ }
207
+ return {
208
+ path: isJs ? real : expanded,
209
+ realPath: real,
210
+ isNpmVersion: isJs,
211
+ };
212
+ }
213
+
214
+ export function readConfiguredClaudePath(prefsFile = join(LOG_DIR, 'preferences.json')) {
215
+ try {
216
+ const prefs = JSON.parse(readFileSync(prefsFile, 'utf8'));
217
+ const value = prefs?.[CLAUDE_EXECUTABLE_PREF_KEY];
218
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
219
+ } catch {
220
+ return null;
221
+ }
222
+ }
223
+
224
+ export function resolveConfiguredClaudePath(prefsFile) {
225
+ return resolveExplicitClaudePath(readConfiguredClaudePath(prefsFile));
226
+ }
227
+
228
+ function describeClaudeSelection(path, isNpmVersion, source) {
229
+ let realPath = path;
230
+ try { realPath = realpathSync(path); } catch { }
231
+ return { path, realPath, isNpmVersion, source };
232
+ }
233
+
234
+ /**
235
+ * Resolve the executable used by every CCV launch surface.
236
+ * A non-empty saved configuration is authoritative: if it is invalid, return
237
+ * an error instead of silently starting a different (possibly unapproved) build.
238
+ */
239
+ export function resolvePreferredClaudeSelection({ prefsFile } = {}) {
240
+ const configuredValue = readConfiguredClaudePath(prefsFile);
241
+ if (configuredValue) {
242
+ const configured = resolveExplicitClaudePath(configuredValue);
243
+ return configured
244
+ ? { ...configured, source: 'configured' }
245
+ : { error: 'configured-invalid', configuredPath: configuredValue };
246
+ }
247
+
248
+ const codeFusePath = resolveCodeFuseClaudePath();
249
+ if (codeFusePath) return describeClaudeSelection(codeFusePath, false, 'codefuse');
250
+
251
+ const pathSelection = resolveClaudeFromPath();
252
+ if (pathSelection) return describeClaudeSelection(pathSelection.path, pathSelection.isNpmVersion, 'path');
253
+
254
+ const npmPath = resolveNpmClaudePath();
255
+ if (npmPath) return describeClaudeSelection(npmPath, true, 'npm');
256
+
257
+ const nativePath = resolveNativePath();
258
+ return nativePath ? describeClaudeSelection(nativePath, false, 'native') : null;
259
+ }
260
+
175
261
  // The import statement injected at the top of @anthropic-ai/claude-code/cli.js.
176
262
  // EXTERNAL CONTRACT: uses bare specifier resolved via package.json `exports` —
177
263
  // the physical path can change without touching this line; keep in sync with
@@ -217,7 +303,12 @@ export function isBrowserOpenSuppressed() {
217
303
 
218
304
  export function getGlobalNodeModulesDir() {
219
305
  try {
220
- return execSync('npm root -g', { encoding: 'utf-8', windowsHide: true }).trim();
306
+ return execFileSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['root', '-g'], {
307
+ encoding: 'utf-8',
308
+ windowsHide: true,
309
+ timeout: 2000,
310
+ maxBuffer: 1024 * 1024,
311
+ }).trim();
221
312
  } catch {
222
313
  return null;
223
314
  }
@@ -351,6 +442,64 @@ export function pickSpawnableLookupResult(rawOut, platform = process.platform) {
351
442
  return lines[0] || null;
352
443
  }
353
444
 
445
+ /**
446
+ * Resolve the `claude` executable explicitly selected by the caller's PATH.
447
+ *
448
+ * Wrappers such as CodeFuse's `cfuse --ccv` prepend their managed Claude Code
449
+ * directory to PATH before launching ccv. This lookup must happen before any
450
+ * global npm fallback, otherwise an unrelated global @anthropic-ai install wins
451
+ * and the wrapper-provided (and potentially enterprise-approved) binary is lost.
452
+ *
453
+ * Returns both the path and whether Node is required for a legacy cli.js entry.
454
+ */
455
+ export function resolveClaudeFromPath() {
456
+ const lookupCmds = process.platform === 'win32'
457
+ ? [`where ${BINARY_NAME}`]
458
+ : [`which ${BINARY_NAME}`, `command -v ${BINARY_NAME}`];
459
+
460
+ for (const cmd of lookupCmds) {
461
+ try {
462
+ const rawOut = execSync(cmd, { encoding: 'utf-8', shell: true, env: process.env, windowsHide: true });
463
+ const result = pickSpawnableLookupResult(rawOut);
464
+ if (!result || !existsSync(result)) continue;
465
+
466
+ let real = result;
467
+ try { real = realpathSync(result); } catch { }
468
+ if (real.endsWith('.js')) return { path: real, isNpmVersion: true };
469
+ return { path: result, isNpmVersion: false };
470
+ } catch {
471
+ // Try the next lookup command.
472
+ }
473
+ }
474
+
475
+ return null;
476
+ }
477
+
478
+ // Starpoint currently approves the CodeFuse-managed Claude Code 2.1.199 build.
479
+ // Keep standalone `ccv` on the same binary as `cfuse --ccv`; callers can move
480
+ // the pin deliberately after a new enterprise build has been approved.
481
+ export const DEFAULT_CODEFUSE_CLAUDE_VERSION = '2.1.199';
482
+
483
+ export function resolveCodeFuseClaudePath(
484
+ codeFuseClaudeRoot = null,
485
+ version = process.env.CCV_CODEFUSE_CLAUDE_VERSION?.trim() || DEFAULT_CODEFUSE_CLAUDE_VERSION,
486
+ ) {
487
+ // Never let a test process escape its disposable fixtures into the real home.
488
+ // Tests exercise this resolver by passing an explicit temporary root.
489
+ if (!codeFuseClaudeRoot) {
490
+ if (isRealClaudeLookupBlocked()) return null;
491
+ codeFuseClaudeRoot = join(homedir(), '.codefuse', 'fuse', 'engine', 'bin', 'claude');
492
+ }
493
+ if (!version || !/^\d+\.\d+\.\d+$/.test(version)) return null;
494
+ const versionDir = join(codeFuseClaudeRoot, version);
495
+ const names = process.platform === 'win32' ? ['claude.exe'] : ['claude', 'claude.exe'];
496
+ for (const name of names) {
497
+ const candidate = join(versionDir, name);
498
+ if (existsSync(candidate)) return candidate;
499
+ }
500
+ return null;
501
+ }
502
+
354
503
  export function resolveNativePath() {
355
504
  // L7: steps 1 & 4 (platform/packaged binaries under `npm root -g`) ignore PATH isolation —
356
505
  // neutralized under test context via a null globalRoot (both helpers null-guard).
@@ -503,6 +652,99 @@ export function findPlatformBinary(nodeModulesRoot) {
503
652
  return null;
504
653
  }
505
654
 
655
+ /**
656
+ * Collect spawnable Claude Code entry points for the machine-level selector.
657
+ * This is intentionally read-only and never executes a discovered binary.
658
+ */
659
+ export function discoverClaudeExecutables({ configuredPath = null, allowReal = false, includeDefaults = true } = {}) {
660
+ const found = [];
661
+ const seen = new Set();
662
+ const inferNpmVersion = (realPath) => {
663
+ const normalized = String(realPath).replace(/\\/g, '/');
664
+ const match = normalized.match(/^(.*\/node_modules\/@(?:anthropic-ai|ali)\/claude-code)(?:\/|$)/);
665
+ if (!match) return null;
666
+ try {
667
+ const pkg = JSON.parse(readFileSync(join(match[1], 'package.json'), 'utf8'));
668
+ return typeof pkg.version === 'string' ? pkg.version : null;
669
+ } catch { return null; }
670
+ };
671
+ const add = (value, source, version = null) => {
672
+ const expanded = expandClaudeExecutablePath(value);
673
+ const resolved = resolveExplicitClaudePath(expanded);
674
+ if (!resolved || seen.has(resolved.realPath)) return;
675
+ const { realPath } = resolved;
676
+ seen.add(realPath);
677
+ found.push({
678
+ path: expanded,
679
+ realPath,
680
+ source,
681
+ version: version || inferNpmVersion(realPath),
682
+ isNpmVersion: resolved.isNpmVersion,
683
+ });
684
+ };
685
+
686
+ // Keep the saved value first so the UI does not jump when it is also found
687
+ // through PATH or a package scan.
688
+ if (configuredPath) add(configuredPath, 'configured');
689
+ if (!includeDefaults || (isRealClaudeLookupBlocked() && !allowReal)) return found;
690
+
691
+ // CodeFuse keeps one directory per managed Claude version.
692
+ const codeFuseRoot = join(homedir(), '.codefuse', 'fuse', 'engine', 'bin', 'claude');
693
+ try {
694
+ const versions = readdirSync(codeFuseRoot, { withFileTypes: true })
695
+ .filter((e) => e.isDirectory() && /^\d+\.\d+\.\d+$/.test(e.name))
696
+ .map((e) => e.name)
697
+ .sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
698
+ for (const version of versions) {
699
+ const names = process.platform === 'win32' ? ['claude.exe'] : ['claude', 'claude.exe'];
700
+ for (const name of names) add(join(codeFuseRoot, version, name), 'codefuse', version);
701
+ }
702
+ } catch { /* CodeFuse is optional. */ }
703
+
704
+ // Scan PATH directly. Avoid shelling out to `which`/`where` from the HTTP
705
+ // request path: a wrapper or slow shell startup must not block the viewer.
706
+ const pathNames = process.platform === 'win32' ? [`${BINARY_NAME}.exe`] : [BINARY_NAME];
707
+ for (const dir of String(process.env.PATH || '').split(delimiter).filter(Boolean)) {
708
+ for (const name of pathNames) add(resolve(dir, name), 'path');
709
+ }
710
+
711
+ // npm wrapper, its 2.x bin entry, and the platform-specific optional package.
712
+ const npmRoots = new Set([NODE_MODULES]);
713
+ const globalRoot = getGlobalNodeModulesDir();
714
+ if (globalRoot) npmRoots.add(globalRoot);
715
+ for (const root of npmRoots) {
716
+ for (const pkg of PACKAGES) {
717
+ const pkgDir = join(root, pkg);
718
+ add(join(pkgDir, CLI_ENTRY), 'npm');
719
+ add(join(pkgDir, 'bin', 'claude.exe'), 'npm');
720
+ add(join(pkgDir, 'bin', 'claude'), 'npm');
721
+ }
722
+ const platformBin = findPlatformBinary(root);
723
+ if (platformBin) add(platformBin, 'npm-platform');
724
+ }
725
+
726
+ // Native installer defaults and common package-manager locations.
727
+ const home = homedir();
728
+ const claudeDir = getClaudeConfigDir();
729
+ for (const value of NATIVE_CANDIDATES) {
730
+ const expanded = value.startsWith('~/.claude/')
731
+ ? join(claudeDir, value.slice('~/.claude/'.length))
732
+ : value.startsWith('~/') ? join(home, value.slice(2)) : value;
733
+ add(expanded, 'native');
734
+ if (process.platform === 'win32') add(expanded + '.exe', 'native');
735
+ }
736
+ if (process.platform === 'win32') {
737
+ const userProfile = process.env.USERPROFILE || home;
738
+ const localAppData = process.env.LOCALAPPDATA;
739
+ const appData = process.env.APPDATA;
740
+ add(join(userProfile, '.local', 'bin', 'claude.exe'), 'native');
741
+ if (localAppData) add(join(localAppData, 'Programs', 'claude', 'claude.exe'), 'native');
742
+ if (appData) add(join(appData, 'npm', 'node_modules', '@anthropic-ai', 'claude-code', 'bin', 'claude.exe'), 'npm');
743
+ }
744
+
745
+ return found;
746
+ }
747
+
506
748
  export function buildShellCandidates() {
507
749
  const globalRoot = getGlobalNodeModulesDir();
508
750
  // 使用 $HOME 而非硬编码绝对路径,保证 shell 可移植性
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-viewer",
3
- "version": "1.7.5",
3
+ "version": "1.7.7",
4
4
  "description": "Claude Code logging, visualization, and management toolkit — launch a web viewer alongside Claude Code with full request/response tracing, proxy, and mobile support",
5
5
  "license": "MIT",
6
6
  "main": "server.js",
@@ -400,34 +400,45 @@ export function replaceTopLevelModel(jsonStr, oldModel, newModel) {
400
400
  // 家族用**大小写不敏感子串**匹配(/opus/i 等),只认这几个已知家族单词——
401
401
  // 因此 claude-opus-4-8、未来的 claude-opus-5 等任何版本都命中同一家族,版本升级无需重配。
402
402
  // 字段直接沿用 Claude Code 的环境变量名以保持一致:
403
- // ANTHROPIC_MODEL —— 主模型;body.model 含 "fable" / "mythos" 时映射到它
403
+ // ANTHROPIC_MODEL —— 主模型(catch-all 兜底;body.model 含 "fable"/"mythos"
404
+ // 及所有未识别家族均回落到它)
404
405
  // ANTHROPIC_DEFAULT_OPUS_MODEL —— body.model 含 "opus"
405
406
  // ANTHROPIC_DEFAULT_SONNET_MODEL —— 含 "sonnet"
406
407
  // ANTHROPIC_DEFAULT_HAIKU_MODEL —— 含 "haiku"
407
- // 家族字段留空 = 该家族不改写(透传原始 model)。
408
- // 未识别家族(既不是 opus/sonnet/haiku 也不是 fable/mythos)不做兜底替换,原样透传。
408
+ // 家族字段优先:opus/sonnet/haiku 各自命中专属字段;字段留空则回落到 ANTHROPIC_MODEL。
409
+ // 未识别家族(既不是 opus/sonnet/haiku 也不是 fable/mythos)→ ANTHROPIC_MODEL 兜底。
409
410
  // 兼容旧数据:profile 未设任何新字段但有 activeModel(老结构)时,回退为旧的整体替换语义。
410
- // 返回目标模型字符串;无需改写(无目标 / 目标同旧值 / 入参非法 / 未识别家族)时返回 null。
411
+ // 返回目标模型字符串;无需改写(无目标 / 目标同旧值 / 入参非法)时返回 null。
412
+ // [1m] 后缀(Claude Code 1M context 标记)默认忽略:所有 profile 模型字段值在比较前先剥除。
411
413
  export function resolveProfileModel(oldModel, profile) {
412
414
  if (typeof oldModel !== 'string' || !oldModel || !profile || typeof profile !== 'object') return null;
413
- const opus = typeof profile.ANTHROPIC_DEFAULT_OPUS_MODEL === 'string' ? profile.ANTHROPIC_DEFAULT_OPUS_MODEL.trim() : '';
414
- const sonnet = typeof profile.ANTHROPIC_DEFAULT_SONNET_MODEL === 'string' ? profile.ANTHROPIC_DEFAULT_SONNET_MODEL.trim() : '';
415
- const haiku = typeof profile.ANTHROPIC_DEFAULT_HAIKU_MODEL === 'string' ? profile.ANTHROPIC_DEFAULT_HAIKU_MODEL.trim() : '';
416
- const primary = typeof profile.ANTHROPIC_MODEL === 'string' ? profile.ANTHROPIC_MODEL.trim() : '';
415
+
416
+ // Strip [1m] suffix (case-insensitive) from model names; Claude Code appends it
417
+ // for 1M-context variants and it should not affect model matching/replacement.
418
+ const strip1m = (s) => (typeof s === 'string' ? s.replace(/\[1m\]/gi, '').trim() : '');
419
+
420
+ const opus = strip1m(profile.ANTHROPIC_DEFAULT_OPUS_MODEL);
421
+ const sonnet = strip1m(profile.ANTHROPIC_DEFAULT_SONNET_MODEL);
422
+ const haiku = strip1m(profile.ANTHROPIC_DEFAULT_HAIKU_MODEL);
423
+ const primary = strip1m(profile.ANTHROPIC_MODEL);
417
424
  const hasNew = !!(primary || opus || sonnet || haiku);
418
425
 
419
426
  let target = '';
420
427
  if (hasNew) {
421
- if (/opus/i.test(oldModel)) target = opus;
422
- else if (/sonnet/i.test(oldModel)) target = sonnet;
423
- else if (/haiku/i.test(oldModel)) target = haiku;
424
- else if (/fable/i.test(oldModel) || /mythos/i.test(oldModel)) target = primary; // 显式家族 → 主模型
425
- // 未识别家族:target 保持 '',下方返回 null(不替换、原样透传)
428
+ // Family-specific fields take precedence; fall back to ANTHROPIC_MODEL when
429
+ // the family field is empty/unset. ANTHROPIC_MODEL itself acts as the
430
+ // catch-all default for any unrecognized family (including third-party models
431
+ // like gpt-4o, deepseek-v4, K3/kimi, etc.).
432
+ if (/opus/i.test(oldModel)) target = opus || primary;
433
+ else if (/sonnet/i.test(oldModel)) target = sonnet || primary;
434
+ else if (/haiku/i.test(oldModel)) target = haiku || primary;
435
+ else if (/fable/i.test(oldModel) || /mythos/i.test(oldModel)) target = primary;
436
+ else target = primary; // unrecognized family → ANTHROPIC_MODEL catch-all
426
437
  } else if (typeof profile.activeModel === 'string') {
427
- target = profile.activeModel.trim(); // 旧数据整体替换语义
438
+ target = strip1m(profile.activeModel); // 旧数据整体替换语义
428
439
  }
429
440
 
430
- if (!target || target === oldModel) return null;
441
+ if (!target || target === strip1m(oldModel)) return null;
431
442
  return target;
432
443
  }
433
444
 
@@ -231,6 +231,9 @@ async function _spawnClaudeImpl(proxyPort, cwd, extraArgs = [], claudePath = nul
231
231
  }
232
232
 
233
233
  const env = { ...process.env };
234
+ // CCV owns executable selection. Never let the selected Claude replace itself
235
+ // behind that configuration (especially on enterprise allowlisted machines).
236
+ env.DISABLE_AUTOUPDATER = '1';
234
237
  env.ANTHROPIC_BASE_URL = `http://127.0.0.1:${proxyPort}`;
235
238
  env.CCV_PROXY_MODE = '1'; // 告诉 interceptor.js 不要再启动 server
236
239
  env.CCV_LOG_DIR = LOG_DIR; // 让 fork 出的 Claude Code 进程找到同一份 profile.json 等资源
@@ -2,7 +2,7 @@
2
2
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
3
3
  import { join, dirname } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
- import { LOG_DIR, setLogDir, getClaudeConfigDir } from '../../findcc.js';
5
+ import { LOG_DIR, setLogDir, getClaudeConfigDir, discoverClaudeExecutables, resolveExplicitClaudePath, CLAUDE_EXECUTABLE_PREF_KEY } from '../../findcc.js';
6
6
  import { PROFILE_PATH, _defaultConfig, getActiveProfileId, setActiveProfileForWorkspace, _loadProxyProfile, RETRY_CONFIG_PATH, _retryConfigState, _loadRetryConfigState } from '../interceptor.js';
7
7
  import { migrateProxyProfileList } from '../lib/interceptor-core.js';
8
8
  import { DEFAULT_RETRY_CONFIG, validateRetryConfig, resolveRetryConfig } from '../lib/proxy-retry.js';
@@ -25,6 +25,22 @@ function stripImConfigs(obj) {
25
25
  if (obj) for (const id of listPlatforms()) delete obj[id];
26
26
  }
27
27
 
28
+ // Loopback identifies the socket peer, not the browser page that initiated a
29
+ // request. Since the server intentionally has permissive CORS for legacy APIs,
30
+ // sensitive executable-selection endpoints need their own same-origin guard.
31
+ function isSameOriginBrowserRequest(req, parsedUrl) {
32
+ const origin = req.headers?.origin;
33
+ if (!origin) return true; // CLI/native clients do not send Origin.
34
+ if (req.headers?.['sec-fetch-site'] === 'cross-site') return false;
35
+ try {
36
+ const originUrl = new URL(origin);
37
+ if (parsedUrl?.origin) return originUrl.origin.toLowerCase() === parsedUrl.origin.toLowerCase();
38
+ return originUrl.host.toLowerCase() === String(req.headers?.host || '').toLowerCase();
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
28
44
  // /theme 选择器特征:选项文案高特异、不太可能出现在普通生成输出里(ESC 兜底的门控签名)
29
45
  const THEME_PICKER_RE = /Auto \(match terminal\)|colorblind-friendly/;
30
46
 
@@ -41,6 +57,9 @@ function preferencesGet(req, res, parsedUrl, isLocal, deps) {
41
57
  // 全局 auth 与每个项目的 authByProject 覆盖都要剥离(后者同样含明文密码)。
42
58
  delete prefs.auth;
43
59
  delete prefs.authByProject;
60
+ if (!isLocal || !isSameOriginBrowserRequest(req, parsedUrl)) {
61
+ delete prefs[CLAUDE_EXECUTABLE_PREF_KEY];
62
+ }
44
63
  stripImConfigs(prefs); // dingtalk / feishu / … — admin-only, never to a LAN client
45
64
  // 项目独立配置(多人共用一台 server 时按项目隔离偏好):非本机(LAN)客户端若当前项目有 fork,
46
65
  // 解析出该项目的有效偏好;本机(admin)始终看全局。forks blob 绝不下发,仅以 _projectPrefsKeys
@@ -99,6 +118,30 @@ function preferencesPost(req, res, parsedUrl, isLocal, deps) {
99
118
  // 任意项目的覆盖,绕过 admin 门禁。
100
119
  delete incoming.auth;
101
120
  delete incoming.authByProject;
121
+ // Claude executable selection controls which local program CCV executes.
122
+ // It is machine-global and therefore writable only by the loopback admin,
123
+ // never by a remote/project-scoped preferences client.
124
+ if (!isLocal) {
125
+ delete incoming[CLAUDE_EXECUTABLE_PREF_KEY];
126
+ } else if (Object.prototype.hasOwnProperty.call(incoming, CLAUDE_EXECUTABLE_PREF_KEY)) {
127
+ const value = incoming[CLAUDE_EXECUTABLE_PREF_KEY];
128
+ if (!isSameOriginBrowserRequest(req, parsedUrl)) {
129
+ res.writeHead(403, { 'Content-Type': 'application/json' });
130
+ res.end(JSON.stringify({ error: 'Same-origin request required' }));
131
+ return;
132
+ }
133
+ if (typeof value === 'string' && value.trim()) {
134
+ const resolved = resolveExplicitClaudePath(value.trim().slice(0, 4096));
135
+ if (!resolved) {
136
+ res.writeHead(422, { 'Content-Type': 'application/json' });
137
+ res.end(JSON.stringify({ error: 'Claude executable path is not launchable' }));
138
+ return;
139
+ }
140
+ incoming[CLAUDE_EXECUTABLE_PREF_KEY] = resolved.path;
141
+ } else {
142
+ incoming[CLAUDE_EXECUTABLE_PREF_KEY] = null;
143
+ }
144
+ }
102
145
  // prefsByProject(项目独立配置 fork)只能经 admin-only 的 /api/project-prefs/* 修改;元字段
103
146
  // (_isLocal/_projectScoped/…)仅 GET 回包,绝不让它们经 /api/preferences 落盘污染。
104
147
  delete incoming.prefsByProject;
@@ -168,6 +211,9 @@ function preferencesPost(req, res, parsedUrl, isLocal, deps) {
168
211
  delete prefs.auth;
169
212
  delete prefs.authByProject;
170
213
  delete prefs.prefsByProject; // fork blob 绝不回显(与 GET 一致)
214
+ if (!isLocal || !isSameOriginBrowserRequest(req, parsedUrl)) {
215
+ delete prefs[CLAUDE_EXECUTABLE_PREF_KEY];
216
+ }
171
217
  stripImConfigs(prefs);
172
218
  prefs.logDir = LOG_DIR;
173
219
  res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -181,6 +227,29 @@ function preferencesPost(req, res, parsedUrl, isLocal, deps) {
181
227
  });
182
228
  }
183
229
 
230
+ function claudeExecutablesGet(req, res, parsedUrl, isLocal, deps) {
231
+ if (!isLocal || !isSameOriginBrowserRequest(req, parsedUrl)) {
232
+ res.writeHead(403, { 'Content-Type': 'application/json' });
233
+ res.end(JSON.stringify({ error: isLocal ? 'Same-origin request required' : 'Loopback only' }));
234
+ return;
235
+ }
236
+ let configuredPath = null;
237
+ try {
238
+ const prefs = readPrefsRaw(deps.getPrefsFile());
239
+ const value = prefs?.[CLAUDE_EXECUTABLE_PREF_KEY];
240
+ if (typeof value === 'string' && value.trim()) configuredPath = value.trim();
241
+ } catch { }
242
+ const candidates = discoverClaudeExecutables({ configuredPath }).map(({
243
+ path, source, version, isNpmVersion,
244
+ }) => ({ path, source, version, isNpmVersion }));
245
+ res.writeHead(200, { 'Content-Type': 'application/json' });
246
+ res.end(JSON.stringify({
247
+ configuredPath,
248
+ effectivePath: process.env.CCV_CLAUDE_EXECUTABLE || null,
249
+ candidates,
250
+ }));
251
+ }
252
+
184
253
  function claudeSettingsGet(req, res, parsedUrl, isLocal, deps) {
185
254
  res.writeHead(200, { 'Content-Type': 'application/json' });
186
255
  const fileEnv = deps.claudeSettings.env || {};
@@ -481,6 +550,7 @@ async function ccswitchImportPost(req, res, _parsedUrl, isLocal, deps) {
481
550
  export const preferencesRoutes = [
482
551
  { method: 'GET', match: 'exact', path: '/api/preferences', handler: preferencesGet },
483
552
  { method: 'POST', match: 'exact', path: '/api/preferences', handler: preferencesPost },
553
+ { method: 'GET', match: 'exact', path: '/api/claude-executables', handler: claudeExecutablesGet },
484
554
  { method: 'GET', match: 'exact', path: '/api/claude-settings', handler: claudeSettingsGet },
485
555
  { method: 'POST', match: 'exact', path: '/api/claude-settings', handler: claudeSettingsPost },
486
556
  { method: 'GET', match: 'exact', path: '/api/proxy-profiles', handler: proxyProfilesGet },
@@ -41,6 +41,14 @@
41
41
  "description": "Coding agent prompt tuned for Kimi K2.7 Code: deliver exactly what was asked (no over-engineering), act through tools, explain briefly.",
42
42
  "match": "kimi-k2.7-code",
43
43
  "defaultMode": "override"
44
+ },
45
+ {
46
+ "id": "kimi-k3",
47
+ "title": "Kimi K3",
48
+ "file": "kimi-k3.md",
49
+ "description": "Coding agent prompt tuned for Kimi K3: deliver exactly what was asked (no over-engineering), act through tools, explain briefly.",
50
+ "match": "kimi-k3",
51
+ "defaultMode": "override"
44
52
  }
45
53
  ]
46
54
  }
@@ -0,0 +1,68 @@
1
+ <!--
2
+ Preset: kimi-k3 (category: Global)
3
+ Self-contained template: a tuned preamble plus its own dynamic sections
4
+ (a boundary marker, an OS-only # Environment, and a verbatim # Memory; no Git).
5
+ Edit this file directly. Forked from the Kimi K2.7 Code preset and tuned for
6
+ Kimi K3: keep scope tight, act through tools, and explain briefly.
7
+ -->
8
+
9
+ You are ${model.name}, an interactive coding agent that helps users with software engineering tasks by taking action. Use the instructions below and the tools available to you to make real changes in the user's project.
10
+
11
+ IMPORTANT: Assist with defensive software engineering work. Refuse requests to deploy, facilitate, or hide malware, credential theft, destructive behavior, or other cyber abuse.
12
+ IMPORTANT: Never generate or guess URLs unless you are confident they help the user with programming. Prefer URLs the user provides or ones found in local files.
13
+
14
+ # Doing tasks
15
+ - Read the relevant files before acting or answering; ground every claim and change in code you have actually looked at.
16
+ - When a request could be read either as a question or as a change to make, treat it as a task and carry it out. When the user clearly asks a question or how to approach something, answer that first.
17
+ - Deliver exactly what was asked and nothing more: no unrequested CLI wrappers, configuration options, logging, progress output, or abstractions. This is very important to your performance.
18
+ - Never assume a library or framework is available — check the project's manifest or neighboring files before using it.
19
+ - Do not introduce security vulnerabilities (injection, XSS, path traversal, and the rest of the OWASP top 10); fix insecure code you write immediately.
20
+ - Iterate to green: run the relevant tests or code path, read the failure, fix the cause, and run again.
21
+ - When an approach is blocked, try a different one before handing the problem back to the user.
22
+
23
+ # Using tools
24
+ - A change shown only in your reply does not exist on disk — create and edit files with tools, never by pasting code into the conversation.
25
+ - Do not narrate tool calls; the calls themselves show the user what you are doing.
26
+ - Send independent tool calls together in one response instead of one at a time.
27
+ - Track multi-step work explicitly and mark each step done as you finish it.
28
+ - Tool results and user messages may include <system-reminder> tags. They carry information from the system, not from the user.
29
+
30
+ # Executing actions with care
31
+ Consider the reversibility and blast radius of each action. Local, reversible actions (editing files, running tests) are fine to take freely. For hard-to-reverse or shared-system actions — deleting files or branches, force-pushing, sending messages, posting to external services — confirm with the user first. Never run git mutations (commit, push, reset, rebase) unless the user explicitly asks, and re-confirm each time even if the user approved one earlier. Investigate unexpected state before overwriting it.
32
+
33
+ # Tone and style
34
+ - Be thorough in your actions, not in your explanations: report what changed and where, and stop.
35
+ - Keep text output brief and direct; lead with the answer or action. No filler, and no emojis unless the user asks.
36
+ - Reference code with the `file_path:line_number` pattern.
37
+ - Always respond in the same language as the user, using the `${environment.lang}` locale when the language is not otherwise clear.
38
+
39
+ __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__
40
+
41
+ # Environment
42
+ - Platform: ${os.platform}
43
+ - OS Version: ${os.version}
44
+ - Architecture: ${os.arch}
45
+ - Shell: ${os.shell}
46
+
47
+ # Memory
48
+
49
+ You have a persistent file-based memory at `${memory.dir}`. This directory already exists — write to it directly with the Write tool (do not run mkdir or check for its existence). Each memory is one file holding one fact, with frontmatter:
50
+
51
+ ```markdown
52
+ ---
53
+ name: <short-kebab-case-slug>
54
+ description: <one-line summary — used to decide relevance during recall>
55
+ metadata:
56
+ type: user | feedback | project | reference
57
+ ---
58
+
59
+ <the fact; for feedback/project, follow with **Why:** and **How to apply:** lines. Link related memories with [[their-name]].>
60
+ ```
61
+
62
+ In the body, link to related memories with `[[name]]`, where `name` is the other memory's `name:` slug. Link liberally — a `[[name]]` that doesn't match an existing memory yet is fine; it marks something worth writing later, not an error.
63
+
64
+ `user` — who the user is (role, expertise, preferences). `feedback` — guidance the user has given on how you should work, both corrections and confirmed approaches; include the why. `project` — ongoing work, goals, or constraints not derivable from the code or git history; convert relative dates to absolute. `reference` — pointers to external resources (URLs, dashboards, tickets).
65
+
66
+ After writing the file, add a one-line pointer in `MEMORY.md` (`- [Title](file.md) — hook`). `MEMORY.md` is the index loaded into context each session — one line per memory, no frontmatter, never put memory content there.
67
+
68
+ Before saving, check for an existing file that already covers it — update that file rather than creating a duplicate; delete memories that turn out to be wrong. Don't save what the repo already records (code structure, past fixes, git history, project instructions) or what only matters to this conversation; if asked to remember one of those, ask what was non-obvious about it and save that instead. Recalled memories reflect what was true when written — if one names a file, function, or flag, verify it still exists before recommending it.
@@ -75,11 +75,16 @@ export function resolveCalibrationTokens(calibrationModel, lastMainAgent, projec
75
75
  // (If a stale localStorage value misses both this map and calibrationModels, fall back to 'auto'.)
76
76
  export const LEGACY_CALIBRATION_MIGRATION = {
77
77
  'opus-4.7-1m': '1m',
78
+ 'opus-4.8[1m]': '1m',
78
79
  'sonnet-4.6': '200k',
79
80
  'glm5': '200k',
80
81
  'kimi-k2.5': '200k',
81
82
  'minimax-2.1': '200k',
82
83
  'Qwen 3.5': '200k',
84
+ 'sonnet-5': '1m',
85
+ 'glm5.2': '1m',
86
+ 'kimi-k3': '1m',
87
+ 'Qwen 3.8': '1m'
83
88
  };
84
89
 
85
90
  /**
@@ -326,7 +331,7 @@ const MODEL_PROVIDERS = [
326
331
  svg: '<svg t="1771495640319" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5634" width="200" height="200"><path d="M537.782857 57.161143c16.749714 29.44 33.426286 58.953143 50.102857 88.502857a7.68 7.68 0 0 0 6.692572 3.913143h236.873143c7.424 0 13.750857 4.681143 19.017142 13.933714l62.061715 109.677714c8.082286 14.372571 10.24 20.370286 1.024 35.693715a2262.893714 2262.893714 0 0 0-32.438857 55.478857l-15.652572 28.086857c-4.534857 8.338286-9.508571 11.922286-1.718857 21.833143l113.152 197.851428c7.314286 12.8 4.754286 21.065143-1.828571 32.841143-18.651429 33.499429-37.632 66.742857-56.978286 99.84-6.765714 11.593143-14.994286 16.018286-29.001143 15.798857a1833.398857 1833.398857 0 0 0-99.291429 0.658286 4.242286 4.242286 0 0 0-3.437714 2.157714 24571.830857 24571.830857 0 0 1-115.419428 202.24c-7.204571 12.507429-16.201143 15.469714-30.939429 15.506286-42.532571 0.146286-85.430857 0.182857-128.731429 0.109714a22.930286 22.930286 0 0 1-19.821714-11.556571l-56.978286-99.108571a3.84 3.84 0 0 0-3.547428-2.121143h-218.331429a76.288 76.288 0 0 1-34.377143-3.913143l-68.388571-118.198857a23.149714 23.149714 0 0 1-0.073143-23.04l51.492572-90.441143a8.448 8.448 0 0 0 0-8.411429 23594.496 23594.496 0 0 1-80.018286-139.593143L47.542857 425.325714c-6.838857-13.202286-7.387429-21.138286 4.022857-41.179428 19.858286-34.669714 39.570286-69.302857 59.209143-103.936 5.632-9.984 12.946286-14.262857 24.905143-14.262857 36.827429-0.182857 73.654857-0.182857 110.445714-0.073143 1.901714 0 3.657143-1.024 4.571429-2.669715l119.734857-208.859428a20.845714 20.845714 0 0 1 17.993143-10.496c22.381714-0.036571 44.946286 0 67.547428-0.256l43.410286-0.987429c14.555429-0.109714 30.866286 1.389714 38.4 14.518857z m-146.432 17.188571a2.56 2.56 0 0 0-2.194286 1.28l-122.331428 213.942857a6.692571 6.692571 0 0 1-5.741714 3.364572H138.788571c-2.377143 0-2.962286 1.060571-1.755428 3.145143l247.917714 433.334857c1.060571 1.792 0.548571 2.633143-1.462857 2.669714l-119.222857 0.658286a9.289143 9.289143 0 0 0-8.557714 4.937143l-56.32 98.56c-1.865143 3.328-0.914286 5.046857 2.925714 5.046857l243.858286 0.365714c1.974857 0 3.401143 0.841143 4.425142 2.56l59.867429 104.704c1.974857 3.474286 3.913143 3.510857 5.924571 0l213.577143-373.76 33.426286-58.953143a2.340571 2.340571 0 0 1 4.096 0l60.745143 107.958857a5.193143 5.193143 0 0 0 4.571428 2.633143l117.906286-0.841143a1.718857 1.718857 0 0 0 1.462857-2.56l-123.721143-217.014857a4.608 4.608 0 0 1 0-4.827428l12.507429-21.613715 47.798857-84.370285c1.024-1.755429 0.512-2.633143-1.499428-2.633143H392.521143c-2.486857 0-3.108571-1.097143-1.828572-3.291429l61.184-106.861714a4.571429 4.571429 0 0 0 0-4.864l-58.258285-102.253714a2.56 2.56 0 0 0-2.267429-1.316572z m268.361143 342.198857c1.974857 0 2.486857 0.841143 1.462857 2.56l-35.474286 62.500572-111.542857 195.620571a2.377143 2.377143 0 0 1-2.121143 1.243429 2.486857 2.486857 0 0 1-2.121142-1.243429l-147.346286-257.353143c-0.841143-1.462857-0.402286-2.230857 1.206857-2.304l9.216-0.512 286.793143-0.512h-0.073143z" fill="#7D5DED" p-id="5635"></path></svg>',
327
332
  },
328
333
  {
329
- match: /kimi|moonshot/i,
334
+ match: /kimi|moonshot|^k3$/i,
330
335
  name: 'Kimi',
331
336
  color: 'var(--bg-model-avatar)',
332
337
  svg: '<svg t="1771495664798" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6649" width="200" height="200"><path d="M731.062857 590.262857v318.317714h-148.589714V443.977143a146.285714 146.285714 0 0 1-146.285714 146.285714l-214.491429-0.036571v318.354285H73.142857V165.814857h148.553143v275.858286h180.882286l119.771428-275.858286h167.753143l-67.84 156.269714a255.524571 255.524571 0 0 1-106.678857 119.588572h66.925714a148.553143 148.553143 0 0 1 148.516572 148.589714z m120.758857-473.965714a99.035429 99.035429 0 0 1 0 198.070857h-99.035428V215.332571a99.035429 99.035429 0 0 1 99.035428-99.035428z" fill="currentColor" p-id="6650"></path></svg>',