run-dmcp 0.1.0

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 (167) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +79 -0
  3. package/dist/__tests__/engineVocabulary.test.d.ts +1 -0
  4. package/dist/__tests__/engineVocabulary.test.js +147 -0
  5. package/dist/db/__tests__/connection.test.d.ts +1 -0
  6. package/dist/db/__tests__/connection.test.js +72 -0
  7. package/dist/db/__tests__/testDb.d.ts +33 -0
  8. package/dist/db/__tests__/testDb.js +41 -0
  9. package/dist/db/connection.d.ts +22 -0
  10. package/dist/db/connection.js +107 -0
  11. package/dist/db/schema.d.ts +1 -0
  12. package/dist/db/schema.js +725 -0
  13. package/dist/events/emitter.d.ts +22 -0
  14. package/dist/events/emitter.js +71 -0
  15. package/dist/http/server.d.ts +3 -0
  16. package/dist/http/server.js +649 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +92 -0
  19. package/dist/register/abilities.d.ts +2 -0
  20. package/dist/register/abilities.js +165 -0
  21. package/dist/register/audio.d.ts +2 -0
  22. package/dist/register/audio.js +326 -0
  23. package/dist/register/batch.d.ts +2 -0
  24. package/dist/register/batch.js +343 -0
  25. package/dist/register/character.d.ts +2 -0
  26. package/dist/register/character.js +324 -0
  27. package/dist/register/combat.d.ts +2 -0
  28. package/dist/register/combat.js +207 -0
  29. package/dist/register/core.d.ts +2 -0
  30. package/dist/register/core.js +1040 -0
  31. package/dist/register/display.d.ts +2 -0
  32. package/dist/register/display.js +263 -0
  33. package/dist/register/factions.d.ts +2 -0
  34. package/dist/register/factions.js +186 -0
  35. package/dist/register/images.d.ts +2 -0
  36. package/dist/register/images.js +400 -0
  37. package/dist/register/inventory.d.ts +2 -0
  38. package/dist/register/inventory.js +115 -0
  39. package/dist/register/mcp-prompts.d.ts +2 -0
  40. package/dist/register/mcp-prompts.js +684 -0
  41. package/dist/register/mcp-resources.d.ts +2 -0
  42. package/dist/register/mcp-resources.js +335 -0
  43. package/dist/register/narrative.d.ts +2 -0
  44. package/dist/register/narrative.js +242 -0
  45. package/dist/register/notes.d.ts +2 -0
  46. package/dist/register/notes.js +170 -0
  47. package/dist/register/pause.d.ts +2 -0
  48. package/dist/register/pause.js +580 -0
  49. package/dist/register/quests.d.ts +2 -0
  50. package/dist/register/quests.js +118 -0
  51. package/dist/register/relationships.d.ts +2 -0
  52. package/dist/register/relationships.js +147 -0
  53. package/dist/register/resources.d.ts +2 -0
  54. package/dist/register/resources.js +277 -0
  55. package/dist/register/secrets.d.ts +2 -0
  56. package/dist/register/secrets.js +192 -0
  57. package/dist/register/status.d.ts +2 -0
  58. package/dist/register/status.js +130 -0
  59. package/dist/register/tables.d.ts +2 -0
  60. package/dist/register/tables.js +146 -0
  61. package/dist/register/tags.d.ts +2 -0
  62. package/dist/register/tags.js +114 -0
  63. package/dist/register/time.d.ts +2 -0
  64. package/dist/register/time.js +281 -0
  65. package/dist/register/world.d.ts +2 -0
  66. package/dist/register/world.js +127 -0
  67. package/dist/schemas/index.d.ts +921 -0
  68. package/dist/schemas/index.js +121 -0
  69. package/dist/test-setup.d.ts +1 -0
  70. package/dist/test-setup.js +13 -0
  71. package/dist/tools/__tests__/audio.test.d.ts +1 -0
  72. package/dist/tools/__tests__/audio.test.js +59 -0
  73. package/dist/tools/__tests__/conserved.test.d.ts +1 -0
  74. package/dist/tools/__tests__/conserved.test.js +488 -0
  75. package/dist/tools/__tests__/constraint.test.d.ts +1 -0
  76. package/dist/tools/__tests__/constraint.test.js +212 -0
  77. package/dist/tools/__tests__/expiry-consequences.test.d.ts +1 -0
  78. package/dist/tools/__tests__/expiry-consequences.test.js +110 -0
  79. package/dist/tools/__tests__/images.test.d.ts +1 -0
  80. package/dist/tools/__tests__/images.test.js +59 -0
  81. package/dist/tools/__tests__/relationship.test.d.ts +1 -0
  82. package/dist/tools/__tests__/relationship.test.js +132 -0
  83. package/dist/tools/__tests__/resource-constraints.test.d.ts +1 -0
  84. package/dist/tools/__tests__/resource-constraints.test.js +131 -0
  85. package/dist/tools/__tests__/resource.test.d.ts +1 -0
  86. package/dist/tools/__tests__/resource.test.js +190 -0
  87. package/dist/tools/__tests__/time.test.d.ts +1 -0
  88. package/dist/tools/__tests__/time.test.js +404 -0
  89. package/dist/tools/__tests__/timers.test.d.ts +1 -0
  90. package/dist/tools/__tests__/timers.test.js +426 -0
  91. package/dist/tools/__tests__/world.test.d.ts +1 -0
  92. package/dist/tools/__tests__/world.test.js +70 -0
  93. package/dist/tools/ability.d.ts +48 -0
  94. package/dist/tools/ability.js +238 -0
  95. package/dist/tools/audio.d.ts +24 -0
  96. package/dist/tools/audio.js +365 -0
  97. package/dist/tools/character.d.ts +70 -0
  98. package/dist/tools/character.js +309 -0
  99. package/dist/tools/combat.d.ts +13 -0
  100. package/dist/tools/combat.js +195 -0
  101. package/dist/tools/constraint.d.ts +132 -0
  102. package/dist/tools/constraint.js +269 -0
  103. package/dist/tools/dice.d.ts +23 -0
  104. package/dist/tools/dice.js +111 -0
  105. package/dist/tools/display.d.ts +120 -0
  106. package/dist/tools/display.js +528 -0
  107. package/dist/tools/faction.d.ts +61 -0
  108. package/dist/tools/faction.js +269 -0
  109. package/dist/tools/game.d.ts +96 -0
  110. package/dist/tools/game.js +526 -0
  111. package/dist/tools/image-prompt.d.ts +49 -0
  112. package/dist/tools/image-prompt.js +479 -0
  113. package/dist/tools/images.d.ts +47 -0
  114. package/dist/tools/images.js +449 -0
  115. package/dist/tools/inventory.d.ts +20 -0
  116. package/dist/tools/inventory.js +145 -0
  117. package/dist/tools/narrative.d.ts +58 -0
  118. package/dist/tools/narrative.js +237 -0
  119. package/dist/tools/notes.d.ts +41 -0
  120. package/dist/tools/notes.js +220 -0
  121. package/dist/tools/pause.d.ts +110 -0
  122. package/dist/tools/pause.js +1254 -0
  123. package/dist/tools/quest.d.ts +34 -0
  124. package/dist/tools/quest.js +164 -0
  125. package/dist/tools/relationship.d.ts +74 -0
  126. package/dist/tools/relationship.js +324 -0
  127. package/dist/tools/resource.d.ts +93 -0
  128. package/dist/tools/resource.js +374 -0
  129. package/dist/tools/rules.d.ts +4 -0
  130. package/dist/tools/rules.js +30 -0
  131. package/dist/tools/secrets.d.ts +49 -0
  132. package/dist/tools/secrets.js +195 -0
  133. package/dist/tools/status.d.ts +36 -0
  134. package/dist/tools/status.js +218 -0
  135. package/dist/tools/tables.d.ts +33 -0
  136. package/dist/tools/tables.js +209 -0
  137. package/dist/tools/tags.d.ts +52 -0
  138. package/dist/tools/tags.js +176 -0
  139. package/dist/tools/time.d.ts +33 -0
  140. package/dist/tools/time.js +276 -0
  141. package/dist/tools/timers.d.ts +41 -0
  142. package/dist/tools/timers.js +215 -0
  143. package/dist/tools/world.d.ts +78 -0
  144. package/dist/tools/world.js +331 -0
  145. package/dist/types/index.d.ts +969 -0
  146. package/dist/types/index.js +1 -0
  147. package/dist/utils/__tests__/json.test.d.ts +1 -0
  148. package/dist/utils/__tests__/json.test.js +55 -0
  149. package/dist/utils/__tests__/validation.test.d.ts +1 -0
  150. package/dist/utils/__tests__/validation.test.js +90 -0
  151. package/dist/utils/errors.d.ts +44 -0
  152. package/dist/utils/errors.js +121 -0
  153. package/dist/utils/json.d.ts +9 -0
  154. package/dist/utils/json.js +23 -0
  155. package/dist/utils/logger.d.ts +7 -0
  156. package/dist/utils/logger.js +50 -0
  157. package/dist/utils/output-schemas.d.ts +594 -0
  158. package/dist/utils/output-schemas.js +331 -0
  159. package/dist/utils/tool-annotations.d.ts +147 -0
  160. package/dist/utils/tool-annotations.js +98 -0
  161. package/dist/utils/validation.d.ts +34 -0
  162. package/dist/utils/validation.js +52 -0
  163. package/dist/utils/verbosity.d.ts +57 -0
  164. package/dist/utils/verbosity.js +67 -0
  165. package/dist/utils/webui.d.ts +20 -0
  166. package/dist/utils/webui.js +35 -0
  167. package/package.json +75 -0
@@ -0,0 +1,479 @@
1
+ import { getCharacter, listCharacters } from "./character.js";
2
+ import { getLocation } from "./world.js";
3
+ import { getItem } from "./inventory.js";
4
+ import { getFaction } from "./faction.js";
5
+ import { getDefaultImagePreset, getDefaultImagePromptTemplate, getImagePromptTemplate } from "./game.js";
6
+ import { listEntityImages } from "./images.js";
7
+ // ============================================================================
8
+ // Template Processing
9
+ // ============================================================================
10
+ /**
11
+ * Get a nested value from an object using dot notation path.
12
+ * E.g., getNestedValue(obj, "subject.physicalTraits.gender")
13
+ */
14
+ function getNestedValue(obj, path) {
15
+ const parts = path.split(".");
16
+ let current = obj;
17
+ for (const part of parts) {
18
+ if (current === null || current === undefined)
19
+ return undefined;
20
+ if (typeof current !== "object")
21
+ return undefined;
22
+ current = current[part];
23
+ }
24
+ return current;
25
+ }
26
+ /**
27
+ * Fill a template string with values from data object.
28
+ * Supports:
29
+ * - {{field.path}} - simple value substitution
30
+ * - {{field.path|"default"}} - value with default if undefined/null
31
+ * - {{field.path|}} - value or empty string if undefined
32
+ */
33
+ export function fillTemplate(template, data, defaults, aliases) {
34
+ // Pattern matches: {{path}} or {{path|"default"}} or {{path|}}
35
+ const pattern = /\{\{([^}|]+)(?:\|(?:"([^"]*)"|([^}]*)))?\}\}/g;
36
+ return template.replace(pattern, (match, path, quotedDefault, bareDefault) => {
37
+ const trimmedPath = path.trim();
38
+ // Check aliases first
39
+ const resolvedPath = aliases?.[trimmedPath] || trimmedPath;
40
+ // Get value from data
41
+ let value = getNestedValue(data, resolvedPath);
42
+ // Handle arrays by joining
43
+ if (Array.isArray(value)) {
44
+ value = value.join(", ");
45
+ }
46
+ // If value exists and is not empty, return it
47
+ if (value !== undefined && value !== null && value !== "") {
48
+ return String(value);
49
+ }
50
+ // Check template defaults
51
+ if (defaults?.[trimmedPath] !== undefined) {
52
+ return defaults[trimmedPath];
53
+ }
54
+ // Use inline default if provided
55
+ if (quotedDefault !== undefined) {
56
+ return quotedDefault;
57
+ }
58
+ if (bareDefault !== undefined) {
59
+ return bareDefault.trim();
60
+ }
61
+ // No value, no default - return empty string
62
+ return "";
63
+ });
64
+ }
65
+ /**
66
+ * Clean up a filled template by removing extra commas, spaces, etc.
67
+ */
68
+ function cleanFilledTemplate(text) {
69
+ return text
70
+ .replace(/,\s*,/g, ",") // Remove double commas
71
+ .replace(/,\s*$/g, "") // Remove trailing comma
72
+ .replace(/^\s*,/g, "") // Remove leading comma
73
+ .replace(/\s+/g, " ") // Normalize spaces
74
+ .replace(/\s+,/g, ",") // Remove space before comma
75
+ .trim();
76
+ }
77
+ /**
78
+ * Build data context for template filling from entity and imageGen.
79
+ */
80
+ function buildTemplateContext(entity, entityType, imageGen, notes) {
81
+ // Start with imageGen data (the primary source)
82
+ const context = {
83
+ entity: entity,
84
+ entityType,
85
+ name: entity.name,
86
+ notes,
87
+ ...(imageGen || {}),
88
+ };
89
+ // Add convenience aliases at top level for common fields
90
+ if (imageGen?.subject) {
91
+ context.subject = imageGen.subject;
92
+ if (imageGen.subject.physicalTraits) {
93
+ context.physicalTraits = imageGen.subject.physicalTraits;
94
+ }
95
+ if (imageGen.subject.attire) {
96
+ context.attire = imageGen.subject.attire;
97
+ }
98
+ if (imageGen.subject.environment) {
99
+ context.environment = imageGen.subject.environment;
100
+ }
101
+ if (imageGen.subject.objectDetails) {
102
+ context.objectDetails = imageGen.subject.objectDetails;
103
+ }
104
+ }
105
+ if (imageGen?.style) {
106
+ context.style = imageGen.style;
107
+ }
108
+ if (imageGen?.composition) {
109
+ context.composition = imageGen.composition;
110
+ }
111
+ return context;
112
+ }
113
+ /**
114
+ * Build a prompt using a template.
115
+ */
116
+ export function buildPromptFromTemplate(template, entity, entityType, imageGen, notes) {
117
+ const context = buildTemplateContext(entity, entityType, imageGen, notes);
118
+ // Fill the main template
119
+ let prompt = fillTemplate(template.promptTemplate, context, template.defaults, template.fieldAliases);
120
+ // Clean up the filled template
121
+ prompt = cleanFilledTemplate(prompt);
122
+ // Add prefix and suffix
123
+ if (template.promptPrefix) {
124
+ prompt = template.promptPrefix + " " + prompt;
125
+ }
126
+ if (template.promptSuffix) {
127
+ prompt = prompt + ", " + template.promptSuffix;
128
+ }
129
+ // Build negative prompt
130
+ let negativePrompt = "";
131
+ if (template.negativePromptTemplate) {
132
+ negativePrompt = fillTemplate(template.negativePromptTemplate, context, template.defaults, template.fieldAliases);
133
+ negativePrompt = cleanFilledTemplate(negativePrompt);
134
+ }
135
+ return {
136
+ prompt: cleanFilledTemplate(prompt),
137
+ negativePrompt: negativePrompt || "low quality, blurry, distorted, deformed",
138
+ };
139
+ }
140
+ /**
141
+ * Build an image generation prompt from an entity's structured data.
142
+ * If a template exists for the entity type, uses that. Otherwise uses default logic.
143
+ * Combines imageGen schema, notes, and game preset to create a ready-to-use prompt.
144
+ */
145
+ export function buildImagePrompt(entityId, entityType, gameId, templateId) {
146
+ // Get the entity
147
+ let entity = null;
148
+ let entityName = "";
149
+ let imageGen = null;
150
+ let notes = "";
151
+ switch (entityType) {
152
+ case "character": {
153
+ const char = getCharacter(entityId);
154
+ if (!char)
155
+ return null;
156
+ entity = char;
157
+ entityName = char.name;
158
+ imageGen = char.imageGen || null;
159
+ notes = char.notes || "";
160
+ break;
161
+ }
162
+ case "location": {
163
+ const loc = getLocation(entityId);
164
+ if (!loc)
165
+ return null;
166
+ entity = loc;
167
+ entityName = loc.name;
168
+ imageGen = loc.imageGen || null;
169
+ notes = loc.description || "";
170
+ break;
171
+ }
172
+ case "item": {
173
+ const item = getItem(entityId);
174
+ if (!item)
175
+ return null;
176
+ entity = item;
177
+ entityName = item.name;
178
+ imageGen = item.imageGen || null;
179
+ notes = item.properties?.description || "";
180
+ break;
181
+ }
182
+ case "faction": {
183
+ const faction = getFaction(entityId);
184
+ if (!faction)
185
+ return null;
186
+ entity = faction;
187
+ entityName = faction.name;
188
+ imageGen = faction.imageGen || null;
189
+ notes = faction.description || "";
190
+ break;
191
+ }
192
+ }
193
+ if (!entity)
194
+ return null;
195
+ // Get game ID for template/preset lookup
196
+ const effectiveGameId = gameId || entity.gameId;
197
+ // Check for a template to use
198
+ let template = null;
199
+ if (effectiveGameId) {
200
+ if (templateId) {
201
+ // Use specific template if provided
202
+ template = getImagePromptTemplate(effectiveGameId, templateId);
203
+ }
204
+ else {
205
+ // Try to find default template for this entity type
206
+ template = getDefaultImagePromptTemplate(effectiveGameId, entityType);
207
+ }
208
+ }
209
+ // If we have a template, use template-based building
210
+ if (template) {
211
+ const { prompt, negativePrompt } = buildPromptFromTemplate(template, entity, entityType, imageGen, notes);
212
+ // Build summary
213
+ const summaryParts = [
214
+ `Type: ${entityType}`,
215
+ `Name: ${entityName}`,
216
+ `Template: ${template.name}`,
217
+ ];
218
+ if (imageGen?.subject?.physicalTraits?.gender) {
219
+ summaryParts.push(`Gender: ${imageGen.subject.physicalTraits.gender}`);
220
+ }
221
+ if (imageGen?.subject?.physicalTraits?.age) {
222
+ summaryParts.push(`Age: ${imageGen.subject.physicalTraits.age}`);
223
+ }
224
+ return {
225
+ entityId,
226
+ entityType,
227
+ entityName,
228
+ prompt,
229
+ negativePrompt,
230
+ summary: summaryParts.join("\n"),
231
+ source: {
232
+ fromImageGen: !!imageGen,
233
+ fromNotes: !!notes && !imageGen,
234
+ fromPreset: false,
235
+ fromTemplate: true,
236
+ },
237
+ templateUsed: template.name,
238
+ };
239
+ }
240
+ // No template - use default logic
241
+ // Get game preset for style defaults
242
+ let preset = null;
243
+ if (effectiveGameId) {
244
+ preset = getDefaultImagePreset(effectiveGameId);
245
+ }
246
+ // Build the prompt
247
+ const promptParts = [];
248
+ const negativeParts = [];
249
+ const summaryParts = [];
250
+ const source = { fromImageGen: false, fromNotes: false, fromPreset: false, fromTemplate: false };
251
+ // 1. Start with imageGen structured data if available
252
+ if (imageGen) {
253
+ source.fromImageGen = true;
254
+ // Subject description
255
+ if (imageGen.subject) {
256
+ const subj = imageGen.subject;
257
+ // Primary description
258
+ if (subj.primaryDescription) {
259
+ promptParts.push(subj.primaryDescription);
260
+ }
261
+ // Physical traits (for characters)
262
+ if (subj.physicalTraits) {
263
+ const traits = subj.physicalTraits;
264
+ const traitParts = [];
265
+ if (traits.gender)
266
+ traitParts.push(traits.gender);
267
+ if (traits.age)
268
+ traitParts.push(traits.age);
269
+ if (traits.bodyType)
270
+ traitParts.push(traits.bodyType);
271
+ if (traits.skinTone)
272
+ traitParts.push(`${traits.skinTone} skin`);
273
+ if (traits.hairColor && traits.hairStyle) {
274
+ traitParts.push(`${traits.hairColor} ${traits.hairStyle} hair`);
275
+ }
276
+ else if (traits.hairColor) {
277
+ traitParts.push(`${traits.hairColor} hair`);
278
+ }
279
+ else if (traits.hairStyle) {
280
+ traitParts.push(`${traits.hairStyle} hair`);
281
+ }
282
+ if (traits.eyeColor)
283
+ traitParts.push(`${traits.eyeColor} eyes`);
284
+ if (traits.facialFeatures)
285
+ traitParts.push(traits.facialFeatures);
286
+ if (traits.distinguishingMarks?.length) {
287
+ traitParts.push(traits.distinguishingMarks.join(", "));
288
+ }
289
+ if (traitParts.length > 0) {
290
+ promptParts.push(traitParts.join(", "));
291
+ summaryParts.push(`Physical: ${traitParts.join(", ")}`);
292
+ }
293
+ }
294
+ // Attire
295
+ if (subj.attire) {
296
+ const attireParts = [];
297
+ if (subj.attire.description)
298
+ attireParts.push(subj.attire.description);
299
+ if (subj.attire.colors?.length)
300
+ attireParts.push(subj.attire.colors.join(" and ") + " colors");
301
+ if (subj.attire.materials?.length)
302
+ attireParts.push(subj.attire.materials.join(" and "));
303
+ if (subj.attire.accessories?.length)
304
+ attireParts.push(`wearing ${subj.attire.accessories.join(", ")}`);
305
+ if (attireParts.length > 0) {
306
+ promptParts.push(attireParts.join(", "));
307
+ }
308
+ }
309
+ // Environment (for locations)
310
+ if (subj.environment) {
311
+ const envParts = [];
312
+ if (subj.environment.setting)
313
+ envParts.push(subj.environment.setting);
314
+ if (subj.environment.timeOfDay)
315
+ envParts.push(subj.environment.timeOfDay);
316
+ if (subj.environment.weather)
317
+ envParts.push(subj.environment.weather);
318
+ if (subj.environment.lighting)
319
+ envParts.push(subj.environment.lighting);
320
+ if (subj.environment.architecture)
321
+ envParts.push(subj.environment.architecture);
322
+ if (subj.environment.notableFeatures?.length) {
323
+ envParts.push(subj.environment.notableFeatures.join(", "));
324
+ }
325
+ if (envParts.length > 0) {
326
+ promptParts.push(envParts.join(", "));
327
+ }
328
+ }
329
+ // Object details (for items)
330
+ if (subj.objectDetails) {
331
+ const objParts = [];
332
+ if (subj.objectDetails.material)
333
+ objParts.push(subj.objectDetails.material);
334
+ if (subj.objectDetails.size)
335
+ objParts.push(subj.objectDetails.size);
336
+ if (subj.objectDetails.condition)
337
+ objParts.push(subj.objectDetails.condition);
338
+ if (subj.objectDetails.glowOrEffects)
339
+ objParts.push(subj.objectDetails.glowOrEffects);
340
+ if (objParts.length > 0) {
341
+ promptParts.push(objParts.join(", "));
342
+ }
343
+ }
344
+ // Pose/expression (for characters)
345
+ if (subj.pose)
346
+ promptParts.push(subj.pose);
347
+ if (subj.expression)
348
+ promptParts.push(subj.expression);
349
+ if (subj.action)
350
+ promptParts.push(subj.action);
351
+ }
352
+ // Style
353
+ if (imageGen.style) {
354
+ const style = imageGen.style;
355
+ if (style.artisticStyle)
356
+ promptParts.push(style.artisticStyle);
357
+ if (style.mood)
358
+ promptParts.push(`${style.mood} mood`);
359
+ if (style.colorScheme)
360
+ promptParts.push(`${style.colorScheme} colors`);
361
+ if (style.qualityTags?.length)
362
+ promptParts.push(style.qualityTags.join(", "));
363
+ if (style.influences?.length)
364
+ promptParts.push(`in the style of ${style.influences.join(", ")}`);
365
+ if (style.negativeElements?.length)
366
+ negativeParts.push(...style.negativeElements);
367
+ }
368
+ // Composition
369
+ if (imageGen.composition) {
370
+ const comp = imageGen.composition;
371
+ if (comp.framing)
372
+ promptParts.push(comp.framing);
373
+ if (comp.cameraAngle)
374
+ promptParts.push(comp.cameraAngle);
375
+ if (comp.background)
376
+ promptParts.push(`${comp.background} background`);
377
+ if (comp.depth)
378
+ promptParts.push(comp.depth);
379
+ }
380
+ // Use cached prompts if available
381
+ if (imageGen.prompts?.generic && promptParts.length === 0) {
382
+ promptParts.push(imageGen.prompts.generic);
383
+ }
384
+ }
385
+ // 2. Fall back to notes if no imageGen or very sparse
386
+ if (promptParts.length < 3 && notes) {
387
+ source.fromNotes = true;
388
+ // Clean and truncate notes for use as prompt
389
+ const cleanNotes = notes
390
+ .replace(/\n/g, " ")
391
+ .replace(/\s+/g, " ")
392
+ .trim()
393
+ .slice(0, 500);
394
+ if (cleanNotes) {
395
+ promptParts.push(cleanNotes);
396
+ }
397
+ // Warn that we're using notes which may lack visual specificity
398
+ summaryParts.push("⚠️ WARNING: Using notes/description as prompt source");
399
+ summaryParts.push("Notes may contain biographical info rather than visual description.");
400
+ summaryParts.push("Consider populating imageGen schema or writing a custom prompt.");
401
+ }
402
+ // 3. Apply preset style defaults
403
+ if (preset?.config?.defaultStyle) {
404
+ source.fromPreset = true;
405
+ const style = preset.config.defaultStyle;
406
+ const artisticStyle = style.artisticStyle;
407
+ const mood = style.mood;
408
+ if (artisticStyle && !promptParts.some(p => p.includes(artisticStyle))) {
409
+ promptParts.push(artisticStyle);
410
+ }
411
+ if (mood && !promptParts.some(p => p.includes(mood))) {
412
+ promptParts.push(`${mood} mood`);
413
+ }
414
+ if (style.qualityTags?.length) {
415
+ const newTags = style.qualityTags.filter(t => !promptParts.some(p => p.includes(t)));
416
+ if (newTags.length)
417
+ promptParts.push(newTags.join(", "));
418
+ }
419
+ if (style.negativePrompts?.length) {
420
+ negativeParts.push(...style.negativePrompts.filter(n => !negativeParts.includes(n)));
421
+ }
422
+ }
423
+ // Build summary for verification
424
+ summaryParts.unshift(`Name: ${entityName}`);
425
+ summaryParts.unshift(`Type: ${entityType}`);
426
+ if (imageGen?.subject?.physicalTraits?.gender) {
427
+ summaryParts.push(`Gender: ${imageGen.subject.physicalTraits.gender}`);
428
+ }
429
+ if (imageGen?.subject?.physicalTraits?.age) {
430
+ summaryParts.push(`Age: ${imageGen.subject.physicalTraits.age}`);
431
+ }
432
+ // Default negative prompt if none specified
433
+ if (negativeParts.length === 0) {
434
+ negativeParts.push("low quality", "blurry", "distorted", "deformed");
435
+ }
436
+ return {
437
+ entityId,
438
+ entityType,
439
+ entityName,
440
+ prompt: promptParts.join(", "),
441
+ negativePrompt: negativeParts.join(", "),
442
+ summary: summaryParts.join("\n"),
443
+ source,
444
+ };
445
+ }
446
+ export function listCharacterSummaries(gameId) {
447
+ const characters = listCharacters(gameId);
448
+ return characters.map(char => {
449
+ // Build summary from imageGen or notes
450
+ const summaryParts = [];
451
+ if (char.imageGen?.subject?.physicalTraits) {
452
+ const traits = char.imageGen.subject.physicalTraits;
453
+ if (traits.gender)
454
+ summaryParts.push(traits.gender);
455
+ if (traits.age)
456
+ summaryParts.push(traits.age);
457
+ if (traits.bodyType)
458
+ summaryParts.push(traits.bodyType);
459
+ }
460
+ // Extract role/occupation from notes if available
461
+ if (char.notes) {
462
+ // Look for common role patterns
463
+ const roleMatch = char.notes.match(/(?:is a|works as|serves as|the) ([a-z]+ ?[a-z]*)/i);
464
+ if (roleMatch && roleMatch[1]) {
465
+ summaryParts.push(roleMatch[1].trim());
466
+ }
467
+ }
468
+ // Check if has images
469
+ const images = listEntityImages(char.id, "character");
470
+ return {
471
+ id: char.id,
472
+ name: char.name,
473
+ isPlayer: char.isPlayer,
474
+ summary: summaryParts.length > 0 ? summaryParts.join(", ") : char.isPlayer ? "Player Character" : "NPC",
475
+ hasImageGen: !!char.imageGen,
476
+ hasImages: images.images.length > 0,
477
+ };
478
+ });
479
+ }
@@ -0,0 +1,47 @@
1
+ import type { StoredImage, StoreImageParams, ImageListResult } from "../types/index.js";
2
+ export declare function storeImage(params: StoreImageParams): Promise<StoredImage>;
3
+ export declare function getImage(imageId: string): StoredImage | null;
4
+ export interface GetImageDataOptions {
5
+ format?: "jpeg" | "webp" | "png";
6
+ width?: number;
7
+ height?: number;
8
+ quality?: number;
9
+ fit?: "cover" | "contain" | "fill" | "inside" | "outside";
10
+ }
11
+ export declare function getImageData(imageId: string, options?: GetImageDataOptions): Promise<{
12
+ image: StoredImage;
13
+ base64: string;
14
+ outputFormat: string;
15
+ outputSize: number;
16
+ } | null>;
17
+ export declare function listEntityImages(entityId: string, entityType: string): ImageListResult;
18
+ export interface EntityMissingImage {
19
+ id: string;
20
+ name: string;
21
+ entityType: string;
22
+ hasImageGen: boolean;
23
+ isPlayer?: boolean;
24
+ }
25
+ export interface EntitiesMissingImagesResult {
26
+ gameId: string;
27
+ entities: EntityMissingImage[];
28
+ totalMissing: number;
29
+ byType: {
30
+ characters: number;
31
+ locations: number;
32
+ items: number;
33
+ factions: number;
34
+ };
35
+ }
36
+ export declare function listEntitiesMissingImages(gameId: string, entityType?: string): EntitiesMissingImagesResult;
37
+ export declare function deleteImage(imageId: string): boolean;
38
+ export declare function setPrimaryImage(imageId: string): StoredImage | null;
39
+ export declare function updateImageMetadata(imageId: string, updates: {
40
+ label?: string;
41
+ description?: string;
42
+ entityId?: string;
43
+ entityType?: string;
44
+ }): StoredImage | null;
45
+ export declare function deleteGameImages(gameId: string): number;
46
+ export declare function getPrimaryImage(entityId: string, entityType: string): StoredImage | null;
47
+ export declare function listGameImages(gameId: string): StoredImage[];