cc-viewer 1.6.342 → 1.6.344
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 +1 -1
- package/dist/assets/App-BjHJ9KJ6.css +1 -0
- package/dist/assets/App-D-TSp4Vf.js +2 -0
- package/dist/assets/{MdxEditorPanel-CaDMFlsk.js → MdxEditorPanel-BneOGbUW.js} +1 -1
- package/dist/assets/{Mobile-C22UBGse.js → Mobile-COacALMA.js} +1 -1
- package/dist/assets/index-C1YvPKov.js +2 -0
- package/dist/assets/seqResourceLoaders-BPIKMDJV.js +2 -0
- package/dist/assets/{seqResourceLoaders-g06U0FFU.css → seqResourceLoaders-CFOTQBIe.css} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/server/lib/create_system_prompt.js +525 -0
- package/server/lib/system-prompt-presets.js +68 -0
- package/server/routes/expert.js +21 -0
- package/server/system-prompt-templates/presets/GLM-5.2.md +67 -0
- package/server/system-prompt-templates/presets/Qwen-3.7-Max.md +67 -0
- package/server/system-prompt-templates/presets/deepseek-v4-flash.md +62 -0
- package/server/system-prompt-templates/presets/deepseek-v4-pro.md +70 -0
- package/server/system-prompt-templates/presets/index.json +47 -0
- package/server/system-prompt-templates/presets/kimi-k2.7-code.md +69 -0
- package/server/system-prompt-templates/reference/claude-code-cli-startup-options.md +325 -0
- package/server/system-prompt-templates/reference/prompt-control-tokens-deepseek-v4.md +178 -0
- package/server/system-prompt-templates/reference/prompt-control-tokens-qwen3.md +227 -0
- package/server/system-prompt-templates/reference/prompt-xml-tags-fable-5.md +101 -0
- package/server/system-prompt-templates/reference/prompt-xml-tags-opus-4-8.md +113 -0
- package/server/system-prompt-templates/systemPromptModel.md +168 -0
- package/server/system-prompt-templates/systemPromptVariables.ar.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.da.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.de.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.es.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.fr.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.it.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.ja.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.ko.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.no.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.pl.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.pt-BR.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.ru.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.th.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.tr.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.uk.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.zh-TW.md +113 -0
- package/server/system-prompt-templates/systemPromptVariables.zh.md +113 -0
- package/dist/assets/App-BFcpzWEl.js +0 -2
- package/dist/assets/App-D5BI6yGO.css +0 -1
- package/dist/assets/index-CtSbjm-X.js +0 -2
- package/dist/assets/seqResourceLoaders-BqKidNto.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-
|
|
24
|
+
<script type="module" crossorigin src="./assets/index-C1YvPKov.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.
|
|
3
|
+
"version": "1.6.344",
|
|
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,525 @@
|
|
|
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
|
+
// Locales with a translated systemPromptVariables.<locale>.md sibling. Mirrors the
|
|
424
|
+
// UI's LANG_OPTIONS (src/i18n.js) minus 'en', which is the base file itself.
|
|
425
|
+
export const VARIABLES_DOC_LOCALES = [
|
|
426
|
+
'zh', 'zh-TW', 'ko', 'ja', 'de', 'es', 'fr', 'it', 'da',
|
|
427
|
+
'pl', 'ru', 'ar', 'no', 'pt-BR', 'th', 'tr', 'uk',
|
|
428
|
+
]
|
|
429
|
+
|
|
430
|
+
// The human-readable reference for the ${...} template variables (rendered in the
|
|
431
|
+
// "Edit System Prompt" modal's parameter-docs popup). `lang` is whitelisted against
|
|
432
|
+
// VARIABLES_DOC_LOCALES; 'en', unknown values, and a missing localized file all fall
|
|
433
|
+
// back to the English base document.
|
|
434
|
+
export function loadVariablesDoc(lang) {
|
|
435
|
+
if (typeof lang === 'string' && VARIABLES_DOC_LOCALES.includes(lang)) {
|
|
436
|
+
try {
|
|
437
|
+
return readFileSync(
|
|
438
|
+
new URL(`../system-prompt-templates/systemPromptVariables.${lang}.md`, import.meta.url),
|
|
439
|
+
'utf8',
|
|
440
|
+
)
|
|
441
|
+
} catch (e) {
|
|
442
|
+
// A whitelisted locale should always have a shipped translation file, so a
|
|
443
|
+
// read failure here is diagnostic — log it, then fall back to the English base.
|
|
444
|
+
console.warn(`[CC Viewer] localized variables doc unreadable for ${lang}:`, e.message)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return readFileSync(new URL('../system-prompt-templates/systemPromptVariables.md', import.meta.url), 'utf8')
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function assertSafePresetId(id) {
|
|
451
|
+
if (typeof id !== 'string' || !PRESET_ID_PATTERN.test(id) || id.includes('..')) {
|
|
452
|
+
throw new Error(`Invalid preset id: ${JSON.stringify(id)}`)
|
|
453
|
+
}
|
|
454
|
+
return id
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function loadPreset(id) {
|
|
458
|
+
// Accept an optional `.md` suffix so `... deepseek-v4-pro.md` resolves too.
|
|
459
|
+
const normalized = typeof id === 'string' ? id.replace(/\.md$/i, '') : id
|
|
460
|
+
const safeId = assertSafePresetId(normalized)
|
|
461
|
+
return readFileSync(new URL(`${safeId}.md`, PRESETS_DIR_URL), 'utf8')
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export function listPresets() {
|
|
465
|
+
const manifest = JSON.parse(readFileSync(PRESETS_INDEX_URL, 'utf8'))
|
|
466
|
+
return manifest
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Strips HTML editor comments (`<!-- ... -->`) that document the preset file but
|
|
470
|
+
// must not leak into the rendered prompt.
|
|
471
|
+
function stripEditorComments(source) {
|
|
472
|
+
return source.replace(/<!--[\s\S]*?-->/g, '').replace(/^\s+/, '')
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Normalizes a preset into a full template. Two shapes are supported:
|
|
476
|
+
// - Self-contained (what the shipped presets use): the preset already has its
|
|
477
|
+
// own DYNAMIC_BOUNDARY + dynamic sections, so it is returned as-is.
|
|
478
|
+
// - Preamble-only (no boundary): composed with systemPromptModel.md's shared
|
|
479
|
+
// dynamic tail, so environment snippets can stay unified. None of the shipped
|
|
480
|
+
// presets currently use this path.
|
|
481
|
+
function toFullTemplate(presetSource) {
|
|
482
|
+
const source = stripEditorComments(presetSource)
|
|
483
|
+
if (source.includes(DYNAMIC_BOUNDARY)) return source
|
|
484
|
+
const modelParts = loadModelTemplate().split(DYNAMIC_BOUNDARY)
|
|
485
|
+
return `${source.trim()}\n\n${DYNAMIC_BOUNDARY}\n${modelParts[1]}`
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export function renderModel(options) {
|
|
489
|
+
return assembleSystemPrompt(loadModelTemplate(), options)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export function renderPreset(id, options) {
|
|
493
|
+
return assembleSystemPrompt(toFullTemplate(loadPreset(id)), options)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Returns a preset composed into a full template with the dynamic-boundary marker
|
|
497
|
+
// removed and `${...}` placeholders left LITERAL (no variable substitution). This
|
|
498
|
+
// is the raw text used to pre-fill the "Edit System Prompt" editor, where the user
|
|
499
|
+
// tweaks it before saving.
|
|
500
|
+
export function renderPresetTemplate(id) {
|
|
501
|
+
return toFullTemplate(loadPreset(id))
|
|
502
|
+
.split(DYNAMIC_BOUNDARY)
|
|
503
|
+
.map(part => part.trim())
|
|
504
|
+
.filter(Boolean)
|
|
505
|
+
.join('\n\n')
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function runCli() {
|
|
509
|
+
const arg = process.argv[2]
|
|
510
|
+
if (arg === '--list' || arg === '-l') {
|
|
511
|
+
process.stdout.write(JSON.stringify(listPresets(), null, 2) + '\n')
|
|
512
|
+
return
|
|
513
|
+
}
|
|
514
|
+
const variables = createSystemPromptVariables()
|
|
515
|
+
const output = arg
|
|
516
|
+
? renderPreset(arg, { variables, missingVariableMode: 'empty' })
|
|
517
|
+
: renderModel({ variables, missingVariableMode: 'empty' })
|
|
518
|
+
process.stdout.write(output + '\n')
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Guard against `process.argv[1]` being undefined (e.g. under `node --test`,
|
|
522
|
+
// where importing this module must not run the CLI).
|
|
523
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
524
|
+
runCli()
|
|
525
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
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). `lang` selects a localized systemPromptVariables.<lang>.md
|
|
51
|
+
// when one exists; anything else falls back to the English base.
|
|
52
|
+
export function getSystemPromptVariablesDoc(lang) {
|
|
53
|
+
try {
|
|
54
|
+
return loadVariablesDoc(lang);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.warn('[CC Viewer] system-prompt variables doc unreadable:', e.message);
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Groups a flat preset list by category, preserving first-seen order.
|
|
62
|
+
export function groupPresetsByCategory(presets) {
|
|
63
|
+
const categories = {};
|
|
64
|
+
for (const p of presets) {
|
|
65
|
+
(categories[p.category] || (categories[p.category] = [])).push(p);
|
|
66
|
+
}
|
|
67
|
+
return categories;
|
|
68
|
+
}
|
package/server/routes/expert.js
CHANGED
|
@@ -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,29 @@ 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
|
+
// Optional UI-language hint for the variables doc. parsedUrl may be null in
|
|
157
|
+
// direct handler invocations (tests); the value is whitelisted downstream.
|
|
158
|
+
const lang = parsedUrl?.searchParams?.get('lang') || undefined;
|
|
159
|
+
const presets = listSystemPromptPresets();
|
|
160
|
+
sendJson(res, 200, {
|
|
161
|
+
presets,
|
|
162
|
+
categories: groupPresetsByCategory(presets),
|
|
163
|
+
variablesDoc: getSystemPromptVariablesDoc(lang),
|
|
164
|
+
});
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.error('[CC Viewer] expert system-prompt-presets GET failed:', e.message);
|
|
167
|
+
sendJson(res, 500, { error: 'read_failed' });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
151
171
|
export const expertRoutes = [
|
|
152
172
|
{ method: 'GET', match: 'exact', path: '/api/expert/system-text', handler: getSystemText },
|
|
153
173
|
{ method: 'POST', match: 'exact', path: '/api/expert/system-text', handler: postSystemText },
|
|
154
174
|
{ method: 'GET', match: 'exact', path: '/api/expert/model-prompts', handler: getModelPrompts },
|
|
155
175
|
{ method: 'POST', match: 'exact', path: '/api/expert/model-prompts', handler: postModelPrompts },
|
|
176
|
+
{ method: 'GET', match: 'exact', path: '/api/expert/system-prompt-presets', handler: getSystemPromptPresets },
|
|
156
177
|
];
|