dsh-muv-table 0.2.1 → 0.2.3

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/lib/index.js CHANGED
@@ -12,8 +12,46 @@ import { fileURLToPath } from 'node:url'
12
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
13
13
  let PANEL_HTML = ''
14
14
  try { PANEL_HTML = fs.readFileSync(path.join(__dirname, 'panel.html'), 'utf8') } catch (_) {}
15
- const PRESETS_ROOT = path.join(os.homedir(), '.dsh', '.agent-presets')
16
- const TAVERN_PRESET_DIR = path.join(PRESETS_ROOT, 'tavern-lite')
15
+
16
+ // ── DSH home 解析 ─────────────────────────────────────────
17
+ // 与 @deepseek-ai/dsh-home-paths 的 resolveDshHome() 一致:
18
+ // 优先级 = 显式配置 → $DSH_HOME(非空)→ ~/.dsh。
19
+ // 用户预设目录 = <dshHome>/.agent-presets(酒馆插件也用同一份)。
20
+ // 写死 ~/.dsh 会让 DSH_HOME 指向非默认位置的部署读不到预设。
21
+ function expandHomePrefix(p) {
22
+ if (p === '~') return os.homedir()
23
+ if (p.startsWith('~/') || p.startsWith('~\\')) return path.join(os.homedir(), p.slice(2))
24
+ return p
25
+ }
26
+ function resolveDshHome() {
27
+ const env = process.env.DSH_HOME
28
+ if (typeof env === 'string' && env.trim().length > 0) return path.resolve(expandHomePrefix(env.trim()))
29
+ return path.join(os.homedir(), '.dsh')
30
+ }
31
+
32
+ // apply() 时按 DSH 实际 home 重新绑定
33
+ let DSH_HOME = resolveDshHome()
34
+ let PRESETS_ROOT = path.join(DSH_HOME, '.agent-presets')
35
+ let TAVERN_PRESET_DIR = path.join(PRESETS_ROOT, 'tavern-lite')
36
+ let SESSION_BINDINGS_FILE = path.join(PRESETS_ROOT, 'session-bindings.json')
37
+
38
+ /**
39
+ * 绑定路径到 DSH 实际 home:优先用 DSH 的 dshHomePath 服务,拿不到再自行解析。
40
+ * @param {object} ctx - cordis 上下文
41
+ */
42
+ function bindDshPaths(ctx) {
43
+ let home = ''
44
+ try {
45
+ const fn = ctx && typeof ctx.get === 'function' ? ctx.get('dshHomePath') : undefined
46
+ if (typeof fn === 'function') home = String(fn() || '')
47
+ } catch {}
48
+ if (!home) home = resolveDshHome()
49
+ DSH_HOME = home
50
+ PRESETS_ROOT = path.join(home, '.agent-presets')
51
+ TAVERN_PRESET_DIR = path.join(PRESETS_ROOT, 'tavern-lite')
52
+ SESSION_BINDINGS_FILE = path.join(PRESETS_ROOT, 'session-bindings.json')
53
+ }
54
+
17
55
  const MUV_CARD_DIRS = ['E:/BaiduNetdiskDownload/EdgeDownload']
18
56
 
19
57
  /** Read a YAML/yml file with encoding detection (GBK → UTF-8 fallback). */
@@ -64,7 +102,7 @@ function scanAllPresetCards() {
64
102
  .filter(d => d.isDirectory())
65
103
  .map(d => path.join(PRESETS_ROOT, d.name))
66
104
  // Also scan the DSH root (presets like anchored-standard live there too)
67
- for (const dir of [...dirs, path.join(os.homedir(), '.dsh')]) {
105
+ for (const dir of [...dirs, DSH_HOME]) {
68
106
  const ymlPath = path.join(dir, 'agent.cordis.yml')
69
107
  if (!fs.existsSync(ymlPath)) continue
70
108
  try {
@@ -81,7 +119,7 @@ function scanAllPresetCards() {
81
119
  /** Resolve preset dir for a session id via session-bindings.json */
82
120
  function presetDirForSession(sessionId) {
83
121
  try {
84
- const bindPath = path.join(os.homedir(), '.dsh', '.agent-presets', 'session-bindings.json')
122
+ const bindPath = SESSION_BINDINGS_FILE
85
123
  if (sessionId && fs.existsSync(bindPath)) {
86
124
  const bind = JSON.parse(fs.readFileSync(bindPath, 'utf8'))
87
125
  const preset = bind[sessionId]
@@ -188,6 +226,8 @@ function readBody(req) {
188
226
  }
189
227
 
190
228
  export function apply(ctx) {
229
+ bindDshPaths(ctx)
230
+ console.log('[dsh-muv-table] DSH_HOME = ' + DSH_HOME + ' 预设目录 = ' + PRESETS_ROOT)
191
231
  const routes = [
192
232
  {
193
233
  kind: 'exact',
@@ -1,101 +1,101 @@
1
- // MUV Initvar parser: converts YAML-like indented initvar text to nested objects.
2
- // Format: 2-space indentation, `键: 值` pairs. Supports nested objects and inline
3
- // lists (comma-separated values wrapped in [ ]).
4
-
5
- /**
6
- * Parse an initvar text block into a nested object.
7
- * @param {string} text - The initvar text (between <initvar> and </initvar>)
8
- * @returns {object} Parsed nested object
9
- */
10
- export function parseInitvar(text) {
11
- const lines = text.split(/\r?\n/)
12
- const root = {}
13
- const stack = [{ obj: root, indent: -1 }]
14
-
15
- for (const rawLine of lines) {
16
- const line = rawLine.trimEnd()
17
- if (line.trim() === '') continue
18
-
19
- const indent = line.length - line.trimStart().length
20
- const trimmed = line.trim()
21
-
22
- // Pop stack until we find the right parent
23
- while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
24
- stack.pop()
25
- }
26
-
27
- const parent = stack[stack.length - 1].obj
28
- const colonIdx = trimmed.indexOf(':')
29
-
30
- if (colonIdx === -1) {
31
- // Value-only line (continuation or list item)
32
- continue
33
- }
34
-
35
- const key = trimmed.slice(0, colonIdx).trim()
36
- const value = trimmed.slice(colonIdx + 1).trim()
37
-
38
- if (value === '') {
39
- // Nested object start
40
- const newObj = {}
41
- parent[key] = newObj
42
- stack.push({ obj: newObj, indent })
43
- } else {
44
- // Leaf value
45
- parent[key] = parseValue(value)
46
- }
47
- }
48
-
49
- return root
50
- }
51
-
52
- /**
53
- * Parse a leaf value string to appropriate type.
54
- * @param {string} val
55
- * @returns {string|number|boolean}
56
- */
57
- function parseValue(val) {
58
- // Empty string
59
- if (val === '""' || val === "''") return ''
60
- if (val === '{}') return '{}'
61
-
62
- // Number
63
- if (/^-?\d+(\.\d+)?$/.test(val)) {
64
- return Number(val)
65
- }
66
-
67
- // Boolean
68
- if (val === 'true') return true
69
- if (val === 'false') return false
70
-
71
- return val
72
- }
73
-
74
- /**
75
- * Serialize a nested object back to initvar text.
76
- * @param {object} obj
77
- * @param {number} [depth=0]
78
- * @returns {string}
79
- */
80
- export function serializeInitvar(obj, depth = 0) {
81
- const indent = ' '.repeat(depth)
82
- let result = ''
83
-
84
- for (const [key, value] of Object.entries(obj)) {
85
- if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
86
- result += `${indent}${key}:\n`
87
- result += serializeInitvar(value, depth + 1)
88
- } else {
89
- result += `${indent}${key}: ${formatValue(value)}\n`
90
- }
91
- }
92
-
93
- return result
94
- }
95
-
96
- function formatValue(val) {
97
- if (val === null || val === undefined) return ''
98
- if (typeof val === 'boolean') return val ? 'true' : 'false'
99
- if (typeof val === 'number') return String(val)
100
- return String(val)
1
+ // MUV Initvar parser: converts YAML-like indented initvar text to nested objects.
2
+ // Format: 2-space indentation, `键: 值` pairs. Supports nested objects and inline
3
+ // lists (comma-separated values wrapped in [ ]).
4
+
5
+ /**
6
+ * Parse an initvar text block into a nested object.
7
+ * @param {string} text - The initvar text (between <initvar> and </initvar>)
8
+ * @returns {object} Parsed nested object
9
+ */
10
+ export function parseInitvar(text) {
11
+ const lines = text.split(/\r?\n/)
12
+ const root = {}
13
+ const stack = [{ obj: root, indent: -1 }]
14
+
15
+ for (const rawLine of lines) {
16
+ const line = rawLine.trimEnd()
17
+ if (line.trim() === '') continue
18
+
19
+ const indent = line.length - line.trimStart().length
20
+ const trimmed = line.trim()
21
+
22
+ // Pop stack until we find the right parent
23
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
24
+ stack.pop()
25
+ }
26
+
27
+ const parent = stack[stack.length - 1].obj
28
+ const colonIdx = trimmed.indexOf(':')
29
+
30
+ if (colonIdx === -1) {
31
+ // Value-only line (continuation or list item)
32
+ continue
33
+ }
34
+
35
+ const key = trimmed.slice(0, colonIdx).trim()
36
+ const value = trimmed.slice(colonIdx + 1).trim()
37
+
38
+ if (value === '') {
39
+ // Nested object start
40
+ const newObj = {}
41
+ parent[key] = newObj
42
+ stack.push({ obj: newObj, indent })
43
+ } else {
44
+ // Leaf value
45
+ parent[key] = parseValue(value)
46
+ }
47
+ }
48
+
49
+ return root
50
+ }
51
+
52
+ /**
53
+ * Parse a leaf value string to appropriate type.
54
+ * @param {string} val
55
+ * @returns {string|number|boolean}
56
+ */
57
+ function parseValue(val) {
58
+ // Empty string
59
+ if (val === '""' || val === "''") return ''
60
+ if (val === '{}') return '{}'
61
+
62
+ // Number
63
+ if (/^-?\d+(\.\d+)?$/.test(val)) {
64
+ return Number(val)
65
+ }
66
+
67
+ // Boolean
68
+ if (val === 'true') return true
69
+ if (val === 'false') return false
70
+
71
+ return val
72
+ }
73
+
74
+ /**
75
+ * Serialize a nested object back to initvar text.
76
+ * @param {object} obj
77
+ * @param {number} [depth=0]
78
+ * @returns {string}
79
+ */
80
+ export function serializeInitvar(obj, depth = 0) {
81
+ const indent = ' '.repeat(depth)
82
+ let result = ''
83
+
84
+ for (const [key, value] of Object.entries(obj)) {
85
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
86
+ result += `${indent}${key}:\n`
87
+ result += serializeInitvar(value, depth + 1)
88
+ } else {
89
+ result += `${indent}${key}: ${formatValue(value)}\n`
90
+ }
91
+ }
92
+
93
+ return result
94
+ }
95
+
96
+ function formatValue(val) {
97
+ if (val === null || val === undefined) return ''
98
+ if (typeof val === 'boolean') return val ? 'true' : 'false'
99
+ if (typeof val === 'number') return String(val)
100
+ return String(val)
101
101
  }
package/lib/muv-parser.js CHANGED
@@ -1,122 +1,122 @@
1
- // MUV Schema Parser: extracts variable structure from character card JSON.
2
- // Uses initvar data to infer the schema tree (more reliable than regex-parsing Zod).
3
-
4
- import { parseInitvar } from './initvar-parser.js'
5
-
6
- /**
7
- * Parse a MUV character card JSON and extract schema + variable data.
8
- * @param {object} cardJson - The full character card JSON
9
- * @returns {{ name: string, schemas: object[], initvarData: object, zodSource: string }}
10
- */
11
- export function parseMuvCard(cardJson) {
12
- const name = cardJson?.data?.name || cardJson?.name || 'Unnamed'
13
- const extensions = cardJson?.data?.extensions
14
- const tavernHelper = extensions?.tavern_helper
15
-
16
- // Extract Zod schema source (for reference)
17
- const zodScript = tavernHelper?.scripts?.find(s => s.name === 'zod')
18
- const zodSource = zodScript?.content || ''
19
-
20
- // Extract initvar blocks from alternate_greetings
21
- const greetings = cardJson?.data?.alternate_greetings || []
22
- const initvarBlocks = []
23
-
24
- for (const greeting of greetings) {
25
- if (typeof greeting !== 'string') continue
26
- const match = greeting.match(/<initvar>([\s\S]*?)<\/initvar>/i)
27
- if (match) {
28
- initvarBlocks.push({
29
- raw: match[1].trim(),
30
- parsed: parseInitvar(match[1])
31
- })
32
- }
33
- }
34
-
35
- // Use the first initvar block as default data
36
- const initvarData = initvarBlocks.length > 0 ? initvarBlocks[0].parsed : {}
37
-
38
- // Infer schema from initvar data
39
- const schemas = inferSchemaFromData(initvarData)
40
-
41
- return {
42
- name,
43
- schemas,
44
- initvarData,
45
- initvarBlocks: initvarBlocks.map(b => b.raw),
46
- zodSource
47
- }
48
- }
49
-
50
- /**
51
- * Infer a schema tree from a nested data object.
52
- * @param {object} data
53
- * @param {string} [name='']
54
- * @returns {object[]}
55
- */
56
- function inferSchemaFromData(data, name = '') {
57
- if (!data || typeof data !== 'object') return []
58
-
59
- const fields = []
60
- for (const [key, value] of Object.entries(data)) {
61
- if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
62
- // Nested object
63
- const children = inferSchemaFromData(value, key)
64
- if (children.length > 0) {
65
- fields.push({
66
- name: key,
67
- type: 'object',
68
- defaultValue: '{}',
69
- description: '',
70
- children
71
- })
72
- }
73
- } else if (typeof value === 'number') {
74
- fields.push({
75
- name: key,
76
- type: 'number',
77
- defaultValue: String(value),
78
- description: '',
79
- children: []
80
- })
81
- } else {
82
- fields.push({
83
- name: key,
84
- type: 'string',
85
- defaultValue: String(value ?? ''),
86
- description: '',
87
- children: []
88
- })
89
- }
90
- }
91
- return fields
92
- }
93
-
94
- /**
95
- * Walk the schema tree and extract values from data into flattened rows.
96
- * @param {object[]} schemas
97
- * @param {object} data
98
- * @param {string} [prefix='']
99
- * @returns {object[]}
100
- */
101
- export function flattenSchemaData(schemas, data, prefix = '') {
102
- const rows = []
103
- for (const field of schemas) {
104
- const path = prefix ? `${prefix}.${field.name}` : field.name
105
- const value = data?.[field.name]
106
-
107
- if (field.type === 'object' && field.children && field.children.length > 0) {
108
- rows.push({ path, key: field.name, type: 'object', value: '(object)', isContainer: true, depth: prefix.split('.').filter(Boolean).length })
109
- rows.push(...flattenSchemaData(field.children, value || {}, path))
110
- } else {
111
- rows.push({
112
- path,
113
- key: field.name,
114
- type: field.type,
115
- value: value ?? field.defaultValue ?? '',
116
- isContainer: false,
117
- depth: prefix.split('.').filter(Boolean).length
118
- })
119
- }
120
- }
121
- return rows
1
+ // MUV Schema Parser: extracts variable structure from character card JSON.
2
+ // Uses initvar data to infer the schema tree (more reliable than regex-parsing Zod).
3
+
4
+ import { parseInitvar } from './initvar-parser.js'
5
+
6
+ /**
7
+ * Parse a MUV character card JSON and extract schema + variable data.
8
+ * @param {object} cardJson - The full character card JSON
9
+ * @returns {{ name: string, schemas: object[], initvarData: object, zodSource: string }}
10
+ */
11
+ export function parseMuvCard(cardJson) {
12
+ const name = cardJson?.data?.name || cardJson?.name || 'Unnamed'
13
+ const extensions = cardJson?.data?.extensions
14
+ const tavernHelper = extensions?.tavern_helper
15
+
16
+ // Extract Zod schema source (for reference)
17
+ const zodScript = tavernHelper?.scripts?.find(s => s.name === 'zod')
18
+ const zodSource = zodScript?.content || ''
19
+
20
+ // Extract initvar blocks from alternate_greetings
21
+ const greetings = cardJson?.data?.alternate_greetings || []
22
+ const initvarBlocks = []
23
+
24
+ for (const greeting of greetings) {
25
+ if (typeof greeting !== 'string') continue
26
+ const match = greeting.match(/<initvar>([\s\S]*?)<\/initvar>/i)
27
+ if (match) {
28
+ initvarBlocks.push({
29
+ raw: match[1].trim(),
30
+ parsed: parseInitvar(match[1])
31
+ })
32
+ }
33
+ }
34
+
35
+ // Use the first initvar block as default data
36
+ const initvarData = initvarBlocks.length > 0 ? initvarBlocks[0].parsed : {}
37
+
38
+ // Infer schema from initvar data
39
+ const schemas = inferSchemaFromData(initvarData)
40
+
41
+ return {
42
+ name,
43
+ schemas,
44
+ initvarData,
45
+ initvarBlocks: initvarBlocks.map(b => b.raw),
46
+ zodSource
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Infer a schema tree from a nested data object.
52
+ * @param {object} data
53
+ * @param {string} [name='']
54
+ * @returns {object[]}
55
+ */
56
+ function inferSchemaFromData(data, name = '') {
57
+ if (!data || typeof data !== 'object') return []
58
+
59
+ const fields = []
60
+ for (const [key, value] of Object.entries(data)) {
61
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
62
+ // Nested object
63
+ const children = inferSchemaFromData(value, key)
64
+ if (children.length > 0) {
65
+ fields.push({
66
+ name: key,
67
+ type: 'object',
68
+ defaultValue: '{}',
69
+ description: '',
70
+ children
71
+ })
72
+ }
73
+ } else if (typeof value === 'number') {
74
+ fields.push({
75
+ name: key,
76
+ type: 'number',
77
+ defaultValue: String(value),
78
+ description: '',
79
+ children: []
80
+ })
81
+ } else {
82
+ fields.push({
83
+ name: key,
84
+ type: 'string',
85
+ defaultValue: String(value ?? ''),
86
+ description: '',
87
+ children: []
88
+ })
89
+ }
90
+ }
91
+ return fields
92
+ }
93
+
94
+ /**
95
+ * Walk the schema tree and extract values from data into flattened rows.
96
+ * @param {object[]} schemas
97
+ * @param {object} data
98
+ * @param {string} [prefix='']
99
+ * @returns {object[]}
100
+ */
101
+ export function flattenSchemaData(schemas, data, prefix = '') {
102
+ const rows = []
103
+ for (const field of schemas) {
104
+ const path = prefix ? `${prefix}.${field.name}` : field.name
105
+ const value = data?.[field.name]
106
+
107
+ if (field.type === 'object' && field.children && field.children.length > 0) {
108
+ rows.push({ path, key: field.name, type: 'object', value: '(object)', isContainer: true, depth: prefix.split('.').filter(Boolean).length })
109
+ rows.push(...flattenSchemaData(field.children, value || {}, path))
110
+ } else {
111
+ rows.push({
112
+ path,
113
+ key: field.name,
114
+ type: field.type,
115
+ value: value ?? field.defaultValue ?? '',
116
+ isContainer: false,
117
+ depth: prefix.split('.').filter(Boolean).length
118
+ })
119
+ }
120
+ }
121
+ return rows
122
122
  }
package/package.json CHANGED
@@ -1,21 +1,22 @@
1
- {
2
- "name": "dsh-muv-table",
3
- "version": "0.2.1",
4
- "description": "MUV 变量表格编辑器 —— 树形变量面板、宏测试浮窗(random/pick/roll)、骰子快捷栏、pick 缓存管理",
5
- "type": "module",
6
- "main": "lib/index.js",
7
- "exports": {
8
- ".": "./lib/index.js",
9
- "./client": "./lib/client.js",
10
- "./package.json": "./package.json"
11
- },
12
- "files": ["lib", "cordis.patch.yml", "CHANGELOG.md"],
13
- "keywords": ["dsh", "dsh-plugin", "deepseek-harness", "muv", "magvarupdate", "tavern", "variable-table"],
14
- "license": "PolyForm-Noncommercial-Copyleft-1.0.0",
15
- "dsh": {
16
- "bundle": { "patch": "./cordis.patch.yml" },
17
- "client": { "platform": "web" }
18
- },
19
- "repository": {"type": "git", "url": "git+https://github.com/chen731215-dev/dsh-muv-table.git"},
20
- "engines": { "node": ">=20" }
1
+ {
2
+ "name": "dsh-muv-table",
3
+ "version": "0.2.3",
4
+ "description": "MUV 变量表格编辑器 —— 树形变量面板、宏测试浮窗(random/pick/roll)、骰子快捷栏、pick 缓存管理",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "exports": {
8
+ ".": "./lib/index.js",
9
+ "./client": "./lib/client.js",
10
+ "./lib/*": "./lib/*",
11
+ "./package.json": "./package.json"
12
+ },
13
+ "files": ["lib", "cordis.patch.yml", "CHANGELOG.md"],
14
+ "keywords": ["dsh", "dsh-plugin", "deepseek-harness", "muv", "magvarupdate", "tavern", "variable-table"],
15
+ "license": "PolyForm-Noncommercial-Copyleft-1.0.0",
16
+ "dsh": {
17
+ "bundle": { "patch": "./cordis.patch.yml" },
18
+ "client": { "platform": "web" }
19
+ },
20
+ "repository": {"type": "git", "url": "git+https://github.com/chen731215-dev/dsh-muv-table.git"},
21
+ "engines": { "node": ">=20" }
21
22
  }