@mcptoolshop/style-dataset-lab 1.2.0 → 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 (57) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.es.md +94 -54
  3. package/README.fr.md +86 -46
  4. package/README.hi.md +86 -46
  5. package/README.it.md +93 -53
  6. package/README.ja.md +86 -46
  7. package/README.md +95 -116
  8. package/README.pt-BR.md +86 -46
  9. package/README.zh.md +90 -50
  10. package/bin/sdlab.js +103 -0
  11. package/lib/args.js +106 -0
  12. package/lib/comfyui.js +109 -0
  13. package/lib/config.js +104 -0
  14. package/lib/deprecation.js +14 -0
  15. package/lib/paths.js +38 -0
  16. package/lib/records.js +69 -0
  17. package/package.json +13 -4
  18. package/scripts/bulk-curate-wave2-5.js +1 -1
  19. package/scripts/bulk-curate-waves11-18.js +1 -1
  20. package/scripts/canon-bind.js +268 -332
  21. package/scripts/compare.js +40 -30
  22. package/scripts/curate-wave25.js +1 -1
  23. package/scripts/curate.js +34 -28
  24. package/scripts/doctor.js +321 -0
  25. package/scripts/generate-controlnet.js +36 -121
  26. package/scripts/generate-identity.js +38 -106
  27. package/scripts/generate-ipadapter.js +42 -119
  28. package/scripts/generate.js +22 -81
  29. package/scripts/init.js +226 -0
  30. package/scripts/migrate-records.js +15 -21
  31. package/scripts/painterly-test.js +20 -47
  32. package/scripts/painterly.js +27 -106
  33. package/templates/domains/architecture/constitution.json +97 -0
  34. package/templates/domains/architecture/lanes.json +41 -0
  35. package/templates/domains/architecture/project.json +20 -0
  36. package/templates/domains/architecture/rubric.json +40 -0
  37. package/templates/domains/architecture/terminology.json +19 -0
  38. package/templates/domains/character-design/constitution.json +88 -0
  39. package/templates/domains/character-design/lanes.json +35 -0
  40. package/templates/domains/character-design/project.json +17 -0
  41. package/templates/domains/character-design/rubric.json +38 -0
  42. package/templates/domains/character-design/terminology.json +21 -0
  43. package/templates/domains/creature-design/constitution.json +87 -0
  44. package/templates/domains/creature-design/lanes.json +41 -0
  45. package/templates/domains/creature-design/project.json +18 -0
  46. package/templates/domains/creature-design/rubric.json +39 -0
  47. package/templates/domains/creature-design/terminology.json +20 -0
  48. package/templates/domains/game-art/constitution.json +113 -0
  49. package/templates/domains/game-art/lanes.json +47 -0
  50. package/templates/domains/game-art/project.json +17 -0
  51. package/templates/domains/game-art/rubric.json +36 -0
  52. package/templates/domains/game-art/terminology.json +30 -0
  53. package/templates/domains/vehicle-mech/constitution.json +87 -0
  54. package/templates/domains/vehicle-mech/lanes.json +41 -0
  55. package/templates/domains/vehicle-mech/project.json +17 -0
  56. package/templates/domains/vehicle-mech/rubric.json +40 -0
  57. package/templates/domains/vehicle-mech/terminology.json +21 -0
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * init.js — Scaffold a new project from a domain template.
5
+ *
6
+ * Usage:
7
+ * sdlab init my-project --domain character-design
8
+ * sdlab init my-project # uses 'generic' domain
9
+ * node scripts/init.js my-project --domain game-art
10
+ */
11
+
12
+ import { mkdir, readdir, readFile, writeFile, copyFile } from 'node:fs/promises';
13
+ import { join, basename } from 'node:path';
14
+ import { existsSync } from 'node:fs';
15
+ import { REPO_ROOT } from '../lib/paths.js';
16
+
17
+ const TEMPLATES_DIR = join(REPO_ROOT, 'templates');
18
+ const PROJECTS_DIR = join(REPO_ROOT, 'projects');
19
+
20
+ const PROJECT_DIRS = [
21
+ 'canon',
22
+ 'records',
23
+ 'comparisons',
24
+ 'inputs/prompts',
25
+ 'inputs/identity-packets',
26
+ 'inputs/references',
27
+ 'inputs/control-guides',
28
+ 'outputs/candidates',
29
+ 'outputs/approved',
30
+ 'outputs/rejected',
31
+ 'outputs/borderline',
32
+ 'outputs/painterly',
33
+ 'outputs/painterly-test',
34
+ 'exports',
35
+ ];
36
+
37
+ function isValidName(name) {
38
+ return /^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(name) || /^[a-z0-9]$/.test(name);
39
+ }
40
+
41
+ async function listDomains() {
42
+ const domainsDir = join(TEMPLATES_DIR, 'domains');
43
+ if (!existsSync(domainsDir)) return [];
44
+ const entries = await readdir(domainsDir, { withFileTypes: true });
45
+ return entries.filter(e => e.isDirectory()).map(e => e.name);
46
+ }
47
+
48
+ export async function run(argv = process.argv.slice(2)) {
49
+ const projectName = argv.find(a => !a.startsWith('--'));
50
+
51
+ // Parse --domain
52
+ let domain = 'generic';
53
+ const domainIdx = argv.indexOf('--domain');
54
+ if (domainIdx >= 0 && argv[domainIdx + 1]) {
55
+ domain = argv[domainIdx + 1];
56
+ }
57
+
58
+ if (!projectName) {
59
+ const domains = await listDomains();
60
+ console.log('Usage: sdlab init <project-name> [--domain <domain>]\n');
61
+ console.log('Available domains:');
62
+ console.log(' generic Minimal starter (default)');
63
+ for (const d of domains) {
64
+ console.log(` ${d.padEnd(20)} Domain-specific starter`);
65
+ }
66
+ return;
67
+ }
68
+
69
+ if (!isValidName(projectName)) {
70
+ throw new Error(
71
+ `Invalid project name "${projectName}".\n` +
72
+ `Use lowercase letters, numbers, and hyphens (e.g. "my-project").`
73
+ );
74
+ }
75
+
76
+ const projectDir = join(PROJECTS_DIR, projectName);
77
+ if (existsSync(projectDir)) {
78
+ throw new Error(`Project "${projectName}" already exists at ${projectDir}`);
79
+ }
80
+
81
+ // Resolve domain template source
82
+ let domainDir = null;
83
+ if (domain !== 'generic') {
84
+ domainDir = join(TEMPLATES_DIR, 'domains', domain);
85
+ if (!existsSync(domainDir)) {
86
+ const domains = await listDomains();
87
+ throw new Error(
88
+ `Domain "${domain}" not found.\n` +
89
+ `Available: generic, ${domains.join(', ')}`
90
+ );
91
+ }
92
+ }
93
+
94
+ console.log(`\x1b[1msdlab init\x1b[0m`);
95
+ console.log(` Project: ${projectName}`);
96
+ console.log(` Domain: ${domain}`);
97
+ console.log(` Path: ${projectDir}`);
98
+ console.log('');
99
+
100
+ // Create directory structure
101
+ for (const dir of PROJECT_DIRS) {
102
+ await mkdir(join(projectDir, dir), { recursive: true });
103
+ }
104
+ console.log(` \x1b[32m✓\x1b[0m Created ${PROJECT_DIRS.length} directories`);
105
+
106
+ // Copy config files from domain template or generate generic ones
107
+ const configFiles = ['project.json', 'constitution.json', 'lanes.json', 'rubric.json', 'terminology.json'];
108
+
109
+ if (domainDir) {
110
+ for (const file of configFiles) {
111
+ const src = join(domainDir, file);
112
+ if (existsSync(src)) {
113
+ let content = await readFile(src, 'utf-8');
114
+ // Replace template project name
115
+ if (file === 'project.json') {
116
+ const parsed = JSON.parse(content);
117
+ parsed.name = projectName;
118
+ content = JSON.stringify(parsed, null, 2) + '\n';
119
+ }
120
+ await writeFile(join(projectDir, file), content);
121
+ console.log(` \x1b[32m✓\x1b[0m ${file} (from ${domain} template)`);
122
+ }
123
+ }
124
+ } else {
125
+ // Generic starters
126
+ await writeFile(join(projectDir, 'project.json'), JSON.stringify({
127
+ name: projectName,
128
+ display_name: projectName.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
129
+ domain: 'generic',
130
+ version: '0.1.0',
131
+ defaults: {
132
+ checkpoint: '',
133
+ loras: [],
134
+ width: 1024,
135
+ height: 1024,
136
+ steps: 8,
137
+ cfg: 2.0,
138
+ sampler: 'dpmpp_sde',
139
+ scheduler: 'karras',
140
+ comfy_url: 'http://127.0.0.1:8188'
141
+ }
142
+ }, null, 2) + '\n');
143
+
144
+ await writeFile(join(projectDir, 'constitution.json'), JSON.stringify({
145
+ version: '0.1.0',
146
+ rules: [
147
+ { id: 'STY-001', category: 'style', desc: 'Consistent visual style across all assets',
148
+ dims: ['style_consistency'], faction_specific: false,
149
+ rationale_pass: 'Style consistency achieved (${scorePct}%)',
150
+ rationale_fail: 'Style inconsistent (${scorePct}%)' },
151
+ { id: 'CMP-001', category: 'composition', desc: 'Clean composition and framing',
152
+ dims: ['composition'], faction_specific: false,
153
+ rationale_pass: 'Composition reads clearly (${scorePct}%)',
154
+ rationale_fail: 'Composition unclear or cluttered (${scorePct}%)' },
155
+ ]
156
+ }, null, 2) + '\n');
157
+
158
+ await writeFile(join(projectDir, 'lanes.json'), JSON.stringify({
159
+ default_lane: 'default',
160
+ lanes: [
161
+ { id: 'default', label: 'Default', description: 'All assets', id_patterns: [] }
162
+ ]
163
+ }, null, 2) + '\n');
164
+
165
+ await writeFile(join(projectDir, 'rubric.json'), JSON.stringify({
166
+ dimensions: ['style_consistency', 'composition', 'detail', 'originality'],
167
+ thresholds: {
168
+ approved: { pass: 0.7, partial: 0.5 },
169
+ borderline: { pass: 0.7, partial: 0.5 },
170
+ rejected: { fail_ceiling: 0.4, partial_ceiling: 0.6 }
171
+ },
172
+ failure_to_rules: {}
173
+ }, null, 2) + '\n');
174
+
175
+ await writeFile(join(projectDir, 'terminology.json'), JSON.stringify({
176
+ group_label: 'group',
177
+ id_detection_order: [],
178
+ prompt_detection_order: [],
179
+ groups: {},
180
+ cross_group_patterns: {},
181
+ edge_defaults: {},
182
+ null_faction_patterns: [],
183
+ id_fallbacks: {},
184
+ faction_context: {}
185
+ }, null, 2) + '\n');
186
+
187
+ for (const file of configFiles) {
188
+ console.log(` \x1b[32m✓\x1b[0m ${file} (generic starter)`);
189
+ }
190
+ }
191
+
192
+ // Copy canon markdown templates
193
+ const canonTemplates = ['constitution.md', 'review-rubric.md'];
194
+ for (const file of canonTemplates) {
195
+ const src = join(TEMPLATES_DIR, 'canon', file);
196
+ if (existsSync(src)) {
197
+ await copyFile(src, join(projectDir, 'canon', file));
198
+ console.log(` \x1b[32m✓\x1b[0m canon/${file}`);
199
+ }
200
+ }
201
+
202
+ // Copy example prompt pack
203
+ const examplePack = join(TEMPLATES_DIR, 'inputs', 'prompts', 'example-wave.json');
204
+ if (existsSync(examplePack)) {
205
+ await copyFile(examplePack, join(projectDir, 'inputs', 'prompts', 'example-wave.json'));
206
+ console.log(` \x1b[32m✓\x1b[0m inputs/prompts/example-wave.json`);
207
+ }
208
+
209
+ console.log('');
210
+ console.log(`\x1b[32m✓\x1b[0m Project "${projectName}" initialized`);
211
+ console.log('');
212
+ console.log('Next steps:');
213
+ console.log(` 1. Edit ${projectName}/canon/constitution.md — define your visual rules`);
214
+ console.log(` 2. Edit ${projectName}/constitution.json — encode rules for canon-bind`);
215
+ console.log(` 3. Create prompt packs in ${projectName}/inputs/prompts/`);
216
+ console.log(` 4. Run: sdlab generate <pack> --project ${projectName}`);
217
+ console.log(` 5. Run: sdlab project doctor --project ${projectName}`);
218
+ }
219
+
220
+ // Direct execution guard
221
+ if (process.argv[1] && (process.argv[1].endsWith('init.js') || process.argv[1].endsWith('init'))) {
222
+ run().catch((err) => {
223
+ console.error(err.message || err);
224
+ process.exit(1);
225
+ });
226
+ }
@@ -12,43 +12,33 @@
12
12
  * Usage:
13
13
  * node scripts/migrate-records.js --dry-run # Preview changes
14
14
  * node scripts/migrate-records.js # Apply changes
15
+ * sdlab project migrate --dry-run --project star-freight
15
16
  */
16
17
 
17
18
  import { readdir, readFile, writeFile, access } from 'node:fs/promises';
18
19
  import { join, basename } from 'node:path';
19
- import { fileURLToPath } from 'node:url';
20
-
21
- const __dirname = new URL('.', import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1');
22
- const REPO_ROOT = join(__dirname, '..');
23
- const GAME = process.argv.find((a, i) => process.argv[i - 1] === '--game') || 'star-freight';
24
- const GAME_ROOT = join(REPO_ROOT, 'games', GAME);
25
- const RECORDS_DIR = join(GAME_ROOT, 'records');
26
- const DRY_RUN = process.argv.includes('--dry-run');
20
+ import { getProjectName } from '../lib/args.js';
21
+ import { REPO_ROOT } from '../lib/paths.js';
27
22
 
28
23
  async function fileExists(p) {
29
24
  try { await access(p); return true; } catch { return false; }
30
25
  }
31
26
 
32
27
  function isCuratorDivergent(rec) {
33
- // Shape 2/4: has verdict at top level, judgment is null
34
28
  return rec.judgment === null && typeof rec.verdict === 'string' && !rec.identity;
35
29
  }
36
30
 
37
31
  function isIdentityRecord(rec) {
38
- // Shape 3: has identity block — Grounded pipeline, DO NOT TOUCH
39
32
  return rec.identity != null;
40
33
  }
41
34
 
42
- async function migrateRecord(filePath) {
35
+ async function migrateRecord(filePath, GAME_ROOT, DRY_RUN) {
43
36
  const raw = await readFile(filePath, 'utf-8');
44
37
  const rec = JSON.parse(raw);
45
38
 
46
- // Skip identity records entirely — Grounded pipeline
47
39
  if (isIdentityRecord(rec)) return { file: basename(filePath), action: 'skip-identity' };
48
40
 
49
- // Skip records that are already in standard shape
50
41
  if (!isCuratorDivergent(rec)) {
51
- // Still check for stale asset_path on standard records
52
42
  if (rec.asset_path && rec.asset_path.includes('outputs/candidates/')) {
53
43
  const approvedPath = rec.asset_path.replace('outputs/candidates/', 'outputs/approved/');
54
44
  const fullApproved = join(GAME_ROOT, approvedPath);
@@ -61,10 +51,8 @@ async function migrateRecord(filePath) {
61
51
  return { file: basename(filePath), action: 'skip-standard' };
62
52
  }
63
53
 
64
- // Migrate curator-divergent record
65
54
  const changes = [];
66
55
 
67
- // Build judgment object from top-level fields
68
56
  rec.judgment = {
69
57
  status: rec.verdict || 'unknown',
70
58
  reviewer: rec.curator || 'wave25-script',
@@ -77,7 +65,6 @@ async function migrateRecord(filePath) {
77
65
  };
78
66
  changes.push(`verdict="${rec.verdict}" → judgment.status`);
79
67
 
80
- // Remove top-level divergent fields
81
68
  delete rec.verdict;
82
69
  delete rec.scores;
83
70
  delete rec.explanation;
@@ -86,7 +73,6 @@ async function migrateRecord(filePath) {
86
73
  delete rec.curator;
87
74
  changes.push('removed top-level verdict/scores/explanation/failures/curated_at/curator');
88
75
 
89
- // Fix asset_path if image moved to approved/
90
76
  if (rec.asset_path && rec.asset_path.includes('outputs/candidates/')) {
91
77
  const approvedPath = rec.asset_path.replace('outputs/candidates/', 'outputs/approved/');
92
78
  const fullApproved = join(GAME_ROOT, approvedPath);
@@ -105,7 +91,12 @@ async function migrateRecord(filePath) {
105
91
  return { file: basename(filePath), action: 'migrated', changes };
106
92
  }
107
93
 
108
- async function main() {
94
+ export async function run(argv = process.argv.slice(2)) {
95
+ const projectName = getProjectName(argv);
96
+ const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
97
+ const RECORDS_DIR = join(GAME_ROOT, 'records');
98
+ const DRY_RUN = argv.includes('--dry-run');
99
+
109
100
  console.log(DRY_RUN ? '=== DRY RUN ===' : '=== APPLYING MIGRATION ===');
110
101
 
111
102
  const files = (await readdir(RECORDS_DIR)).filter(f => f.endsWith('.json'));
@@ -115,7 +106,7 @@ async function main() {
115
106
 
116
107
  for (const file of files) {
117
108
  try {
118
- const result = await migrateRecord(join(RECORDS_DIR, file));
109
+ const result = await migrateRecord(join(RECORDS_DIR, file), GAME_ROOT, DRY_RUN);
119
110
  switch (result.action) {
120
111
  case 'skip-standard': results.skipped++; break;
121
112
  case 'skip-identity': results.identity++; break;
@@ -144,4 +135,7 @@ async function main() {
144
135
  console.log(`Total: ${files.length}`);
145
136
  }
146
137
 
147
- main().catch(err => { console.error(err); process.exit(1); });
138
+ // Direct execution guard
139
+ if (process.argv[1] && (process.argv[1].endsWith('migrate-records.js') || process.argv[1].endsWith('migrate-records'))) {
140
+ run().catch(err => { console.error(err.message || err); process.exit(1); });
141
+ }
@@ -3,15 +3,17 @@
3
3
  /**
4
4
  * painterly-test.js — Test different denoise levels on a single image.
5
5
  * Outputs: outputs/painterly-test/<filename>_d<denoise>.png
6
+ *
7
+ * Usage:
8
+ * node scripts/painterly-test.js
9
+ * sdlab painterly:test --project star-freight
6
10
  */
7
11
 
8
- import { readFile, writeFile, mkdir } from "node:fs/promises";
12
+ import { writeFile, mkdir } from "node:fs/promises";
9
13
  import { join } from "node:path";
10
-
11
- const COMFY_URL = process.env.COMFY_URL || "http://127.0.0.1:8188";
12
- const REPO_ROOT = new URL("..", import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1");
13
- const GAME = process.argv.find((a, i) => process.argv[i - 1] === '--game') || 'star-freight';
14
- const GAME_ROOT = join(REPO_ROOT, 'games', GAME);
14
+ import { getProjectName } from "../lib/args.js";
15
+ import { REPO_ROOT } from "../lib/paths.js";
16
+ import { submitAndWait, downloadImage, uploadImage } from "../lib/comfyui.js";
15
17
 
16
18
  const PAINTERLY_PROMPT = [
17
19
  "oil painting, visible brushstrokes, painterly concept art,",
@@ -68,43 +70,11 @@ function buildWorkflow(imagePath, denoise, loraWeight, seed) {
68
70
  return nodes;
69
71
  }
70
72
 
71
- async function upload(filePath, filename) {
72
- const data = await readFile(filePath);
73
- const form = new FormData();
74
- form.append("image", new Blob([data], { type: "image/png" }), filename);
75
- form.append("overwrite", "true");
76
- const res = await fetch(`${COMFY_URL}/upload/image`, { method: "POST", body: form });
77
- if (!res.ok) throw new Error(`Upload failed: ${res.status} ${await res.text()}`);
78
- return (await res.json()).name;
79
- }
80
-
81
- const MAX_POLL_MS = 600_000; // 10 minutes
82
-
83
- async function run(workflow) {
84
- const res = await fetch(`${COMFY_URL}/prompt`, {
85
- method: "POST", headers: { "Content-Type": "application/json" },
86
- body: JSON.stringify({ prompt: workflow, client_id: `test-${Date.now()}` }),
87
- });
88
- if (!res.ok) throw new Error(`ComfyUI submit failed: ${res.status} ${await res.text()}`);
89
- const { prompt_id: promptId } = await res.json();
90
- const start = Date.now();
91
- while (true) {
92
- if (Date.now() - start > MAX_POLL_MS) throw new Error(`ComfyUI poll timeout after ${MAX_POLL_MS/1000}s for prompt ${promptId}`);
93
- await new Promise(r => setTimeout(r, 1000));
94
- const histRes = await fetch(`${COMFY_URL}/history/${promptId}`);
95
- if (!histRes.ok) continue;
96
- const h = await histRes.json();
97
- if (h[promptId]?.status?.completed) return h[promptId];
98
- }
99
- }
100
-
101
- async function download(filename, subfolder) {
102
- const res = await fetch(`${COMFY_URL}/view?filename=${encodeURIComponent(filename)}&subfolder=${encodeURIComponent(subfolder || "")}&type=output`);
103
- if (!res.ok) throw new Error(`Download failed: ${res.status}`);
104
- return Buffer.from(await res.arrayBuffer());
105
- }
73
+ export async function run(argv = process.argv.slice(2)) {
74
+ const projectName = getProjectName(argv);
75
+ const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
76
+ const COMFY_URL = process.env.COMFY_URL || "http://127.0.0.1:8188";
106
77
 
107
- async function main() {
108
78
  const outDir = join(GAME_ROOT, "outputs/painterly-test");
109
79
  await mkdir(outDir, { recursive: true });
110
80
 
@@ -115,14 +85,14 @@ async function main() {
115
85
  ];
116
86
 
117
87
  const denoiseTests = [0.50, 0.60, 0.70];
118
- const loraWeight = 1.0; // Full LoRA weight for stronger effect
88
+ const loraWeight = 1.0;
119
89
 
120
90
  for (const testFile of testFiles) {
121
91
  const srcPath = join(GAME_ROOT, testFile);
122
92
  const fname = testFile.split("/").pop().replace(".png", "");
123
93
 
124
94
  console.log(`\nTesting: ${fname}`);
125
- const uploaded = await upload(srcPath, `test_${fname}.png`);
95
+ const uploaded = await uploadImage(srcPath, `test_${fname}.png`, COMFY_URL);
126
96
 
127
97
  for (const denoise of denoiseTests) {
128
98
  const label = `${fname}_d${String(denoise).replace(".", "")}`;
@@ -130,14 +100,14 @@ async function main() {
130
100
 
131
101
  const t = Date.now();
132
102
  const wf = buildWorkflow(uploaded, denoise, loraWeight, 42);
133
- const result = await run(wf);
103
+ const result = await submitAndWait(wf, COMFY_URL, { clientPrefix: 'test' });
134
104
 
135
105
  let imgFile, imgSub = "";
136
106
  for (const o of Object.values(result.outputs || {})) {
137
107
  if (o.images?.length) { imgFile = o.images[0].filename; imgSub = o.images[0].subfolder || ""; break; }
138
108
  }
139
109
 
140
- const data = await download(imgFile, imgSub);
110
+ const data = await downloadImage(imgFile, imgSub, COMFY_URL);
141
111
  await writeFile(join(outDir, `${label}.png`), data);
142
112
  console.log(` ✓ ${Date.now() - t}ms`);
143
113
  }
@@ -146,4 +116,7 @@ async function main() {
146
116
  console.log("\nDone — check outputs/painterly-test/");
147
117
  }
148
118
 
149
- main().catch(e => { console.error(e); process.exit(1); });
119
+ // Direct execution guard
120
+ if (process.argv[1] && (process.argv[1].endsWith('painterly-test.js') || process.argv[1].endsWith('painterly-test'))) {
121
+ run().catch(e => { console.error(e.message || e); process.exit(1); });
122
+ }