dsh-muv-table 0.2.1 → 0.2.2

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.
@@ -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.2",
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
  }