@mcptoolshop/style-dataset-lab 1.2.1 → 2.0.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 (50) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +95 -116
  3. package/bin/sdlab.js +103 -0
  4. package/lib/args.js +106 -0
  5. package/lib/comfyui.js +109 -0
  6. package/lib/config.js +104 -0
  7. package/lib/deprecation.js +14 -0
  8. package/lib/paths.js +38 -0
  9. package/lib/records.js +69 -0
  10. package/package.json +13 -4
  11. package/scripts/bulk-curate-wave2-5.js +1 -1
  12. package/scripts/bulk-curate-waves11-18.js +1 -1
  13. package/scripts/canon-bind.js +268 -332
  14. package/scripts/compare.js +40 -30
  15. package/scripts/curate-wave25.js +1 -1
  16. package/scripts/curate.js +34 -28
  17. package/scripts/doctor.js +321 -0
  18. package/scripts/generate-controlnet.js +36 -121
  19. package/scripts/generate-identity.js +38 -106
  20. package/scripts/generate-ipadapter.js +42 -119
  21. package/scripts/generate.js +22 -81
  22. package/scripts/init.js +226 -0
  23. package/scripts/migrate-records.js +15 -21
  24. package/scripts/painterly-test.js +20 -47
  25. package/scripts/painterly.js +27 -106
  26. package/templates/domains/architecture/constitution.json +97 -0
  27. package/templates/domains/architecture/lanes.json +41 -0
  28. package/templates/domains/architecture/project.json +20 -0
  29. package/templates/domains/architecture/rubric.json +40 -0
  30. package/templates/domains/architecture/terminology.json +19 -0
  31. package/templates/domains/character-design/constitution.json +88 -0
  32. package/templates/domains/character-design/lanes.json +35 -0
  33. package/templates/domains/character-design/project.json +17 -0
  34. package/templates/domains/character-design/rubric.json +38 -0
  35. package/templates/domains/character-design/terminology.json +21 -0
  36. package/templates/domains/creature-design/constitution.json +87 -0
  37. package/templates/domains/creature-design/lanes.json +41 -0
  38. package/templates/domains/creature-design/project.json +18 -0
  39. package/templates/domains/creature-design/rubric.json +39 -0
  40. package/templates/domains/creature-design/terminology.json +20 -0
  41. package/templates/domains/game-art/constitution.json +113 -0
  42. package/templates/domains/game-art/lanes.json +47 -0
  43. package/templates/domains/game-art/project.json +17 -0
  44. package/templates/domains/game-art/rubric.json +36 -0
  45. package/templates/domains/game-art/terminology.json +30 -0
  46. package/templates/domains/vehicle-mech/constitution.json +87 -0
  47. package/templates/domains/vehicle-mech/lanes.json +41 -0
  48. package/templates/domains/vehicle-mech/project.json +17 -0
  49. package/templates/domains/vehicle-mech/rubric.json +40 -0
  50. package/templates/domains/vehicle-mech/terminology.json +21 -0
@@ -4,31 +4,18 @@
4
4
  * generate-ipadapter.js — IP-Adapter guided generation for structural subjects.
5
5
  *
6
6
  * Uses a reference image to guide the shape/structure of the output
7
- * while the text prompt adds identity-specific details (wear, patching, name).
7
+ * while the text prompt adds identity-specific details.
8
8
  *
9
9
  * Usage:
10
10
  * node scripts/generate-ipadapter.js --subject corrigan --ref <image> --seeds 4
11
- * node scripts/generate-ipadapter.js --subject corrigan --ref <image> --seeds 4 --dry-run
12
- *
13
- * Options:
14
- * --subject NAME Subject name for output file naming
15
- * --ref PATH Reference image path (the structural truth source)
16
- * --prompt TEXT Generation prompt
17
- * --negative TEXT Negative prompt
18
- * --seeds N Number of seeds (default: 4)
19
- * --weight W IP-Adapter weight (default: 0.55)
20
- * --start S IP-Adapter start (default: 0.0)
21
- * --end E IP-Adapter end (default: 0.8)
22
- * --dry-run Print workflow without running
11
+ * sdlab generate:ipadapter --subject corrigan --ref <image> --seeds 4 --project star-freight
23
12
  */
24
13
 
25
14
  import { readFile, writeFile, mkdir } from "node:fs/promises";
26
15
  import { join } from "node:path";
27
-
28
- const COMFY_URL = process.env.COMFY_URL || "http://127.0.0.1:8188";
29
- const REPO_ROOT = new URL("..", import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1");
30
- const GAME = process.argv.find((a, i) => process.argv[i - 1] === '--game') || 'star-freight';
31
- const GAME_ROOT = join(REPO_ROOT, 'games', GAME);
16
+ import { getProjectName } from "../lib/args.js";
17
+ import { REPO_ROOT } from "../lib/paths.js";
18
+ import { comfyHealth, submitAndWait, downloadImage, uploadImage } from "../lib/comfyui.js";
32
19
 
33
20
  const DEFAULTS = {
34
21
  checkpoint: "dreamshaperXL_v21TurboDPMSDE.safetensors",
@@ -44,11 +31,10 @@ const DEFAULTS = {
44
31
  base_seed: 27200,
45
32
  };
46
33
 
47
- function parseArgs() {
48
- const args = process.argv.slice(2);
34
+ function parseLocalArgs(argv) {
49
35
  const get = (flag, def) => {
50
- const i = args.indexOf(flag);
51
- return i >= 0 && i + 1 < args.length ? args[i + 1] : def;
36
+ const i = argv.indexOf(flag);
37
+ return i >= 0 && i + 1 < argv.length ? argv[i + 1] : def;
52
38
  };
53
39
  return {
54
40
  subject: get("--subject", "unknown"),
@@ -59,7 +45,7 @@ function parseArgs() {
59
45
  weight: parseFloat(get("--weight", "0.55")),
60
46
  start: parseFloat(get("--start", "0.0")),
61
47
  end: parseFloat(get("--end", "0.8")),
62
- dryRun: args.includes("--dry-run"),
48
+ dryRun: argv.includes("--dry-run"),
63
49
  };
64
50
  }
65
51
 
@@ -67,7 +53,6 @@ function buildIPAdapterWorkflow(prompt, negativePrompt, seed, refImageName, weig
67
53
  const nodes = {};
68
54
  let nextId = 1;
69
55
 
70
- // Checkpoint
71
56
  const ckptId = String(nextId++);
72
57
  nodes[ckptId] = {
73
58
  class_type: "CheckpointLoaderSimple",
@@ -77,33 +62,30 @@ function buildIPAdapterWorkflow(prompt, negativePrompt, seed, refImageName, weig
77
62
  let modelOut = [ckptId, 0];
78
63
  let clipOut = [ckptId, 1];
79
64
 
80
- // LoRA
81
65
  for (const lora of DEFAULTS.loras) {
82
66
  const loraId = String(nextId++);
83
67
  nodes[loraId] = {
84
68
  class_type: "LoraLoader",
85
69
  inputs: {
86
- model: modelOut,
87
- clip: clipOut,
70
+ model: modelOut, clip: clipOut,
88
71
  lora_name: lora.name,
89
- strength_model: lora.weight,
90
- strength_clip: lora.weight,
72
+ strength_model: lora.weight, strength_clip: lora.weight,
91
73
  },
92
74
  };
93
75
  modelOut = [loraId, 0];
94
76
  clipOut = [loraId, 1];
95
77
  }
96
78
 
97
- // Load CLIP Vision
79
+ // CLIP Vision
98
80
  const clipVisionId = String(nextId++);
99
81
  nodes[clipVisionId] = {
100
82
  class_type: "CLIPVisionLoader",
101
83
  inputs: { clip_name: DEFAULTS.clip_vision_model },
102
84
  };
103
85
 
104
- // Load IP-Adapter model
105
- const ipaLoadId = String(nextId++);
106
- nodes[ipaLoadId] = {
86
+ // IP-Adapter model
87
+ const ipaLoaderId = String(nextId++);
88
+ nodes[ipaLoaderId] = {
107
89
  class_type: "IPAdapterModelLoader",
108
90
  inputs: { ipadapter_file: DEFAULTS.ipadapter_model },
109
91
  };
@@ -115,47 +97,43 @@ function buildIPAdapterWorkflow(prompt, negativePrompt, seed, refImageName, weig
115
97
  inputs: { image: refImageName },
116
98
  };
117
99
 
118
- // Apply IP-Adapter (using IPAdapterAdvanced from ComfyUI_IPAdapter_plus)
100
+ // Apply IP-Adapter
119
101
  const ipaApplyId = String(nextId++);
120
102
  nodes[ipaApplyId] = {
121
103
  class_type: "IPAdapterAdvanced",
122
104
  inputs: {
123
105
  model: modelOut,
124
- ipadapter: [ipaLoadId, 0],
125
- image: [refImgId, 0],
106
+ ipadapter: [ipaLoaderId, 0],
126
107
  clip_vision: [clipVisionId, 0],
127
- weight: weight,
108
+ image: [refImgId, 0],
109
+ weight,
128
110
  weight_type: "linear",
129
111
  combine_embeds: "concat",
112
+ embeds_scaling: "V only",
130
113
  start_at: start,
131
114
  end_at: end,
132
- embeds_scaling: "V only",
133
115
  },
134
116
  };
135
117
  modelOut = [ipaApplyId, 0];
136
118
 
137
- // Positive prompt
138
119
  const posId = String(nextId++);
139
120
  nodes[posId] = {
140
121
  class_type: "CLIPTextEncode",
141
122
  inputs: { text: prompt, clip: clipOut },
142
123
  };
143
124
 
144
- // Negative prompt
145
125
  const negId = String(nextId++);
146
126
  nodes[negId] = {
147
127
  class_type: "CLIPTextEncode",
148
128
  inputs: { text: negativePrompt, clip: clipOut },
149
129
  };
150
130
 
151
- // Empty latent
152
131
  const latentId = String(nextId++);
153
132
  nodes[latentId] = {
154
133
  class_type: "EmptyLatentImage",
155
134
  inputs: { width: DEFAULTS.width, height: DEFAULTS.height, batch_size: 1 },
156
135
  };
157
136
 
158
- // KSampler
159
137
  const samplerId = String(nextId++);
160
138
  nodes[samplerId] = {
161
139
  class_type: "KSampler",
@@ -173,14 +151,12 @@ function buildIPAdapterWorkflow(prompt, negativePrompt, seed, refImageName, weig
173
151
  },
174
152
  };
175
153
 
176
- // VAE Decode
177
154
  const vaeId = String(nextId++);
178
155
  nodes[vaeId] = {
179
156
  class_type: "VAEDecode",
180
157
  inputs: { samples: [samplerId, 0], vae: [ckptId, 2] },
181
158
  };
182
159
 
183
- // Save
184
160
  const saveId = String(nextId++);
185
161
  nodes[saveId] = {
186
162
  class_type: "SaveImage",
@@ -190,62 +166,18 @@ function buildIPAdapterWorkflow(prompt, negativePrompt, seed, refImageName, weig
190
166
  return nodes;
191
167
  }
192
168
 
193
- async function comfyHealth() {
194
- try {
195
- const res = await fetch(`${COMFY_URL}/system_stats`);
196
- return res.ok;
197
- } catch {
198
- return false;
199
- }
200
- }
201
-
202
- const MAX_POLL_MS = 600_000; // 10 minutes
169
+ export async function run(argv = process.argv.slice(2)) {
170
+ const projectName = getProjectName(argv);
171
+ const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
172
+ const COMFY_URL = process.env.COMFY_URL || "http://127.0.0.1:8188";
203
173
 
204
- async function submitAndWait(workflow) {
205
- const clientId = `ipa-${Date.now()}`;
206
- const res = await fetch(`${COMFY_URL}/prompt`, {
207
- method: "POST",
208
- headers: { "Content-Type": "application/json" },
209
- body: JSON.stringify({ prompt: workflow, client_id: clientId }),
210
- });
211
- if (!res.ok) {
212
- const text = await res.text();
213
- throw new Error(`ComfyUI submit failed: ${res.status} ${text}`);
214
- }
215
- const { prompt_id: promptId } = await res.json();
216
- const start = Date.now();
217
- while (true) {
218
- if (Date.now() - start > MAX_POLL_MS) throw new Error(`ComfyUI poll timeout after ${MAX_POLL_MS/1000}s for prompt ${promptId}`);
219
- await new Promise((r) => setTimeout(r, 1000));
220
- const histRes = await fetch(`${COMFY_URL}/history/${promptId}`);
221
- if (!histRes.ok) continue;
222
- const history = await histRes.json();
223
- const entry = history[promptId];
224
- if (!entry) continue;
225
- if (entry.status?.completed) return entry;
226
- if (entry.status?.status_str === "error") {
227
- throw new Error(`ComfyUI error: ${JSON.stringify(entry.status)}`);
228
- }
229
- }
230
- }
231
-
232
- async function downloadImage(filename, subfolder, type = "output") {
233
- const url = `${COMFY_URL}/view?filename=${encodeURIComponent(filename)}&subfolder=${encodeURIComponent(subfolder || "")}&type=${type}`;
234
- const res = await fetch(url);
235
- if (!res.ok) throw new Error(`Download failed: ${res.status}`);
236
- return Buffer.from(await res.arrayBuffer());
237
- }
238
-
239
- async function main() {
240
- const opts = parseArgs();
174
+ const opts = parseLocalArgs(argv);
241
175
 
242
176
  if (!opts.refPath) {
243
- console.error("Error: --ref <path> is required");
244
- process.exit(1);
177
+ throw new Error("--ref <path> is required");
245
178
  }
246
179
  if (!opts.prompt) {
247
- console.error("Error: --prompt is required");
248
- process.exit(1);
180
+ throw new Error("--prompt is required");
249
181
  }
250
182
 
251
183
  const negative = opts.negative ||
@@ -259,33 +191,21 @@ async function main() {
259
191
  console.log(` Seeds: ${opts.seeds}`);
260
192
  console.log("");
261
193
 
194
+ const refFilename = `ipa_ref_${opts.subject}.png`;
195
+
262
196
  if (!opts.dryRun) {
263
- const online = await comfyHealth();
197
+ const online = await comfyHealth(COMFY_URL);
264
198
  if (!online) {
265
- console.error("\x1b[31mError:\x1b[0m ComfyUI not reachable");
266
- process.exit(1);
199
+ throw new Error("ComfyUI not reachable");
267
200
  }
268
201
  console.log("\x1b[32m+\x1b[0m ComfyUI online");
269
202
 
270
- // Upload reference image
271
- const refData = await readFile(join(GAME_ROOT, opts.refPath));
272
- const refFilename = `ipa_ref_${opts.subject}.png`;
273
- const formData = new FormData();
274
- formData.append("image", new Blob([refData], { type: "image/png" }), refFilename);
275
- formData.append("overwrite", "true");
276
- const uploadRes = await fetch(`${COMFY_URL}/upload/image`, { method: "POST", body: formData });
277
- if (!uploadRes.ok) {
278
- console.error(`Reference upload failed: ${uploadRes.status}`);
279
- process.exit(1);
280
- }
281
- const uploadResult = await uploadRes.json();
282
- console.log(`\x1b[32m+\x1b[0m Reference uploaded: ${uploadResult.name}`);
203
+ await uploadImage(join(GAME_ROOT, opts.refPath), refFilename, COMFY_URL);
204
+ console.log(`\x1b[32m+\x1b[0m Reference uploaded: ${refFilename}`);
283
205
  }
284
206
 
285
207
  await mkdir(join(GAME_ROOT, "outputs/candidates"), { recursive: true });
286
208
 
287
- const refFilename = `ipa_ref_${opts.subject}.png`;
288
-
289
209
  for (let si = 0; si < opts.seeds; si++) {
290
210
  const seed = DEFAULTS.base_seed + si;
291
211
  const assetId = `${opts.subject}_ipa_s${si}`;
@@ -302,7 +222,7 @@ async function main() {
302
222
  );
303
223
 
304
224
  try {
305
- const result = await submitAndWait(workflow);
225
+ const result = await submitAndWait(workflow, COMFY_URL, { clientPrefix: 'ipa' });
306
226
  const elapsed = Date.now() - startMs;
307
227
 
308
228
  const outputs = result.outputs || {};
@@ -321,7 +241,7 @@ async function main() {
321
241
  continue;
322
242
  }
323
243
 
324
- const imgData = await downloadImage(imageFile, imageSubfolder);
244
+ const imgData = await downloadImage(imageFile, imageSubfolder, COMFY_URL);
325
245
  await writeFile(join(GAME_ROOT, destPath), imgData);
326
246
  console.log(` \x1b[32m+\x1b[0m ${destPath} (${elapsed}ms, ${imgData.length} bytes)`);
327
247
  } catch (err) {
@@ -333,7 +253,10 @@ async function main() {
333
253
  if (opts.dryRun) console.log(" (dry run)");
334
254
  }
335
255
 
336
- main().catch((err) => {
337
- console.error(err);
338
- process.exit(1);
339
- });
256
+ // Direct execution guard
257
+ if (process.argv[1] && (process.argv[1].endsWith('generate-ipadapter.js') || process.argv[1].endsWith('generate-ipadapter'))) {
258
+ run().catch((err) => {
259
+ console.error(err.message || err);
260
+ process.exit(1);
261
+ });
262
+ }
@@ -4,29 +4,17 @@
4
4
  * generate.js — Drive ComfyUI to produce candidate assets from a prompt pack.
5
5
  *
6
6
  * Usage: node scripts/generate.js [--prompt-pack inputs/prompts/rpg-icons-lane1.json]
7
+ * sdlab generate inputs/prompts/wave1.json --project star-freight
7
8
  *
8
9
  * For each subject × variation, submits a workflow to ComfyUI HTTP API,
9
10
  * downloads the output PNG, and writes a provenance record to records/.
10
11
  */
11
12
 
12
13
  import { readFile, writeFile, mkdir } from "node:fs/promises";
13
- import { join, basename } from "node:path";
14
-
15
- const COMFY_URL = process.env.COMFY_URL || "http://127.0.0.1:8188";
16
- const REPO_ROOT = new URL("..", import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1");
17
- const GAME = process.argv.find((a, i) => process.argv[i - 1] === '--game') || 'star-freight';
18
- const GAME_ROOT = join(REPO_ROOT, 'games', GAME);
19
-
20
- // ── ComfyUI API helpers ──
21
-
22
- async function comfyHealth() {
23
- try {
24
- const res = await fetch(`${COMFY_URL}/system_stats`);
25
- return res.ok;
26
- } catch {
27
- return false;
28
- }
29
- }
14
+ import { join } from "node:path";
15
+ import { getProjectName } from "../lib/args.js";
16
+ import { REPO_ROOT } from "../lib/paths.js";
17
+ import { comfyHealth, submitAndWait, downloadImage } from "../lib/comfyui.js";
30
18
 
31
19
  function buildWorkflow(prompt, negativePrompt, checkpoint, loras, seed, steps, cfg, sampler, scheduler, width, height, ipAdapterConfig) {
32
20
  const nodes = {};
@@ -164,57 +152,13 @@ function buildWorkflow(prompt, negativePrompt, checkpoint, loras, seed, steps, c
164
152
  return { nodes, saveNodeId: saveId };
165
153
  }
166
154
 
167
- const MAX_POLL_MS = 600_000; // 10 minutes
168
-
169
- async function submitAndWait(workflow) {
170
- const clientId = `sdl-${Date.now()}`;
171
-
172
- // Submit
173
- const res = await fetch(`${COMFY_URL}/prompt`, {
174
- method: "POST",
175
- headers: { "Content-Type": "application/json" },
176
- body: JSON.stringify({ prompt: workflow, client_id: clientId }),
177
- });
178
-
179
- if (!res.ok) {
180
- const text = await res.text();
181
- throw new Error(`ComfyUI submit failed: ${res.status} ${text}`);
182
- }
183
-
184
- const { prompt_id: promptId } = await res.json();
185
-
186
- // Poll history until complete
187
- const start = Date.now();
188
- while (true) {
189
- if (Date.now() - start > MAX_POLL_MS) throw new Error(`ComfyUI poll timeout after ${MAX_POLL_MS/1000}s for prompt ${promptId}`);
190
- await new Promise((r) => setTimeout(r, 1000));
191
- const histRes = await fetch(`${COMFY_URL}/history/${promptId}`);
192
- if (!histRes.ok) continue;
193
- const history = await histRes.json();
194
- const entry = history[promptId];
195
- if (!entry) continue;
196
- if (entry.status?.completed) {
197
- return entry;
198
- }
199
- if (entry.status?.status_str === "error") {
200
- throw new Error(`ComfyUI generation failed: ${JSON.stringify(entry.status)}`);
201
- }
202
- }
203
- }
204
-
205
- async function downloadImage(filename, subfolder, type = "output") {
206
- const url = `${COMFY_URL}/view?filename=${encodeURIComponent(filename)}&subfolder=${encodeURIComponent(subfolder || "")}&type=${type}`;
207
- const res = await fetch(url);
208
- if (!res.ok) throw new Error(`Download failed: ${res.status}`);
209
- return Buffer.from(await res.arrayBuffer());
210
- }
211
-
212
- // ── Main ──
155
+ export async function run(argv = process.argv.slice(2)) {
156
+ const projectName = getProjectName(argv);
157
+ const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
158
+ const COMFY_URL = process.env.COMFY_URL || "http://127.0.0.1:8188";
213
159
 
214
- async function main() {
215
- const args = process.argv.slice(2);
216
- const packPath = args.find((a) => !a.startsWith("--")) || "inputs/prompts/rpg-icons-lane1.json";
217
- const dryRun = args.includes("--dry-run");
160
+ const packPath = argv.find((a) => !a.startsWith("--")) || "inputs/prompts/rpg-icons-lane1.json";
161
+ const dryRun = argv.includes("--dry-run");
218
162
 
219
163
  const fullPackPath = join(GAME_ROOT, packPath);
220
164
  const pack = JSON.parse(await readFile(fullPackPath, "utf-8"));
@@ -227,11 +171,9 @@ async function main() {
227
171
  console.log("");
228
172
 
229
173
  if (!dryRun) {
230
- const online = await comfyHealth();
174
+ const online = await comfyHealth(COMFY_URL);
231
175
  if (!online) {
232
- console.error("\x1b[31mError:\x1b[0m ComfyUI not reachable at " + COMFY_URL);
233
- console.error("Start ComfyUI: cd F:/AI-Models/ComfyUI-runtime && python main.py --listen 127.0.0.1 --port 8188");
234
- process.exit(1);
176
+ throw new Error("ComfyUI not reachable at " + COMFY_URL);
235
177
  }
236
178
  console.log("\x1b[32m✓\x1b[0m ComfyUI online");
237
179
  }
@@ -244,7 +186,7 @@ async function main() {
244
186
  const totalExpected = pack.subjects.length * pack.variations.length;
245
187
 
246
188
  for (const subject of pack.subjects) {
247
- for (const variation of variation_list(pack.variations)) {
189
+ for (const variation of pack.variations) {
248
190
  const assetId = `${subject.id}_${variation.id}`;
249
191
  const seed = (d.base_seed + generated) + (variation.seed_offset || 0);
250
192
  const fullPrompt = `${pack.style_prefix}, ${subject.prompt}`;
@@ -266,7 +208,7 @@ async function main() {
266
208
  );
267
209
 
268
210
  try {
269
- const result = await submitAndWait(nodes);
211
+ const result = await submitAndWait(nodes, COMFY_URL);
270
212
  const elapsed = Date.now() - startMs;
271
213
 
272
214
  // Find output image
@@ -288,7 +230,7 @@ async function main() {
288
230
  }
289
231
 
290
232
  // Download and save
291
- const imgData = await downloadImage(imageFile, imageSubfolder);
233
+ const imgData = await downloadImage(imageFile, imageSubfolder, COMFY_URL);
292
234
  const destPath = `outputs/candidates/${assetId}.png`;
293
235
  await writeFile(join(GAME_ROOT, destPath), imgData);
294
236
 
@@ -341,11 +283,10 @@ async function main() {
341
283
  if (dryRun) console.log(" (dry run — no images produced)");
342
284
  }
343
285
 
344
- function variation_list(variations) {
345
- return variations;
286
+ // Direct execution guard
287
+ if (process.argv[1] && (process.argv[1].endsWith('generate.js') || process.argv[1].endsWith('generate'))) {
288
+ run().catch((err) => {
289
+ console.error(err.message || err);
290
+ process.exit(1);
291
+ });
346
292
  }
347
-
348
- main().catch((err) => {
349
- console.error(err);
350
- process.exit(1);
351
- });