@thijshakkenberg/valkyrie-mom-mcp 2.1.0 → 2.2.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +24 -4
- package/agents/scenario-designer.md +7 -2
- package/dist/artwork/comfyui.d.ts +146 -0
- package/dist/artwork/comfyui.d.ts.map +1 -0
- package/dist/artwork/comfyui.js +214 -0
- package/dist/artwork/comfyui.js.map +1 -0
- package/dist/io/package-manifest.d.ts +18 -0
- package/dist/io/package-manifest.d.ts.map +1 -0
- package/dist/io/package-manifest.js +57 -0
- package/dist/io/package-manifest.js.map +1 -0
- package/dist/resources/format-docs.d.ts +1 -1
- package/dist/resources/format-docs.d.ts.map +1 -1
- package/dist/resources/format-docs.js +17 -9
- package/dist/resources/format-docs.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +68 -7
- package/dist/server.js.map +1 -1
- package/dist/validation/rules/custom-images.d.ts.map +1 -1
- package/dist/validation/rules/custom-images.js +24 -2
- package/dist/validation/rules/custom-images.js.map +1 -1
- package/dist/validation/rules/package-name.d.ts +9 -0
- package/dist/validation/rules/package-name.d.ts.map +1 -0
- package/dist/validation/rules/package-name.js +32 -0
- package/dist/validation/rules/package-name.js.map +1 -0
- package/dist/validation/rules/puzzles.d.ts +15 -0
- package/dist/validation/rules/puzzles.d.ts.map +1 -0
- package/dist/validation/rules/puzzles.js +76 -0
- package/dist/validation/rules/puzzles.js.map +1 -0
- package/dist/validation/validator.d.ts.map +1 -1
- package/dist/validation/validator.js +4 -0
- package/dist/validation/validator.js.map +1 -1
- package/package.json +1 -1
- package/skills/artwork/SKILL.md +120 -0
- package/skills/scenario/SKILL.md +4 -1
- package/skills/ui-and-puzzles/SKILL.md +50 -133
- package/src/artwork/comfyui.ts +289 -0
- package/src/io/package-manifest.ts +59 -0
- package/src/resources/format-docs.ts +17 -9
- package/src/server.ts +85 -7
- package/src/validation/rules/custom-images.ts +23 -2
- package/src/validation/rules/package-name.ts +33 -0
- package/src/validation/rules/puzzles.ts +79 -0
- package/src/validation/validator.ts +4 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ MCP (Model Context Protocol) server and Claude Code plugin for AI-assisted Mansi
|
|
|
8
8
|
|
|
9
9
|
This server exposes Valkyrie scenario editing as MCP tools, enabling AI assistants (Claude, etc.) to create, modify, validate, and build complete MoM scenarios. It auto-detects the Valkyrie editor directory so scenarios appear directly in the app.
|
|
10
10
|
|
|
11
|
-
The plugin bundles
|
|
11
|
+
The plugin bundles 8 skills covering advanced patterns (event loops, mythos scaling, tile placement chains, custom puzzles, generated artwork, etc.) and an autonomous scenario designer agent.
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@ claude plugin install valkyrie-mom
|
|
|
21
21
|
|
|
22
22
|
This gives you:
|
|
23
23
|
- **MCP server** with 25 tools for scenario editing (auto-started)
|
|
24
|
-
- **
|
|
24
|
+
- **8 skills**: `/scenario`, `/event-patterns`, `/tile-placement`, `/variables-and-mythos`, `/custom-monsters`, `/ui-and-puzzles`, `/items-and-distribution`, `/artwork`
|
|
25
25
|
- **Scenario designer agent** for autonomous scenario creation
|
|
26
26
|
- **5 MCP resources** for format documentation
|
|
27
27
|
|
|
@@ -73,6 +73,7 @@ npx tsx src/index.ts # Run MCP server via stdio
|
|
|
73
73
|
| `/custom-monsters` | Custom activations, evade/horror events, round-based spawn triggering |
|
|
74
74
|
| `/ui-and-puzzles` | Prologues, interactive journals, combination locks, built-in puzzle types |
|
|
75
75
|
| `/items-and-distribution` | Random items, unique items, starting items, inspection events |
|
|
76
|
+
| `/artwork` | Generate pictures with a local ComfyUI (FLUX.2 [klein] 4B) and show them: cover, intro cutscene, handouts, scenes, monsters, tokens, image puzzles |
|
|
76
77
|
|
|
77
78
|
## MCP Tools
|
|
78
79
|
|
|
@@ -84,7 +85,8 @@ npx tsx src/index.ts # Run MCP server via stdio
|
|
|
84
85
|
| `load_scenario` | Load an existing scenario from a directory |
|
|
85
86
|
| `get_scenario_state` | Get current scenario component/localization summary |
|
|
86
87
|
| `validate_scenario` | Run all validation rules |
|
|
87
|
-
| `
|
|
88
|
+
| `save_scenario` | Write the scenario to its folder (edits stay in memory until saved or built) |
|
|
89
|
+
| `build_scenario` | Save and build the `.valkyrie` package with Valkyrie's manifest `.ini` and cover image (default: `Desktop/<Package>/`, like Valkyrie's Create Package) |
|
|
88
90
|
| `set_quest_config` | Set difficulty, play time, investigator count, cover image |
|
|
89
91
|
|
|
90
92
|
### Components
|
|
@@ -111,6 +113,24 @@ npx tsx src/index.ts # Run MCP server via stdio
|
|
|
111
113
|
| `suggest_tile_layout` | Suggest coordinates for large (7x7) tiles in linear, L-shape, or hub-spoke layouts |
|
|
112
114
|
| `place_tile_relative` | Position and rotation for a new tile against an existing one, with a door lined up |
|
|
113
115
|
|
|
116
|
+
### Artwork
|
|
117
|
+
| Tool | Description |
|
|
118
|
+
|------|-------------|
|
|
119
|
+
| `artwork_status` | Check a local ComfyUI server and the FLUX.2 [klein] 4B model files; prints comfy-cli setup and download commands for anything missing |
|
|
120
|
+
| `generate_artwork` | Generate an image (4 steps, CFG 1, size presets per use) and save it into the scenario folder, with a preview |
|
|
121
|
+
|
|
122
|
+
Artwork is optional and runs entirely on your machine. Setup with [comfy-cli](https://github.com/Comfy-Org/comfy-cli):
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pip install comfy-cli && comfy install
|
|
126
|
+
comfy model download --url https://huggingface.co/Comfy-Org/flux2-klein-4B/resolve/main/split_files/diffusion_models/flux-2-klein-4b.safetensors --relative-path models/diffusion_models
|
|
127
|
+
comfy model download --url https://huggingface.co/Comfy-Org/flux2-klein-4B/resolve/main/split_files/text_encoders/qwen_3_4b.safetensors --relative-path models/text_encoders
|
|
128
|
+
comfy model download --url https://huggingface.co/Comfy-Org/flux2-dev/resolve/main/split_files/vae/flux2-vae.safetensors --relative-path models/vae
|
|
129
|
+
comfy launch --background
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
On NVIDIA GPUs with less VRAM, the fp8 model (`black-forest-labs/FLUX.2-klein-4b-fp8`) is smaller. Set `VALKYRIE_COMFYUI_URL` if ComfyUI runs somewhere other than `http://127.0.0.1:8188`. The `/artwork` skill covers prompts and where Valkyrie shows images.
|
|
133
|
+
|
|
114
134
|
### Reference
|
|
115
135
|
| Tool | Description |
|
|
116
136
|
|------|-------------|
|
|
@@ -201,7 +221,7 @@ npm run build # Compile to dist/
|
|
|
201
221
|
|
|
202
222
|
```
|
|
203
223
|
.claude-plugin/ Plugin manifest
|
|
204
|
-
skills/
|
|
224
|
+
skills/ 8 skill SKILL.md files
|
|
205
225
|
agents/ Scenario designer agent
|
|
206
226
|
src/
|
|
207
227
|
io/ INI parser/writer, localization CSV, ZIP packager
|
|
@@ -27,6 +27,7 @@ skills:
|
|
|
27
27
|
- custom-monsters
|
|
28
28
|
- ui-and-puzzles
|
|
29
29
|
- items-and-distribution
|
|
30
|
+
- artwork
|
|
30
31
|
---
|
|
31
32
|
|
|
32
33
|
# Scenario Designer Agent
|
|
@@ -40,6 +41,7 @@ You are an autonomous Mansions of Madness 2nd Edition scenario designer. You cre
|
|
|
40
41
|
- Place tokens for exploration, searches, and encounters
|
|
41
42
|
- Configure monster spawns with appropriate difficulty scaling
|
|
42
43
|
- Build custom puzzles and UI overlays
|
|
44
|
+
- Generate scenario artwork (cover, intro, handouts) when a local ComfyUI is available (`artwork_status`)
|
|
43
45
|
- Set up mythos scaling for tension progression
|
|
44
46
|
- Write atmospheric narrative text in the Lovecraftian horror style
|
|
45
47
|
- Validate scenarios for correctness and completeness
|
|
@@ -54,6 +56,7 @@ Use these skills for detailed pattern guidance:
|
|
|
54
56
|
- `/custom-monsters` — Custom activations, evade/horror events, spawn triggering
|
|
55
57
|
- `/ui-and-puzzles` — Prologues, interactive journals, combination locks, built-in puzzles
|
|
56
58
|
- `/items-and-distribution` — Random items, unique items, starting items, inspection events
|
|
59
|
+
- `/artwork` — Generate pictures with ComfyUI (FLUX.2 [klein]) and show them: cover, intro cutscene, handouts, scenes, monsters, tokens, image puzzles
|
|
57
60
|
|
|
58
61
|
## Design Principles
|
|
59
62
|
|
|
@@ -93,7 +96,8 @@ Use these skills for detailed pattern guidance:
|
|
|
93
96
|
- Use `{rnd:hero}` for random hero selection, `{c:EventName}` for recall
|
|
94
97
|
|
|
95
98
|
### UI & Puzzles
|
|
96
|
-
- Always use `vunits=True` for resolution independence
|
|
99
|
+
- Always use `vunits=True` for resolution independence; positions are offsets from the screen centre
|
|
100
|
+
- A puzzle is an event started from a button (`event1=PuzzleX`); tell its story in the event before it
|
|
97
101
|
- Layering order matters: background first, buttons LAST
|
|
98
102
|
- Clean up all UI elements when dismissing overlays
|
|
99
103
|
|
|
@@ -109,7 +113,8 @@ Use these skills for detailed pattern guidance:
|
|
|
109
113
|
8. Configure items with `upsert_item`
|
|
110
114
|
9. Write all narrative with `set_localization`
|
|
111
115
|
10. Validate with `validate_scenario`
|
|
112
|
-
11.
|
|
116
|
+
11. Save with `save_scenario` (edits stay in memory until saved or built)
|
|
117
|
+
12. Build with `build_scenario`
|
|
113
118
|
|
|
114
119
|
## Available MCP Tools
|
|
115
120
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scenario artwork through a local ComfyUI server running FLUX.2 [klein] 4B.
|
|
3
|
+
* The workflow is ComfyUI's API format; the settings are the ones the distilled
|
|
4
|
+
* model is built for (4 steps, CFG 1, euler), which take ~25 s per image on an M1 Pro
|
|
5
|
+
* once the model is loaded (the first image also loads ~15 GB of weights).
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_COMFYUI_URL: string;
|
|
8
|
+
/** Image sizes per use in a scenario (width x height, multiples of 16) */
|
|
9
|
+
export declare const ARTWORK_PRESETS: {
|
|
10
|
+
/** Scenario list picture (quest.ini image=) and the intro cutscene's left panel */
|
|
11
|
+
readonly cover: {
|
|
12
|
+
readonly width: 896;
|
|
13
|
+
readonly height: 896;
|
|
14
|
+
};
|
|
15
|
+
readonly intro: {
|
|
16
|
+
readonly width: 896;
|
|
17
|
+
readonly height: 896;
|
|
18
|
+
};
|
|
19
|
+
/** Letters, parchments, photographs, journal pages: portrait handouts shown during play */
|
|
20
|
+
readonly handout: {
|
|
21
|
+
readonly width: 768;
|
|
22
|
+
readonly height: 1024;
|
|
23
|
+
};
|
|
24
|
+
/** Landscape pictures shown during play (a room, a revelation, the ending) */
|
|
25
|
+
readonly scene: {
|
|
26
|
+
readonly width: 1024;
|
|
27
|
+
readonly height: 768;
|
|
28
|
+
};
|
|
29
|
+
/** Custom monster portrait (CustomMonster image=) */
|
|
30
|
+
readonly monster: {
|
|
31
|
+
readonly width: 768;
|
|
32
|
+
readonly height: 768;
|
|
33
|
+
};
|
|
34
|
+
/** Board token artwork (Token customImage=) */
|
|
35
|
+
readonly token: {
|
|
36
|
+
readonly width: 512;
|
|
37
|
+
readonly height: 512;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export type ArtworkPreset = keyof typeof ARTWORK_PRESETS;
|
|
41
|
+
/** Appended to every prompt unless a style is given, so a scenario's pictures look like one set */
|
|
42
|
+
export declare const DEFAULT_STYLE: string;
|
|
43
|
+
/** The three files of the FLUX.2 [klein] 4B setup and where to get them */
|
|
44
|
+
export declare const FLUX2_KLEIN_MODELS: {
|
|
45
|
+
readonly diffusion: {
|
|
46
|
+
readonly folder: "diffusion_models";
|
|
47
|
+
readonly match: RegExp;
|
|
48
|
+
readonly downloads: readonly [{
|
|
49
|
+
readonly file: "flux-2-klein-4b.safetensors";
|
|
50
|
+
readonly note: "bf16, 7.2 GB; use this on Apple Silicon (MPS has no fp8)";
|
|
51
|
+
readonly url: "https://huggingface.co/Comfy-Org/flux2-klein-4B/resolve/main/split_files/diffusion_models/flux-2-klein-4b.safetensors";
|
|
52
|
+
}, {
|
|
53
|
+
readonly file: "flux-2-klein-4b-fp8.safetensors";
|
|
54
|
+
readonly note: "fp8, ~4 GB; for NVIDIA GPUs with less VRAM";
|
|
55
|
+
readonly url: "https://huggingface.co/black-forest-labs/FLUX.2-klein-4b-fp8/resolve/main/flux-2-klein-4b-fp8.safetensors";
|
|
56
|
+
}];
|
|
57
|
+
};
|
|
58
|
+
readonly textEncoder: {
|
|
59
|
+
readonly folder: "text_encoders";
|
|
60
|
+
readonly match: RegExp;
|
|
61
|
+
readonly downloads: readonly [{
|
|
62
|
+
readonly file: "qwen_3_4b.safetensors";
|
|
63
|
+
readonly note: "7.5 GB";
|
|
64
|
+
readonly url: "https://huggingface.co/Comfy-Org/flux2-klein-4B/resolve/main/split_files/text_encoders/qwen_3_4b.safetensors";
|
|
65
|
+
}];
|
|
66
|
+
};
|
|
67
|
+
readonly vae: {
|
|
68
|
+
readonly folder: "vae";
|
|
69
|
+
readonly match: RegExp;
|
|
70
|
+
readonly downloads: readonly [{
|
|
71
|
+
readonly file: "flux2-vae.safetensors";
|
|
72
|
+
readonly note: "321 MB; FLUX.1 and SDXL VAEs give distorted colours";
|
|
73
|
+
readonly url: "https://huggingface.co/Comfy-Org/flux2-dev/resolve/main/split_files/vae/flux2-vae.safetensors";
|
|
74
|
+
}];
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
export interface Flux2KleinSettings {
|
|
78
|
+
diffusionModel: string;
|
|
79
|
+
textEncoder: string;
|
|
80
|
+
vae: string;
|
|
81
|
+
prompt: string;
|
|
82
|
+
width: number;
|
|
83
|
+
height: number;
|
|
84
|
+
seed: number;
|
|
85
|
+
steps: number;
|
|
86
|
+
cfg: number;
|
|
87
|
+
filenamePrefix: string;
|
|
88
|
+
}
|
|
89
|
+
/** Recommended sampling for the distilled model; the undistilled "base" model needs more steps and real CFG */
|
|
90
|
+
export declare function recommendedSampling(diffusionModel: string): {
|
|
91
|
+
steps: number;
|
|
92
|
+
cfg: number;
|
|
93
|
+
};
|
|
94
|
+
/** ComfyUI API-format workflow: FLUX.2 [klein] text-to-image */
|
|
95
|
+
export declare function buildFlux2KleinWorkflow(s: Flux2KleinSettings): Record<string, unknown>;
|
|
96
|
+
type Fetch = typeof fetch;
|
|
97
|
+
export interface ArtworkStatus {
|
|
98
|
+
reachable: boolean;
|
|
99
|
+
url: string;
|
|
100
|
+
error?: string;
|
|
101
|
+
comfyuiVersion?: string;
|
|
102
|
+
device?: string;
|
|
103
|
+
models: {
|
|
104
|
+
diffusion?: string;
|
|
105
|
+
textEncoder?: string;
|
|
106
|
+
vae?: string;
|
|
107
|
+
};
|
|
108
|
+
missing: Array<keyof typeof FLUX2_KLEIN_MODELS>;
|
|
109
|
+
}
|
|
110
|
+
/** Checks the ComfyUI server and which of the FLUX.2 [klein] files it can load */
|
|
111
|
+
export declare function getArtworkStatus(url?: string, fetchFn?: Fetch): Promise<ArtworkStatus>;
|
|
112
|
+
/** Human-readable setup instructions for what getArtworkStatus found missing */
|
|
113
|
+
export declare function setupInstructions(status: ArtworkStatus): string;
|
|
114
|
+
export interface GenerateArtworkOptions {
|
|
115
|
+
prompt: string;
|
|
116
|
+
/** Where to save the image; .jpg/.jpeg saves a JPEG (much smaller in the package), otherwise PNG */
|
|
117
|
+
outputFile: string;
|
|
118
|
+
preset?: ArtworkPreset;
|
|
119
|
+
width?: number;
|
|
120
|
+
height?: number;
|
|
121
|
+
seed?: number;
|
|
122
|
+
steps?: number;
|
|
123
|
+
cfg?: number;
|
|
124
|
+
/** Replaces DEFAULT_STYLE; pass '' for no style suffix */
|
|
125
|
+
style?: string;
|
|
126
|
+
url?: string;
|
|
127
|
+
timeoutMs?: number;
|
|
128
|
+
pollMs?: number;
|
|
129
|
+
fetchFn?: Fetch;
|
|
130
|
+
}
|
|
131
|
+
export interface GenerateArtworkResult {
|
|
132
|
+
file: string;
|
|
133
|
+
width: number;
|
|
134
|
+
height: number;
|
|
135
|
+
seed: number;
|
|
136
|
+
steps: number;
|
|
137
|
+
cfg: number;
|
|
138
|
+
diffusionModel: string;
|
|
139
|
+
seconds: number;
|
|
140
|
+
/** Compact JPEG of the result, for showing the image back to the caller */
|
|
141
|
+
preview: Buffer;
|
|
142
|
+
}
|
|
143
|
+
/** Generates one image with FLUX.2 [klein] on a running ComfyUI and saves it to outputFile */
|
|
144
|
+
export declare function generateArtwork(opts: GenerateArtworkOptions): Promise<GenerateArtworkResult>;
|
|
145
|
+
export {};
|
|
146
|
+
//# sourceMappingURL=comfyui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comfyui.d.ts","sourceRoot":"","sources":["../../src/artwork/comfyui.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Scenario artwork through a local ComfyUI server running FLUX.2 [klein] 4B.
|
|
5
|
+
* The workflow is ComfyUI's API format; the settings are the ones the distilled
|
|
6
|
+
* model is built for (4 steps, CFG 1, euler), which take ~25 s per image on an M1 Pro
|
|
7
|
+
* once the model is loaded (the first image also loads ~15 GB of weights).
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_COMFYUI_URL = process.env.VALKYRIE_COMFYUI_URL ?? 'http://127.0.0.1:8188';
|
|
10
|
+
/** Image sizes per use in a scenario (width x height, multiples of 16) */
|
|
11
|
+
export const ARTWORK_PRESETS = {
|
|
12
|
+
/** Scenario list picture (quest.ini image=) and the intro cutscene's left panel */
|
|
13
|
+
cover: { width: 896, height: 896 },
|
|
14
|
+
intro: { width: 896, height: 896 },
|
|
15
|
+
/** Letters, parchments, photographs, journal pages: portrait handouts shown during play */
|
|
16
|
+
handout: { width: 768, height: 1024 },
|
|
17
|
+
/** Landscape pictures shown during play (a room, a revelation, the ending) */
|
|
18
|
+
scene: { width: 1024, height: 768 },
|
|
19
|
+
/** Custom monster portrait (CustomMonster image=) */
|
|
20
|
+
monster: { width: 768, height: 768 },
|
|
21
|
+
/** Board token artwork (Token customImage=) */
|
|
22
|
+
token: { width: 512, height: 512 },
|
|
23
|
+
};
|
|
24
|
+
/** Appended to every prompt unless a style is given, so a scenario's pictures look like one set */
|
|
25
|
+
export const DEFAULT_STYLE = 'Dark painterly Lovecraftian horror illustration in the style of a 1920s Mansions of Madness board game card, '
|
|
26
|
+
+ 'muted desaturated palette, deep shadows, eerie atmosphere, no text, no letters, no watermark.';
|
|
27
|
+
/** The three files of the FLUX.2 [klein] 4B setup and where to get them */
|
|
28
|
+
export const FLUX2_KLEIN_MODELS = {
|
|
29
|
+
diffusion: {
|
|
30
|
+
folder: 'diffusion_models',
|
|
31
|
+
match: /flux-?2-klein-4b/i,
|
|
32
|
+
downloads: [
|
|
33
|
+
{ file: 'flux-2-klein-4b.safetensors', note: 'bf16, 7.2 GB; use this on Apple Silicon (MPS has no fp8)', url: 'https://huggingface.co/Comfy-Org/flux2-klein-4B/resolve/main/split_files/diffusion_models/flux-2-klein-4b.safetensors' },
|
|
34
|
+
{ file: 'flux-2-klein-4b-fp8.safetensors', note: 'fp8, ~4 GB; for NVIDIA GPUs with less VRAM', url: 'https://huggingface.co/black-forest-labs/FLUX.2-klein-4b-fp8/resolve/main/flux-2-klein-4b-fp8.safetensors' },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
textEncoder: {
|
|
38
|
+
folder: 'text_encoders',
|
|
39
|
+
match: /qwen_?3_?4b/i,
|
|
40
|
+
downloads: [
|
|
41
|
+
{ file: 'qwen_3_4b.safetensors', note: '7.5 GB', url: 'https://huggingface.co/Comfy-Org/flux2-klein-4B/resolve/main/split_files/text_encoders/qwen_3_4b.safetensors' },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
vae: {
|
|
45
|
+
folder: 'vae',
|
|
46
|
+
match: /flux2-vae/i,
|
|
47
|
+
downloads: [
|
|
48
|
+
{ file: 'flux2-vae.safetensors', note: '321 MB; FLUX.1 and SDXL VAEs give distorted colours', url: 'https://huggingface.co/Comfy-Org/flux2-dev/resolve/main/split_files/vae/flux2-vae.safetensors' },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
/** Recommended sampling for the distilled model; the undistilled "base" model needs more steps and real CFG */
|
|
53
|
+
export function recommendedSampling(diffusionModel) {
|
|
54
|
+
return /base/i.test(diffusionModel) ? { steps: 20, cfg: 4 } : { steps: 4, cfg: 1 };
|
|
55
|
+
}
|
|
56
|
+
/** ComfyUI API-format workflow: FLUX.2 [klein] text-to-image */
|
|
57
|
+
export function buildFlux2KleinWorkflow(s) {
|
|
58
|
+
return {
|
|
59
|
+
'1': { class_type: 'UNETLoader', inputs: { unet_name: s.diffusionModel, weight_dtype: 'default' } },
|
|
60
|
+
'2': { class_type: 'CLIPLoader', inputs: { clip_name: s.textEncoder, type: 'flux2' } },
|
|
61
|
+
'3': { class_type: 'VAELoader', inputs: { vae_name: s.vae } },
|
|
62
|
+
'4': { class_type: 'CLIPTextEncode', inputs: { clip: ['2', 0], text: s.prompt } },
|
|
63
|
+
'5': { class_type: 'ConditioningZeroOut', inputs: { conditioning: ['4', 0] } },
|
|
64
|
+
'6': { class_type: 'EmptyFlux2LatentImage', inputs: { width: s.width, height: s.height, batch_size: 1 } },
|
|
65
|
+
'7': { class_type: 'Flux2Scheduler', inputs: { steps: s.steps, width: s.width, height: s.height } },
|
|
66
|
+
'8': { class_type: 'KSamplerSelect', inputs: { sampler_name: 'euler' } },
|
|
67
|
+
'9': { class_type: 'RandomNoise', inputs: { noise_seed: s.seed } },
|
|
68
|
+
'10': { class_type: 'CFGGuider', inputs: { model: ['1', 0], positive: ['4', 0], negative: ['5', 0], cfg: s.cfg } },
|
|
69
|
+
'11': { class_type: 'SamplerCustomAdvanced', inputs: { noise: ['9', 0], guider: ['10', 0], sampler: ['8', 0], sigmas: ['7', 0], latent_image: ['6', 0] } },
|
|
70
|
+
'12': { class_type: 'VAEDecode', inputs: { samples: ['11', 0], vae: ['3', 0] } },
|
|
71
|
+
'13': { class_type: 'SaveImage', inputs: { images: ['12', 0], filename_prefix: s.filenamePrefix } },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function getJson(fetchFn, url) {
|
|
75
|
+
const res = await fetchFn(url, { signal: AbortSignal.timeout(10_000) });
|
|
76
|
+
if (!res.ok)
|
|
77
|
+
throw new Error(`${url} answered ${res.status}`);
|
|
78
|
+
return res.json();
|
|
79
|
+
}
|
|
80
|
+
/** Picks the preferred file: the distilled model over "base", then the first match */
|
|
81
|
+
function pickModel(files, match) {
|
|
82
|
+
const candidates = files.filter(f => match.test(f));
|
|
83
|
+
return candidates.find(f => !/base/i.test(f)) ?? candidates[0];
|
|
84
|
+
}
|
|
85
|
+
/** Checks the ComfyUI server and which of the FLUX.2 [klein] files it can load */
|
|
86
|
+
export async function getArtworkStatus(url = DEFAULT_COMFYUI_URL, fetchFn = fetch) {
|
|
87
|
+
const status = { reachable: false, url, models: {}, missing: [] };
|
|
88
|
+
try {
|
|
89
|
+
const stats = await getJson(fetchFn, `${url}/system_stats`);
|
|
90
|
+
status.reachable = true;
|
|
91
|
+
status.comfyuiVersion = stats.system?.comfyui_version;
|
|
92
|
+
status.device = stats.devices?.[0]?.name;
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
status.error = e.message;
|
|
96
|
+
status.missing = ['diffusion', 'textEncoder', 'vae'];
|
|
97
|
+
return status;
|
|
98
|
+
}
|
|
99
|
+
for (const key of ['diffusion', 'textEncoder', 'vae']) {
|
|
100
|
+
const spec = FLUX2_KLEIN_MODELS[key];
|
|
101
|
+
let files = [];
|
|
102
|
+
try {
|
|
103
|
+
files = await getJson(fetchFn, `${url}/models/${spec.folder}`);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Older ComfyUI without /models/<folder>: treat as missing so the setup hint is shown
|
|
107
|
+
}
|
|
108
|
+
const found = pickModel(files, spec.match);
|
|
109
|
+
if (found)
|
|
110
|
+
status.models[key] = found;
|
|
111
|
+
else
|
|
112
|
+
status.missing.push(key);
|
|
113
|
+
}
|
|
114
|
+
return status;
|
|
115
|
+
}
|
|
116
|
+
/** Human-readable setup instructions for what getArtworkStatus found missing */
|
|
117
|
+
export function setupInstructions(status) {
|
|
118
|
+
const lines = [];
|
|
119
|
+
if (!status.reachable) {
|
|
120
|
+
lines.push(`ComfyUI is not reachable at ${status.url}${status.error ? ` (${status.error})` : ''}. With comfy-cli:`, ' pip install comfy-cli (or: uv tool install comfy-cli)', ' comfy install (once; installs ComfyUI into a workspace)', ' comfy launch --background', 'Models kept elsewhere: comfy launch --background -- --extra-model-paths-config <yaml listing diffusion_models, text_encoders, vae>.', 'Set VALKYRIE_COMFYUI_URL if ComfyUI runs on another address.');
|
|
121
|
+
}
|
|
122
|
+
for (const key of status.missing) {
|
|
123
|
+
const spec = FLUX2_KLEIN_MODELS[key];
|
|
124
|
+
lines.push(`Missing ${spec.folder} model. Download one of:`);
|
|
125
|
+
for (const d of spec.downloads) {
|
|
126
|
+
lines.push(` ${d.file} (${d.note}):`, ` comfy model download --url ${d.url} --relative-path models/${spec.folder}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return lines.join('\n');
|
|
130
|
+
}
|
|
131
|
+
function roundTo16(n) {
|
|
132
|
+
return Math.max(256, Math.round(n / 16) * 16);
|
|
133
|
+
}
|
|
134
|
+
/** Generates one image with FLUX.2 [klein] on a running ComfyUI and saves it to outputFile */
|
|
135
|
+
export async function generateArtwork(opts) {
|
|
136
|
+
const url = opts.url ?? DEFAULT_COMFYUI_URL;
|
|
137
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
138
|
+
const status = await getArtworkStatus(url, fetchFn);
|
|
139
|
+
if (!status.reachable || status.missing.length > 0) {
|
|
140
|
+
throw new Error(`ComfyUI is not ready for FLUX.2 [klein].\n${setupInstructions(status)}`);
|
|
141
|
+
}
|
|
142
|
+
const diffusionModel = status.models.diffusion;
|
|
143
|
+
const size = ARTWORK_PRESETS[opts.preset ?? 'scene'];
|
|
144
|
+
const width = roundTo16(opts.width ?? size.width);
|
|
145
|
+
const height = roundTo16(opts.height ?? size.height);
|
|
146
|
+
const sampling = recommendedSampling(diffusionModel);
|
|
147
|
+
const style = opts.style ?? DEFAULT_STYLE;
|
|
148
|
+
const settings = {
|
|
149
|
+
diffusionModel,
|
|
150
|
+
textEncoder: status.models.textEncoder,
|
|
151
|
+
vae: status.models.vae,
|
|
152
|
+
prompt: style ? `${opts.prompt.trim()} ${style}` : opts.prompt.trim(),
|
|
153
|
+
width,
|
|
154
|
+
height,
|
|
155
|
+
seed: opts.seed ?? Math.floor(Math.random() * 2 ** 32),
|
|
156
|
+
steps: opts.steps ?? sampling.steps,
|
|
157
|
+
cfg: opts.cfg ?? sampling.cfg,
|
|
158
|
+
filenamePrefix: `valkyrie_${path.basename(opts.outputFile, path.extname(opts.outputFile)).replace(/[^\w-]/g, '_')}`,
|
|
159
|
+
};
|
|
160
|
+
const started = Date.now();
|
|
161
|
+
const submit = await fetchFn(`${url}/prompt`, {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: { 'Content-Type': 'application/json' },
|
|
164
|
+
body: JSON.stringify({ prompt: buildFlux2KleinWorkflow(settings) }),
|
|
165
|
+
signal: AbortSignal.timeout(30_000),
|
|
166
|
+
});
|
|
167
|
+
const submitted = await submit.json();
|
|
168
|
+
if (!submit.ok || !submitted.prompt_id) {
|
|
169
|
+
throw new Error(`ComfyUI rejected the workflow: ${submitted.error?.message ?? submit.status} ${JSON.stringify(submitted.node_errors ?? {})}`);
|
|
170
|
+
}
|
|
171
|
+
// The first image of a session also loads the models, which can take minutes
|
|
172
|
+
const timeoutMs = opts.timeoutMs ?? 15 * 60_000;
|
|
173
|
+
const pollMs = opts.pollMs ?? 2000;
|
|
174
|
+
let image;
|
|
175
|
+
while (!image) {
|
|
176
|
+
if (Date.now() - started > timeoutMs)
|
|
177
|
+
throw new Error(`ComfyUI did not finish within ${Math.round(timeoutMs / 1000)} s (prompt ${submitted.prompt_id})`);
|
|
178
|
+
await new Promise(r => setTimeout(r, pollMs));
|
|
179
|
+
const history = await getJson(fetchFn, `${url}/history/${submitted.prompt_id}`);
|
|
180
|
+
const entry = history[submitted.prompt_id];
|
|
181
|
+
if (!entry)
|
|
182
|
+
continue;
|
|
183
|
+
if (entry.status?.status_str === 'error') {
|
|
184
|
+
throw new Error(`ComfyUI failed: ${JSON.stringify(entry.status.messages ?? []).slice(0, 1000)}`);
|
|
185
|
+
}
|
|
186
|
+
image = Object.values(entry.outputs ?? {}).flatMap(o => o.images ?? [])[0];
|
|
187
|
+
if (!image && entry.status?.status_str === 'success')
|
|
188
|
+
throw new Error('ComfyUI finished without an image');
|
|
189
|
+
}
|
|
190
|
+
const view = (extra = '') => `${url}/view?filename=${encodeURIComponent(image.filename)}&subfolder=${encodeURIComponent(image.subfolder)}&type=${image.type}${extra}`;
|
|
191
|
+
const download = async (u) => {
|
|
192
|
+
const res = await fetchFn(u, { signal: AbortSignal.timeout(60_000) });
|
|
193
|
+
if (!res.ok)
|
|
194
|
+
throw new Error(`Could not download the image from ComfyUI (${res.status})`);
|
|
195
|
+
return Buffer.from(await res.arrayBuffer());
|
|
196
|
+
};
|
|
197
|
+
const jpeg = /\.jpe?g$/i.test(opts.outputFile);
|
|
198
|
+
const full = await download(view(jpeg ? '&preview=jpeg;90' : ''));
|
|
199
|
+
const preview = jpeg ? full : await download(view('&preview=jpeg;80'));
|
|
200
|
+
fs.mkdirSync(path.dirname(opts.outputFile), { recursive: true });
|
|
201
|
+
fs.writeFileSync(opts.outputFile, full);
|
|
202
|
+
return {
|
|
203
|
+
file: opts.outputFile,
|
|
204
|
+
width,
|
|
205
|
+
height,
|
|
206
|
+
seed: settings.seed,
|
|
207
|
+
steps: settings.steps,
|
|
208
|
+
cfg: settings.cfg,
|
|
209
|
+
diffusionModel,
|
|
210
|
+
seconds: Math.round((Date.now() - started) / 1000),
|
|
211
|
+
preview,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=comfyui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comfyui.js","sourceRoot":"","sources":["../../src/artwork/comfyui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,uBAAuB,CAAC;AAE/F,0EAA0E;AAC1E,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,mFAAmF;IACnF,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;IAClC,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;IAClC,2FAA2F;IAC3F,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IACrC,8EAA8E;IAC9E,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;IACnC,qDAAqD;IACrD,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;IACpC,+CAA+C;IAC/C,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;CAC1B,CAAC;AAIX,mGAAmG;AACnG,MAAM,CAAC,MAAM,aAAa,GACxB,+GAA+G;MAC7G,+FAA+F,CAAC;AAEpG,2EAA2E;AAC3E,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,SAAS,EAAE;QACT,MAAM,EAAE,kBAAkB;QAC1B,KAAK,EAAE,mBAAmB;QAC1B,SAAS,EAAE;YACT,EAAE,IAAI,EAAE,6BAA6B,EAAE,IAAI,EAAE,0DAA0D,EAAE,GAAG,EAAE,uHAAuH,EAAE;YACvO,EAAE,IAAI,EAAE,iCAAiC,EAAE,IAAI,EAAE,4CAA4C,EAAE,GAAG,EAAE,2GAA2G,EAAE;SAClN;KACF;IACD,WAAW,EAAE;QACX,MAAM,EAAE,eAAe;QACvB,KAAK,EAAE,cAAc;QACrB,SAAS,EAAE;YACT,EAAE,IAAI,EAAE,uBAAuB,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,8GAA8G,EAAE;SACvK;KACF;IACD,GAAG,EAAE;QACH,MAAM,EAAE,KAAK;QACb,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE;YACT,EAAE,IAAI,EAAE,uBAAuB,EAAE,IAAI,EAAE,qDAAqD,EAAE,GAAG,EAAE,+FAA+F,EAAE;SACrM;KACF;CACO,CAAC;AAeX,+GAA+G;AAC/G,MAAM,UAAU,mBAAmB,CAAC,cAAsB;IACxD,OAAO,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AACrF,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,uBAAuB,CAAC,CAAqB;IAC3D,OAAO;QACL,GAAG,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,cAAc,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE;QACnG,GAAG,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QACtF,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,EAAE;QAC7D,GAAG,EAAE,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE;QACjF,GAAG,EAAE,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE;QAC9E,GAAG,EAAE,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE;QACzG,GAAG,EAAE,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE;QACnG,GAAG,EAAE,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE;QACxE,GAAG,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE;QAClE,IAAI,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,EAAE;QAClH,IAAI,EAAE,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE;QAC1J,IAAI,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE;QAChF,IAAI,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,cAAc,EAAE,EAAE;KACpG,CAAC;AACJ,CAAC;AAID,KAAK,UAAU,OAAO,CAAC,OAAc,EAAE,GAAW;IAChD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACxE,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,aAAa,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9D,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;AACpB,CAAC;AAED,sFAAsF;AACtF,SAAS,SAAS,CAAC,KAAe,EAAE,KAAa;IAC/C,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAYD,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAG,GAAG,mBAAmB,EAAE,UAAiB,KAAK;IACtF,MAAM,MAAM,GAAkB,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACjF,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,eAAe,CAAkF,CAAC;QAC7I,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC,MAAM,EAAE,eAAe,CAAC;QACtD,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;IAC3C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,GAAI,CAAW,CAAC,OAAO,CAAC;QACpC,MAAM,CAAC,OAAO,GAAG,CAAC,WAAW,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC;QACrD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,aAAa,EAAE,KAAK,CAAU,EAAE,CAAC;QAC/D,MAAM,IAAI,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,KAAK,GAAa,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,WAAW,IAAI,CAAC,MAAM,EAAE,CAAa,CAAC;QAC7E,CAAC;QAAC,MAAM,CAAC;YACP,sFAAsF;QACxF,CAAC;QACD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,KAAK;YAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;;YACjC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,iBAAiB,CAAC,MAAqB;IACrD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CACR,+BAA+B,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,mBAAmB,EACvG,2DAA2D,EAC3D,qEAAqE,EACrE,6BAA6B,EAC7B,qIAAqI,EACrI,8DAA8D,CAC/D,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,MAAM,0BAA0B,CAAC,CAAC;QAC7D,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,kCAAkC,CAAC,CAAC,GAAG,2BAA2B,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1H,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAiCD,SAAS,SAAS,CAAC,CAAS;IAC1B,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAA4B;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,mBAAmB,CAAC;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,6CAA6C,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,SAAU,CAAC;IAChD,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,cAAc,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,aAAa,CAAC;IAC1C,MAAM,QAAQ,GAAuB;QACnC,cAAc;QACd,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,WAAY;QACvC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,GAAI;QACvB,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;QACrE,KAAK;QACL,MAAM;QACN,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;QACtD,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,KAAK;QACnC,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,QAAQ,CAAC,GAAG;QAC7B,cAAc,EAAE,YAAY,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE;KACpH,CAAC;IAEF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,GAAG,SAAS,EAAE;QAC5C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;KACpC,CAAC,CAAC;IACH,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,IAAI,EAAiF,CAAC;IACrH,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,kCAAkC,SAAS,CAAC,KAAK,EAAE,OAAO,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IAChJ,CAAC;IAED,6EAA6E;IAC7E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,GAAG,MAAM,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC;IAEnC,IAAI,KAAwE,CAAC;IAC7E,OAAO,CAAC,KAAK,EAAE,CAAC;QACd,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,cAAc,SAAS,CAAC,SAAS,GAAG,CAAC,CAAC;QACzJ,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,YAAY,SAAS,CAAC,SAAS,EAAE,CAAY,CAAC;QAC3F,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,IAAI,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACnG,CAAC;QACD,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3E,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAC7G,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,KAAK,GAAG,EAAE,EAAE,EAAE,CAC1B,GAAG,GAAG,kBAAkB,kBAAkB,CAAC,KAAM,CAAC,QAAQ,CAAC,cAAc,kBAAkB,CAAC,KAAM,CAAC,SAAS,CAAC,SAAS,KAAM,CAAC,IAAI,GAAG,KAAK,EAAE,CAAC;IAC9I,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAS,EAAE,EAAE;QACnC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QAC1F,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IAC9C,CAAC,CAAC;IACF,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAEvE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACxC,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,UAAU;QACrB,KAAK;QACL,MAAM;QACN,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,GAAG,EAAE,QAAQ,CAAC,GAAG;QACjB,cAAc;QACd,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC;QAClD,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ScenarioModel } from '../model/scenario-model.js';
|
|
2
|
+
/**
|
|
3
|
+
* Package name Valkyrie can publish: the scenario folder name, which must not contain spaces
|
|
4
|
+
* (it becomes the package and manifest file name, and the key in the download list).
|
|
5
|
+
* "The Wrath of Elder Claude" → "TheWrathOfElderClaude".
|
|
6
|
+
*/
|
|
7
|
+
export declare function toPackageName(name: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Writes what Valkyrie's editor "Create Package" writes next to the .valkyrie file
|
|
10
|
+
* (EditorTools.CreatePackage): <Package>.ini, the manifest a scenario list entry is made from,
|
|
11
|
+
* and a copy of the cover image. version is the SHA-256 of the package, as .NET's
|
|
12
|
+
* BitConverter.ToString prints it ("AB-CD-...").
|
|
13
|
+
*/
|
|
14
|
+
export declare function writePackageManifest(model: ScenarioModel, packagePath: string): {
|
|
15
|
+
manifest: string;
|
|
16
|
+
icon?: string;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=package-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-manifest.d.ts","sourceRoot":"","sources":["../../src/io/package-manifest.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAoCnH"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { serializeQuestConfig } from '../model/component-types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Package name Valkyrie can publish: the scenario folder name, which must not contain spaces
|
|
7
|
+
* (it becomes the package and manifest file name, and the key in the download list).
|
|
8
|
+
* "The Wrath of Elder Claude" → "TheWrathOfElderClaude".
|
|
9
|
+
*/
|
|
10
|
+
export function toPackageName(name) {
|
|
11
|
+
const words = name.normalize('NFKD').replace(/[̀-ͯ]/g, '').split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
12
|
+
return words.map(w => w[0].toUpperCase() + w.slice(1)).join('') || 'Scenario';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Writes what Valkyrie's editor "Create Package" writes next to the .valkyrie file
|
|
16
|
+
* (EditorTools.CreatePackage): <Package>.ini, the manifest a scenario list entry is made from,
|
|
17
|
+
* and a copy of the cover image. version is the SHA-256 of the package, as .NET's
|
|
18
|
+
* BitConverter.ToString prints it ("AB-CD-...").
|
|
19
|
+
*/
|
|
20
|
+
export function writePackageManifest(model, packagePath) {
|
|
21
|
+
const destination = path.dirname(packagePath);
|
|
22
|
+
const packageName = path.basename(packagePath, path.extname(packagePath));
|
|
23
|
+
const digest = createHash('sha256').update(fs.readFileSync(packagePath)).digest('hex').toUpperCase();
|
|
24
|
+
const version = digest.match(/../g).join('-');
|
|
25
|
+
const config = { ...model.questConfig, version };
|
|
26
|
+
let icon;
|
|
27
|
+
if (config.image) {
|
|
28
|
+
const source = path.join(model.scenarioDir, config.image);
|
|
29
|
+
icon = path.basename(config.image);
|
|
30
|
+
if (fs.existsSync(source))
|
|
31
|
+
fs.copyFileSync(source, path.join(destination, icon));
|
|
32
|
+
config.image = icon;
|
|
33
|
+
}
|
|
34
|
+
const lines = ['[Quest]'];
|
|
35
|
+
for (const [k, v] of Object.entries(serializeQuestConfig(config)))
|
|
36
|
+
lines.push(`${k}=${v}`);
|
|
37
|
+
// The localization file holds the default language; each text is written under that language's name
|
|
38
|
+
const lang = config.defaultlanguage || 'English';
|
|
39
|
+
const text = (key) => model.localization.get(key);
|
|
40
|
+
const oneLine = (v) => v.replace(/\r/g, '').replace(/\\n|\n/g, '');
|
|
41
|
+
const escaped = (v) => v.replace(/\r/g, '').replace(/\n/g, '\\n');
|
|
42
|
+
const entries = [
|
|
43
|
+
['name', text('quest.name'), v => v],
|
|
44
|
+
['synopsys', text('quest.synopsys'), oneLine],
|
|
45
|
+
['description', text('quest.description'), escaped],
|
|
46
|
+
['authors', text('quest.authors'), escaped],
|
|
47
|
+
['authors_short', text('quest.authors_short'), oneLine],
|
|
48
|
+
];
|
|
49
|
+
for (const [field, value, format] of entries) {
|
|
50
|
+
if (value !== undefined)
|
|
51
|
+
lines.push(`${field}.${lang}=${format(value)}`);
|
|
52
|
+
}
|
|
53
|
+
const manifest = path.join(destination, `${packageName}.ini`);
|
|
54
|
+
fs.writeFileSync(manifest, lines.join('\n') + '\n');
|
|
55
|
+
return { manifest, icon };
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=package-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-manifest.js","sourceRoot":"","sources":["../../src/io/package-manifest.ts"],"names":[],"mappings":"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"}
|