cc-viewer 1.7.6 → 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.
- package/README.md +17 -0
- package/cli.js +41 -31
- package/dist/assets/App-BfzeNwOB.css +1 -0
- package/dist/assets/App-Bqz6Y5h4.js +2 -0
- package/dist/assets/{MdxEditorPanel-BQ5MgI4D.js → MdxEditorPanel-BAE_5ssH.js} +1 -1
- package/dist/assets/{Mobile-wwcXmnrU.js → Mobile-2LDWfaUe.js} +1 -1
- package/dist/assets/{UnifiedProxyRetryPage-BpcKIn9N.js → UnifiedProxyRetryPage-DFyS9doh.js} +1 -1
- package/dist/assets/{_baseUniq-yiNE5Ckw.js → _baseUniq-BkQkqbX3.js} +1 -1
- package/dist/assets/{arc-M-eBYYbN.js → arc-BKY9CS7c.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-CtweeLKi.js → architectureDiagram-Q4EWVU46-f8VVp6F9.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-DCVQ-go_.js → blockDiagram-DXYQGD6D-3h2PkZUH.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-C7JB9PqP.js → c4Diagram-AHTNJAMY-DigIDzhn.js} +1 -1
- package/dist/assets/{channel-BnzX9zBS.js → channel-5ajs7xDK.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-dVKiUtqe.js → chunk-4BX2VUAB-BvwhA1RA.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-CQjD9-fD.js → chunk-4TB4RGXK-GHcSNG2k.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-BZuKI6JQ.js → chunk-55IACEB6-DWO0TwNe.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-sk9g7szp.js → chunk-EDXVE4YY-0ycrM2oE.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-CGL9Lo51.js → chunk-FMBD7UC4-DmdCml-0.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-DaN0AdQD.js → chunk-OYMX7WX6-fg2n9B_W.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-CNEAHnZV.js → chunk-QZHKN3VN-Bg3VPGcJ.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-BOlts_Dm.js → chunk-YZCP3GAM-BV1RGRqK.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CnUCTEpg.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-CnUCTEpg.js +1 -0
- package/dist/assets/clone-D0NlqTo0.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-DH4_YiGi.js → cose-bilkent-S5V4N54A-CjAVlyw6.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-RqusStYI.js → dagre-KV5264BT-DZV42aOy.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-8haVbBnt.js → diagram-5BDNPKRD-DpDA9ZeE.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-CsrJNUhT.js → diagram-G4DWMVQ6-86vmHGG_.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-1luPOM38.js → diagram-MMDJMWI5-CzLVfy9C.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-Dad1XR3W.js → diagram-TYMM5635-C1n6QVNv.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-Br6-kbp9.js → erDiagram-SMLLAGMA-BWST8PqT.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-DuX1mbA7.js → flowDiagram-DWJPFMVM-zfku-qzI.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-BL_XQ958.js → ganttDiagram-T4ZO3ILL-BIxQMxEL.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-DzvW5FEs.js → gitGraphDiagram-UUTBAWPF-E7gXMGl0.js} +1 -1
- package/dist/assets/{graph-CbUmu0Oe.js → graph-Chi_Do3A.js} +1 -1
- package/dist/assets/{index-B3-JUU7J.js → index-BVkdlwjO.js} +1 -1
- package/dist/assets/{index-CzPHj3iF.js → index-Batfx707.js} +1 -1
- package/dist/assets/{index-BgW_nP_0.js → index-BbuqKcgW.js} +1 -1
- package/dist/assets/index-Bp83Qa3f.js +2 -0
- package/dist/assets/{index-vImWXiMM.js → index-BrmFDbRb.js} +1 -1
- package/dist/assets/{index-DDlAMOiV.js → index-BsRFLaCT.js} +1 -1
- package/dist/assets/{index-Do5xNTOT.js → index-CaTBBfzw.js} +1 -1
- package/dist/assets/{index-LjVy-Pzg.js → index-CyMcZjWq.js} +1 -1
- package/dist/assets/{infoDiagram-42DDH7IO-DGsgRji7.js → infoDiagram-42DDH7IO-CJSIzH2A.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-D3odpv9_.js → ishikawaDiagram-UXIWVN3A-B631W1wW.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-Dj3NaOWF.js → journeyDiagram-VCZTEJTY-ChtGieBW.js} +1 -1
- package/dist/assets/{jszip.min-GmVScWAV.js → jszip.min-MLKd3flP.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-GKjJaLwD.js → kanban-definition-6JOO6SKY-hLAy-WRt.js} +1 -1
- package/dist/assets/{layout-Bhk66H0F.js → layout-cxTukSs_.js} +1 -1
- package/dist/assets/{linear-CLk9nvqn.js → linear-lKN6YVn3.js} +1 -1
- package/dist/assets/{mermaid.core-DH-GfeNS.js → mermaid.core-BYpzUgET.js} +2 -2
- package/dist/assets/{min-DQftOGfo.js → min-BvhzyJ7G.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-DgDD6Jbu.js → mindmap-definition-QFDTVHPH-DLmLjuqG.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-jy1yFaxA.js → pieDiagram-DEJITSTG-CpQId_ss.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-BdvMwrSD.js → quadrantDiagram-34T5L4WZ-B2xWOHsh.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-CtBOCtbk.js → requirementDiagram-MS252O5E-CfCJaJBw.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-DEFlQp-h.js → sankeyDiagram-XADWPNL6-CSClMzTh.js} +1 -1
- package/dist/assets/seqResourceLoaders-DIUJh1ym.js +2 -0
- package/dist/assets/{sequenceDiagram-FGHM5R23-0F2R8Ky0.js → sequenceDiagram-FGHM5R23-D5WoARR1.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-D4xOwcSW.js → stateDiagram-FHFEXIEX-DC1_JCl1.js} +1 -1
- package/dist/assets/{stateDiagram-v2-QKLJ7IA2-DSf_BSF8.js → stateDiagram-v2-QKLJ7IA2-BOndcGRp.js} +1 -1
- package/dist/assets/{timeline-definition-GMOUNBTQ-B5cPasy_.js → timeline-definition-GMOUNBTQ-Bm3Lk96s.js} +1 -1
- package/dist/assets/vendor-antd-CyudKxuD.js +17 -0
- package/dist/assets/{vendor-codemirror-_NbrtdQc.js → vendor-codemirror-BD02EJx5.js} +1 -1
- package/dist/assets/{vendor-mdxeditor-DG0Nyxw5.js → vendor-mdxeditor-C3Q4i3LX.js} +2 -2
- package/dist/assets/{vendor-qrcode-B3HN--HO.js → vendor-qrcode-DK9W5Ghb.js} +1 -1
- package/dist/assets/{vendor-virtuoso-BvrezPQx.js → vendor-virtuoso-C82iSAZx.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-a_ybZDST.js → vennDiagram-DHZGUBPP-CA8MWsbD.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-CKla3KF8.js → wardley-RL74JXVD-Dw7GlJWj.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-BKke2aih.js → wardleyDiagram-NUSXRM2D-woq7iHEC.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-CozWtcdf.js → xychartDiagram-5P7HB3ND-B4JCiFF4.js} +1 -1
- package/dist/index.html +4 -4
- package/findcc.js +188 -4
- package/package.json +1 -1
- package/server/pty-manager.js +3 -8
- package/server/routes/preferences.js +71 -1
- package/server/system-prompt-templates/presets/index.json +8 -0
- package/server/system-prompt-templates/presets/kimi-k3.md +68 -0
- package/src/utils/helpers.js +6 -1
- package/dist/assets/App-3VM471BO.js +0 -2
- package/dist/assets/App-CCDFxj11.css +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CumOMdO7.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-CumOMdO7.js +0 -1
- package/dist/assets/clone-C3SZDzs1.js +0 -1
- package/dist/assets/index-BjNLFJ0z.js +0 -2
- package/dist/assets/seqResourceLoaders-CDRtUSxI.js +0 -2
- 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
|
|
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
|
}
|
|
@@ -561,6 +652,99 @@ export function findPlatformBinary(nodeModulesRoot) {
|
|
|
561
652
|
return null;
|
|
562
653
|
}
|
|
563
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
|
+
|
|
564
748
|
export function buildShellCandidates() {
|
|
565
749
|
const globalRoot = getGlobalNodeModulesDir();
|
|
566
750
|
// 使用 $HOME 而非硬编码绝对路径,保证 shell 可移植性
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-viewer",
|
|
3
|
-
"version": "1.7.
|
|
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",
|
package/server/pty-manager.js
CHANGED
|
@@ -231,14 +231,9 @@ async function _spawnClaudeImpl(proxyPort, cwd, extraArgs = [], claudePath = nul
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
const env = { ...process.env };
|
|
234
|
-
//
|
|
235
|
-
// that
|
|
236
|
-
|
|
237
|
-
// be silently replaced during a CCV session.
|
|
238
|
-
const normalizedClaudePath = String(claudePath || '').replace(/\\/g, '/');
|
|
239
|
-
if (normalizedClaudePath.includes('/.codefuse/fuse/engine/bin/claude/')) {
|
|
240
|
-
env.DISABLE_AUTOUPDATER = '1';
|
|
241
|
-
}
|
|
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';
|
|
242
237
|
env.ANTHROPIC_BASE_URL = `http://127.0.0.1:${proxyPort}`;
|
|
243
238
|
env.CCV_PROXY_MODE = '1'; // 告诉 interceptor.js 不要再启动 server
|
|
244
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.
|
package/src/utils/helpers.js
CHANGED
|
@@ -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
|
|
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>',
|