cc-viewer 1.6.342 → 1.6.343

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 (27) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/App-DkT8kHeT.js +2 -0
  3. package/dist/assets/App-Pkp5KFXT.css +1 -0
  4. package/dist/assets/{MdxEditorPanel-CaDMFlsk.js → MdxEditorPanel-DJtY-RZu.js} +1 -1
  5. package/dist/assets/{Mobile-C22UBGse.js → Mobile-BSe9GgwO.js} +1 -1
  6. package/dist/assets/index-5v2dgTRS.js +2 -0
  7. package/dist/assets/{seqResourceLoaders-BqKidNto.js → seqResourceLoaders-BlUz36Ig.js} +2 -2
  8. package/dist/index.html +1 -1
  9. package/package.json +1 -1
  10. package/server/lib/create_system_prompt.js +504 -0
  11. package/server/lib/system-prompt-presets.js +67 -0
  12. package/server/routes/expert.js +18 -0
  13. package/server/system-prompt-templates/presets/GLM-5.2.md +63 -0
  14. package/server/system-prompt-templates/presets/Qwen-3.7-Max.md +63 -0
  15. package/server/system-prompt-templates/presets/deepseek-v4-flash.md +60 -0
  16. package/server/system-prompt-templates/presets/deepseek-v4-pro.md +65 -0
  17. package/server/system-prompt-templates/presets/index.json +39 -0
  18. package/server/system-prompt-templates/reference/claude-code-cli-startup-options.md +325 -0
  19. package/server/system-prompt-templates/reference/prompt-control-tokens-deepseek-v4.md +178 -0
  20. package/server/system-prompt-templates/reference/prompt-control-tokens-qwen3.md +227 -0
  21. package/server/system-prompt-templates/reference/prompt-xml-tags-fable-5.md +101 -0
  22. package/server/system-prompt-templates/reference/prompt-xml-tags-opus-4-8.md +113 -0
  23. package/server/system-prompt-templates/systemPromptModel.md +168 -0
  24. package/server/system-prompt-templates/systemPromptVariables.md +113 -0
  25. package/dist/assets/App-BFcpzWEl.js +0 -2
  26. package/dist/assets/App-D5BI6yGO.css +0 -1
  27. package/dist/assets/index-CtSbjm-X.js +0 -2
package/dist/index.html CHANGED
@@ -21,7 +21,7 @@
21
21
  // 整体显示大小已弃用 CSS zoom:Electron 改用 webFrame.setZoomFactor(首屏抢占见
22
22
  // electron/tab-content-preload.js),纯浏览器交由用户用浏览器自带快捷键缩放,故此处不再设 zoom。
23
23
  </script>
24
- <script type="module" crossorigin src="./assets/index-CtSbjm-X.js"></script>
24
+ <script type="module" crossorigin src="./assets/index-5v2dgTRS.js"></script>
25
25
  <link rel="modulepreload" crossorigin href="./assets/vendor-antd-D5HmSDSg.js">
26
26
  <link rel="modulepreload" crossorigin href="./assets/vendor-codemirror-C5yuj7ze.js">
27
27
  <link rel="modulepreload" crossorigin href="./assets/vendor-mdxeditor-BcEooTvb.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-viewer",
3
- "version": "1.6.342",
3
+ "version": "1.6.343",
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",
@@ -0,0 +1,504 @@
1
+ // System-prompt template builder: resolves runtime variables, loads
2
+ // systemPromptModel.md + the presets, and renders/assembles system-prompt text.
3
+ // Consumed by server/lib/system-prompt-presets.js and runnable directly as a CLI.
4
+ //
5
+ // CLI usage (renders with missing variables blanked out):
6
+ // node server/lib/create_system_prompt.js # base model template
7
+ // node server/lib/create_system_prompt.js deepseek-v4-pro # a named preset
8
+ // node server/lib/create_system_prompt.js --list # list presets
9
+
10
+ import { execFileSync } from 'node:child_process'
11
+ import { readFileSync, statSync } from 'node:fs'
12
+ import os from 'node:os'
13
+ import { delimiter, join, sep } from 'node:path'
14
+ import { pathToFileURL } from 'node:url'
15
+
16
+ const TEMPLATE_VARIABLE_PATTERN = /\$\{([^}]+)\}/g
17
+
18
+ export const DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'
19
+
20
+ const MODEL_TEMPLATE_URL = new URL('../system-prompt-templates/systemPromptModel.md', import.meta.url)
21
+ const PRESETS_DIR_URL = new URL('../system-prompt-templates/presets/', import.meta.url)
22
+ const PRESETS_INDEX_URL = new URL('index.json', PRESETS_DIR_URL)
23
+ const PRESET_ID_PATTERN = /^[A-Za-z0-9._-]+$/
24
+
25
+ function stringifyTemplateValue(value) {
26
+ if (typeof value === 'string') return value
27
+ if (typeof value === 'number') {
28
+ return Number.isFinite(value) ? String(value) : ''
29
+ }
30
+ return ''
31
+ }
32
+
33
+ function stringOrEmpty(readValue) {
34
+ try {
35
+ const value = readValue()
36
+ if (value === null || value === undefined) return ''
37
+ return String(value)
38
+ } catch {
39
+ return ''
40
+ }
41
+ }
42
+
43
+ function numberOrEmpty(readValue) {
44
+ try {
45
+ const value = Number(readValue())
46
+ return Number.isFinite(value) ? value : ''
47
+ } catch {
48
+ return ''
49
+ }
50
+ }
51
+
52
+ function envString(name) {
53
+ return stringOrEmpty(() => process.env[name] ?? '')
54
+ }
55
+
56
+ function commandOutput(command, args, cwd) {
57
+ return stringOrEmpty(() =>
58
+ execFileSync(command, args, {
59
+ cwd,
60
+ encoding: 'utf8',
61
+ stdio: ['ignore', 'pipe', 'ignore'],
62
+ }).trim(),
63
+ )
64
+ }
65
+
66
+ function firstNonEmpty(...values) {
67
+ return values.find(value => value.length > 0) ?? ''
68
+ }
69
+
70
+ function currentDate(timeZone, date) {
71
+ if (timeZone.length > 0) {
72
+ const formatted = stringOrEmpty(() =>
73
+ new Intl.DateTimeFormat('en-CA', {
74
+ timeZone,
75
+ year: 'numeric',
76
+ month: '2-digit',
77
+ day: '2-digit',
78
+ }).format(date),
79
+ )
80
+ if (formatted.length > 0) return formatted
81
+ }
82
+ return stringOrEmpty(() => date.toISOString().slice(0, 10))
83
+ }
84
+
85
+ function mergeSystemPromptVariables(base, overrides) {
86
+ return {
87
+ environment: { ...base.environment, ...overrides.environment },
88
+ git: { ...base.git, ...overrides.git },
89
+ os: { ...base.os, ...overrides.os },
90
+ runtime: { ...base.runtime, ...overrides.runtime },
91
+ time: { ...base.time, ...overrides.time },
92
+ permissions: { ...base.permissions, ...overrides.permissions },
93
+ sandbox: { ...base.sandbox, ...overrides.sandbox },
94
+ terminal: { ...base.terminal, ...overrides.terminal },
95
+ filesystem: { ...base.filesystem, ...overrides.filesystem },
96
+ model: { ...base.model, ...overrides.model },
97
+ memory: { ...base.memory, ...overrides.memory },
98
+ scratchpad: { ...base.scratchpad, ...overrides.scratchpad },
99
+ }
100
+ }
101
+
102
+ function slugifyPath(value) {
103
+ return value.replace(/[^A-Za-z0-9]/g, '-')
104
+ }
105
+
106
+ function directoryExists(dir) {
107
+ if (dir.length === 0) return false
108
+ try {
109
+ return statSync(dir).isDirectory()
110
+ } catch {
111
+ return false
112
+ }
113
+ }
114
+
115
+ function resolveMemory(home, cwd) {
116
+ const overrideDir = firstNonEmpty(
117
+ envString('CC_MEMORY_DIR'),
118
+ envString('CLAUDE_MEMORY_DIR'),
119
+ )
120
+ const dir =
121
+ overrideDir.length > 0
122
+ ? overrideDir
123
+ : home.length > 0
124
+ ? join(home, '.claude', 'projects', slugifyPath(cwd), 'memory') + sep
125
+ : ''
126
+ const enabled = directoryExists(dir)
127
+ const index = enabled
128
+ ? stringOrEmpty(() => readFileSync(join(dir, 'MEMORY.md'), 'utf8'))
129
+ : ''
130
+ return { dir, index, enabled: enabled ? 'true' : 'false' }
131
+ }
132
+
133
+ export function createSystemPromptVariables(overrides = {}) {
134
+ const cwd = stringOrEmpty(() => process.cwd())
135
+ const now = new Date()
136
+ const timeZone = stringOrEmpty(
137
+ () => Intl.DateTimeFormat().resolvedOptions().timeZone,
138
+ )
139
+ const isGitRepository =
140
+ commandOutput('git', ['rev-parse', '--is-inside-work-tree'], cwd) === 'true'
141
+ const gitRoot = isGitRepository
142
+ ? commandOutput('git', ['rev-parse', '--show-toplevel'], cwd)
143
+ : ''
144
+ const gitBranch = isGitRepository
145
+ ? firstNonEmpty(
146
+ commandOutput('git', ['branch', '--show-current'], cwd),
147
+ commandOutput('git', ['rev-parse', '--short', 'HEAD'], cwd),
148
+ )
149
+ : ''
150
+ const gitMainBranch = isGitRepository
151
+ ? firstNonEmpty(
152
+ commandOutput('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], cwd).replace(/^origin\//, ''),
153
+ commandOutput('git', ['config', '--get', 'init.defaultBranch'], cwd),
154
+ )
155
+ : ''
156
+
157
+ const variables = {
158
+ environment: {
159
+ cwd,
160
+ originalCwd: envString('PWD'),
161
+ home: envString('HOME'),
162
+ user: firstNonEmpty(envString('USER'), envString('USERNAME')),
163
+ workspaceRoots: firstNonEmpty(
164
+ envString('CODEX_WORKSPACE_ROOTS'),
165
+ envString('CLAUDE_WORKSPACE_ROOTS'),
166
+ ),
167
+ path: envString('PATH'),
168
+ lang: firstNonEmpty(envString('LANG'), envString('LC_ALL')),
169
+ },
170
+ git: {
171
+ isRepository: isGitRepository ? 'true' : 'false',
172
+ root: gitRoot,
173
+ branch: gitBranch,
174
+ mainBranch: gitMainBranch,
175
+ userName: isGitRepository
176
+ ? commandOutput('git', ['config', 'user.name'], cwd)
177
+ : '',
178
+ status: isGitRepository
179
+ ? commandOutput('git', ['status', '--short'], cwd)
180
+ : '',
181
+ recentCommits: isGitRepository
182
+ ? commandOutput('git', ['log', '--oneline', '-5'], cwd)
183
+ : '',
184
+ },
185
+ os: {
186
+ platform: stringOrEmpty(() => process.platform),
187
+ type: stringOrEmpty(() => os.type()),
188
+ arch: stringOrEmpty(() => os.arch()),
189
+ shell: envString('SHELL'),
190
+ version: stringOrEmpty(() => os.version()),
191
+ release: stringOrEmpty(() => os.release()),
192
+ hostname: stringOrEmpty(() => os.hostname()),
193
+ availableParallelism: numberOrEmpty(() => os.availableParallelism()),
194
+ totalMemory: numberOrEmpty(() => os.totalmem()),
195
+ freeMemory: numberOrEmpty(() => os.freemem()),
196
+ uptime: numberOrEmpty(() => os.uptime()),
197
+ },
198
+ runtime: {
199
+ nodeVersion: stringOrEmpty(() => process.version),
200
+ execPath: stringOrEmpty(() => process.execPath),
201
+ pid: numberOrEmpty(() => process.pid),
202
+ ppid: numberOrEmpty(() => process.ppid),
203
+ },
204
+ time: {
205
+ current: stringOrEmpty(() => now.toString()),
206
+ iso: stringOrEmpty(() => now.toISOString()),
207
+ date: currentDate(timeZone, now),
208
+ timezone: timeZone,
209
+ },
210
+ permissions: {
211
+ mode: firstNonEmpty(
212
+ envString('CLAUDE_PERMISSION_MODE'),
213
+ envString('CODEX_PERMISSION_MODE'),
214
+ ),
215
+ approvalsReviewer: envString('CODEX_APPROVALS_REVIEWER'),
216
+ },
217
+ sandbox: {
218
+ mode: firstNonEmpty(envString('SANDBOX_MODE'), envString('CODEX_SANDBOX_MODE')),
219
+ networkAccess: firstNonEmpty(
220
+ envString('NETWORK_ACCESS'),
221
+ envString('CODEX_NETWORK_ACCESS'),
222
+ ),
223
+ writableRoots: firstNonEmpty(
224
+ envString('WRITABLE_ROOTS'),
225
+ envString('CODEX_WRITABLE_ROOTS'),
226
+ ),
227
+ },
228
+ terminal: {
229
+ term: envString('TERM'),
230
+ colorTerm: envString('COLORTERM'),
231
+ columns: numberOrEmpty(() => process.stdout.columns),
232
+ rows: numberOrEmpty(() => process.stdout.rows),
233
+ },
234
+ filesystem: {
235
+ tmpdir: stringOrEmpty(() => os.tmpdir()),
236
+ pathSeparator: sep,
237
+ pathDelimiter: delimiter,
238
+ },
239
+ model: {
240
+ name: firstNonEmpty(envString('CLAUDE_MODEL'), envString('ANTHROPIC_MODEL')),
241
+ knowledgeCutoff: envString('CLAUDE_KNOWLEDGE_CUTOFF'),
242
+ },
243
+ memory: resolveMemory(envString('HOME'), cwd),
244
+ scratchpad: {
245
+ dir: firstNonEmpty(
246
+ envString('CC_SCRATCHPAD_DIR'),
247
+ envString('CLAUDE_SCRATCHPAD_DIR'),
248
+ ),
249
+ },
250
+ }
251
+
252
+ return mergeSystemPromptVariables(variables, overrides)
253
+ }
254
+
255
+ function readDottedPath(path, variables) {
256
+ const normalizedPath = path.trim()
257
+ if (Object.prototype.hasOwnProperty.call(variables, normalizedPath)) {
258
+ return variables[normalizedPath]
259
+ }
260
+
261
+ return normalizedPath.split('.').reduce((current, key) => {
262
+ if (
263
+ current === null ||
264
+ current === undefined ||
265
+ typeof current !== 'object' ||
266
+ !Object.prototype.hasOwnProperty.call(current, key)
267
+ ) {
268
+ return undefined
269
+ }
270
+ return current[key]
271
+ }, variables)
272
+ }
273
+
274
+ function replaceTemplateVariable(rawMatch, rawName, variables, missingVariableMode) {
275
+ const value = readDottedPath(rawName, variables)
276
+ if (value !== undefined) {
277
+ return stringifyTemplateValue(value)
278
+ }
279
+
280
+ if (missingVariableMode === 'keep') return rawMatch
281
+ if (missingVariableMode === 'throw') {
282
+ throw new Error(`Missing system prompt template variable: ${rawName.trim()}`)
283
+ }
284
+ return ''
285
+ }
286
+
287
+ export function listTemplateVariables(markdownTemplate) {
288
+ const names = new Set()
289
+ for (const match of markdownTemplate.matchAll(TEMPLATE_VARIABLE_PATTERN)) {
290
+ names.add(match[1].trim())
291
+ }
292
+ return Array.from(names).sort()
293
+ }
294
+
295
+ export function createSystemPrompt(markdownTemplate, options) {
296
+ const variables = options.variables
297
+ const missingVariableMode = options.missingVariableMode ?? 'throw'
298
+
299
+ return markdownTemplate.replace(TEMPLATE_VARIABLE_PATTERN, (match, name) =>
300
+ replaceTemplateVariable(match, name, variables, missingVariableMode),
301
+ )
302
+ }
303
+
304
+ function isTrue(value) {
305
+ return value === 'true'
306
+ }
307
+
308
+ // Ordered header -> key mapping. Section prose lives only in
309
+ // systemPromptModel.md (parsed at runtime), never duplicated here.
310
+ export const SYSTEM_PROMPT_SECTIONS = [
311
+ { key: 'preamble', header: null },
312
+ { key: 'environment', header: '# Environment' },
313
+ { key: 'operatingSystem', header: '# Operating system' },
314
+ { key: 'runtime', header: '# Runtime' },
315
+ { key: 'time', header: '# Time' },
316
+ { key: 'permissionsSandbox', header: '# Permissions and sandbox' },
317
+ { key: 'terminal', header: '# Terminal' },
318
+ { key: 'filesystem', header: '# Filesystem' },
319
+ { key: 'model', header: '# Model' },
320
+ { key: 'git', header: '# Git' },
321
+ {
322
+ key: 'memory',
323
+ header: '# Memory',
324
+ includeWhen: variables => isTrue(variables.memory.enabled),
325
+ },
326
+ {
327
+ key: 'scratchpad',
328
+ header: '# Scratchpad Directory',
329
+ includeWhen: variables => variables.scratchpad.dir.length > 0,
330
+ },
331
+ { key: 'contextManagement', header: '# Context management' },
332
+ ]
333
+
334
+ // Splits a model template into its preamble and a header -> raw-section-text map.
335
+ // Level-1 headers (`# `) inside fenced code blocks are ignored so fenced
336
+ // examples (e.g. the memory frontmatter) do not start new sections.
337
+ function splitTemplate(markdownTemplate) {
338
+ const parts = markdownTemplate.split(DYNAMIC_BOUNDARY)
339
+ if (parts.length !== 2) {
340
+ throw new Error(
341
+ `System prompt template must contain exactly one ${DYNAMIC_BOUNDARY} marker (found ${parts.length - 1})`,
342
+ )
343
+ }
344
+ const preamble = parts[0].trim()
345
+ const sectionsByHeader = new Map()
346
+ const lines = parts[1].split('\n')
347
+ let currentHeader = null
348
+ let buffer = []
349
+ let inFence = false
350
+
351
+ const flush = () => {
352
+ if (currentHeader !== null) {
353
+ sectionsByHeader.set(currentHeader, buffer.join('\n').trim())
354
+ }
355
+ }
356
+
357
+ for (const line of lines) {
358
+ if (line.trimStart().startsWith('```')) {
359
+ inFence = !inFence
360
+ }
361
+ if (!inFence && line.startsWith('# ')) {
362
+ flush()
363
+ currentHeader = line.trim()
364
+ buffer = [line]
365
+ continue
366
+ }
367
+ if (currentHeader !== null) buffer.push(line)
368
+ }
369
+ flush()
370
+ return { preamble, sectionsByHeader }
371
+ }
372
+
373
+ export function assembleSystemPrompt(markdownTemplate, options) {
374
+ const { variables, missingVariableMode, sections } = options
375
+ const known = new Set(SYSTEM_PROMPT_SECTIONS.map(section => section.key))
376
+ if (sections) {
377
+ for (const key of sections) {
378
+ if (!known.has(key)) {
379
+ throw new Error(`Unknown system prompt section key: ${key}`)
380
+ }
381
+ }
382
+ }
383
+ const selected = sections ? new Set(sections) : known
384
+ const { preamble, sectionsByHeader } = splitTemplate(markdownTemplate)
385
+
386
+ const rendered = []
387
+ for (const section of SYSTEM_PROMPT_SECTIONS) {
388
+ if (!selected.has(section.key)) continue
389
+ if (section.includeWhen && !section.includeWhen(variables)) continue
390
+
391
+ let raw
392
+ if (section.header === null) {
393
+ raw = preamble
394
+ } else {
395
+ const found = sectionsByHeader.get(section.header)
396
+ // A template may legitimately include only a subset of sections (e.g. the
397
+ // presets omit git and most environment blocks); skip any that are absent.
398
+ if (found === undefined) continue
399
+ raw = found
400
+ }
401
+ rendered.push(createSystemPrompt(raw, { variables, missingVariableMode }))
402
+ }
403
+
404
+ // The MEMORY.md contents are runtime data, not authored prose, and they are
405
+ // already self-titled ("# Memory index"). Append them verbatim as a trailing
406
+ // block whenever the memory section is present, without re-templating (the
407
+ // index may legitimately contain literal ${...} text).
408
+ if (
409
+ selected.has('memory') &&
410
+ isTrue(variables.memory.enabled) &&
411
+ variables.memory.index.trim().length > 0
412
+ ) {
413
+ rendered.push(variables.memory.index)
414
+ }
415
+
416
+ return rendered.map(part => part.trim()).filter(Boolean).join('\n\n')
417
+ }
418
+
419
+ export function loadModelTemplate() {
420
+ return readFileSync(MODEL_TEMPLATE_URL, 'utf8')
421
+ }
422
+
423
+ // The human-readable reference for the ${...} template variables (rendered in the
424
+ // "Edit System Prompt" modal's parameter-docs popup).
425
+ export function loadVariablesDoc() {
426
+ return readFileSync(new URL('../system-prompt-templates/systemPromptVariables.md', import.meta.url), 'utf8')
427
+ }
428
+
429
+ function assertSafePresetId(id) {
430
+ if (typeof id !== 'string' || !PRESET_ID_PATTERN.test(id) || id.includes('..')) {
431
+ throw new Error(`Invalid preset id: ${JSON.stringify(id)}`)
432
+ }
433
+ return id
434
+ }
435
+
436
+ export function loadPreset(id) {
437
+ // Accept an optional `.md` suffix so `... deepseek-v4-pro.md` resolves too.
438
+ const normalized = typeof id === 'string' ? id.replace(/\.md$/i, '') : id
439
+ const safeId = assertSafePresetId(normalized)
440
+ return readFileSync(new URL(`${safeId}.md`, PRESETS_DIR_URL), 'utf8')
441
+ }
442
+
443
+ export function listPresets() {
444
+ const manifest = JSON.parse(readFileSync(PRESETS_INDEX_URL, 'utf8'))
445
+ return manifest
446
+ }
447
+
448
+ // Strips HTML editor comments (`<!-- ... -->`) that document the preset file but
449
+ // must not leak into the rendered prompt.
450
+ function stripEditorComments(source) {
451
+ return source.replace(/<!--[\s\S]*?-->/g, '').replace(/^\s+/, '')
452
+ }
453
+
454
+ // Normalizes a preset into a full template. Two shapes are supported:
455
+ // - Self-contained (what the shipped presets use): the preset already has its
456
+ // own DYNAMIC_BOUNDARY + dynamic sections, so it is returned as-is.
457
+ // - Preamble-only (no boundary): composed with systemPromptModel.md's shared
458
+ // dynamic tail, so environment snippets can stay unified. None of the shipped
459
+ // presets currently use this path.
460
+ function toFullTemplate(presetSource) {
461
+ const source = stripEditorComments(presetSource)
462
+ if (source.includes(DYNAMIC_BOUNDARY)) return source
463
+ const modelParts = loadModelTemplate().split(DYNAMIC_BOUNDARY)
464
+ return `${source.trim()}\n\n${DYNAMIC_BOUNDARY}\n${modelParts[1]}`
465
+ }
466
+
467
+ export function renderModel(options) {
468
+ return assembleSystemPrompt(loadModelTemplate(), options)
469
+ }
470
+
471
+ export function renderPreset(id, options) {
472
+ return assembleSystemPrompt(toFullTemplate(loadPreset(id)), options)
473
+ }
474
+
475
+ // Returns a preset composed into a full template with the dynamic-boundary marker
476
+ // removed and `${...}` placeholders left LITERAL (no variable substitution). This
477
+ // is the raw text used to pre-fill the "Edit System Prompt" editor, where the user
478
+ // tweaks it before saving.
479
+ export function renderPresetTemplate(id) {
480
+ return toFullTemplate(loadPreset(id))
481
+ .split(DYNAMIC_BOUNDARY)
482
+ .map(part => part.trim())
483
+ .filter(Boolean)
484
+ .join('\n\n')
485
+ }
486
+
487
+ function runCli() {
488
+ const arg = process.argv[2]
489
+ if (arg === '--list' || arg === '-l') {
490
+ process.stdout.write(JSON.stringify(listPresets(), null, 2) + '\n')
491
+ return
492
+ }
493
+ const variables = createSystemPromptVariables()
494
+ const output = arg
495
+ ? renderPreset(arg, { variables, missingVariableMode: 'empty' })
496
+ : renderModel({ variables, missingVariableMode: 'empty' })
497
+ process.stdout.write(output + '\n')
498
+ }
499
+
500
+ // Guard against `process.argv[1]` being undefined (e.g. under `node --test`,
501
+ // where importing this module must not run the CLI).
502
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
503
+ runCli()
504
+ }
@@ -0,0 +1,67 @@
1
+ // Serves the built-in system-prompt presets (server/system-prompt-templates/presets/*)
2
+ // to the "Edit System Prompt" modal. Reads the manifest and renders each preset into
3
+ // raw editor text (placeholders left literal) via the system-prompt builder.
4
+ //
5
+ // This path is pure file reads + string ops — it does NOT call
6
+ // createSystemPromptVariables, so no git subprocess is spawned when serving.
7
+ import { listPresets, renderPresetTemplate, loadVariablesDoc } from './create_system_prompt.js';
8
+
9
+ // Returns a flat list of presets with their raw editor text:
10
+ // [{ id, title, description, match, category, defaultMode, text }]
11
+ // Best-effort per entry: a preset that fails to load/render is logged and
12
+ // skipped rather than failing the whole list.
13
+ export function listSystemPromptPresets() {
14
+ let manifest;
15
+ try {
16
+ manifest = listPresets();
17
+ } catch (e) {
18
+ console.warn('[CC Viewer] system-prompt presets manifest unreadable:', e.message);
19
+ return [];
20
+ }
21
+ const categories = (manifest && manifest.categories) || {};
22
+ const out = [];
23
+ for (const [category, entries] of Object.entries(categories)) {
24
+ if (!Array.isArray(entries)) continue;
25
+ for (const entry of entries) {
26
+ if (!entry || typeof entry.id !== 'string') continue;
27
+ let text;
28
+ try {
29
+ text = renderPresetTemplate(entry.id);
30
+ } catch (e) {
31
+ console.warn(`[CC Viewer] system-prompt preset "${entry.id}" render failed:`, e.message);
32
+ continue;
33
+ }
34
+ out.push({
35
+ id: entry.id,
36
+ title: entry.title || entry.id,
37
+ description: entry.description || '',
38
+ match: entry.match || '',
39
+ category,
40
+ defaultMode: entry.defaultMode === 'override' ? 'override' : 'append',
41
+ text,
42
+ });
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+
48
+ // Returns the ${...} template-variable reference (systemPromptVariables.md) as
49
+ // markdown, or '' if it can't be read (best-effort; the modal hides its help
50
+ // affordance when empty).
51
+ export function getSystemPromptVariablesDoc() {
52
+ try {
53
+ return loadVariablesDoc();
54
+ } catch (e) {
55
+ console.warn('[CC Viewer] system-prompt variables doc unreadable:', e.message);
56
+ return '';
57
+ }
58
+ }
59
+
60
+ // Groups a flat preset list by category, preserving first-seen order.
61
+ export function groupPresetsByCategory(presets) {
62
+ const categories = {};
63
+ for (const p of presets) {
64
+ (categories[p.category] || (categories[p.category] = [])).push(p);
65
+ }
66
+ return categories;
67
+ }
@@ -12,6 +12,7 @@ import {
12
12
  MODEL_PROMPT_DIR, normalizeModelName, listModelPrompts,
13
13
  writeModelPrompt, deleteModelPrompt,
14
14
  } from '../lib/model-system-prompts.js';
15
+ import { listSystemPromptPresets, groupPresetsByCategory, getSystemPromptVariablesDoc } from '../lib/system-prompt-presets.js';
15
16
  import { LOG_DIR } from '../../findcc.js';
16
17
 
17
18
  // 当前工作区目录全部由服务端解析(绝不接收客户端路径 → 无遍历面):
@@ -148,9 +149,26 @@ function postModelPrompts(req, res, parsedUrl, isLocal, deps) {
148
149
  });
149
150
  }
150
151
 
152
+ // 内置系统提示词预设(server/system-prompt-templates/presets/*):只读,返回可直接回填编辑器的原始模板文本
153
+ // (占位符保持字面量,不做变量替换),供「+ 添加模型」时按名称匹配/下拉选择预填。
154
+ function getSystemPromptPresets(req, res, parsedUrl, isLocal, deps) {
155
+ try {
156
+ const presets = listSystemPromptPresets();
157
+ sendJson(res, 200, {
158
+ presets,
159
+ categories: groupPresetsByCategory(presets),
160
+ variablesDoc: getSystemPromptVariablesDoc(),
161
+ });
162
+ } catch (e) {
163
+ console.error('[CC Viewer] expert system-prompt-presets GET failed:', e.message);
164
+ sendJson(res, 500, { error: 'read_failed' });
165
+ }
166
+ }
167
+
151
168
  export const expertRoutes = [
152
169
  { method: 'GET', match: 'exact', path: '/api/expert/system-text', handler: getSystemText },
153
170
  { method: 'POST', match: 'exact', path: '/api/expert/system-text', handler: postSystemText },
154
171
  { method: 'GET', match: 'exact', path: '/api/expert/model-prompts', handler: getModelPrompts },
155
172
  { method: 'POST', match: 'exact', path: '/api/expert/model-prompts', handler: postModelPrompts },
173
+ { method: 'GET', match: 'exact', path: '/api/expert/system-prompt-presets', handler: getSystemPromptPresets },
156
174
  ];
@@ -0,0 +1,63 @@
1
+ <!--
2
+ Preset: GLM-5.2 (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.
6
+ -->
7
+
8
+ You are ${model.name}, an interactive agent that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
9
+
10
+ IMPORTANT: Assist with defensive software engineering work. Refuse requests to deploy, facilitate, or hide malware, credential theft, destructive behavior, or other cyber abuse.
11
+ 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.
12
+
13
+ # Doing tasks
14
+ - Treat unclear instructions in the context of software engineering and the current working directory.
15
+ - Read the relevant code before proposing or making changes, and follow the conventions already present in the file.
16
+ - Keep changes scoped to the request: no unrequested features, refactors, fallbacks, or one-off abstractions.
17
+ - When an approach fails, diagnose the error before trying something else; don't repeat the same failing action.
18
+ - Avoid security vulnerabilities (injection, XSS, path traversal, and the OWASP top 10); fix any insecure code you write.
19
+ - Validate changes by running the relevant tests or code path before reporting completion.
20
+
21
+ # Using tools
22
+ - Prefer the dedicated tool for reading files, editing files, searching contents, and running commands over ad-hoc shell commands.
23
+ - Issue independent tool calls in parallel when they have no dependencies.
24
+ - Track multi-step work and mark each step complete as you go.
25
+
26
+ # Executing actions with care
27
+ Weigh reversibility and blast radius. Local, reversible actions like editing files or running tests are fine. Confirm with the user before hard-to-reverse or shared-system actions: deleting files or branches, force-pushing, resetting, sending messages, or posting to external services. Investigate unexpected state before overwriting it.
28
+
29
+ # Tone and style
30
+ - Keep output brief and direct; lead with the answer or action. No filler, and no emojis unless the user asks.
31
+ - Reference code with the `file_path:line_number` pattern.
32
+ - Respond in the user's language, using the `${environment.lang}` locale when the language is not otherwise clear.
33
+
34
+ __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__
35
+
36
+ # Environment
37
+ - Platform: ${os.platform}
38
+ - OS Version: ${os.version}
39
+ - Architecture: ${os.arch}
40
+ - Shell: ${os.shell}
41
+
42
+ # Memory
43
+
44
+ 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:
45
+
46
+ ```markdown
47
+ ---
48
+ name: <short-kebab-case-slug>
49
+ description: <one-line summary — used to decide relevance during recall>
50
+ metadata:
51
+ type: user | feedback | project | reference
52
+ ---
53
+
54
+ <the fact; for feedback/project, follow with **Why:** and **How to apply:** lines. Link related memories with [[their-name]].>
55
+ ```
56
+
57
+ 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.
58
+
59
+ `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).
60
+
61
+ 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.
62
+
63
+ 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.