@genart-dev/mcp-server 0.3.0 → 0.4.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/dist/index.cjs +2045 -227
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2051 -227
- package/dist/index.js.map +1 -1
- package/dist/lib.cjs +2074 -256
- package/dist/lib.cjs.map +1 -1
- package/dist/lib.js +2076 -252
- package/dist/lib.js.map +1 -1
- package/package.json +19 -4
package/dist/lib.cjs
CHANGED
|
@@ -38,11 +38,25 @@ module.exports = __toCommonJS(lib_exports);
|
|
|
38
38
|
// src/server.ts
|
|
39
39
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
40
40
|
var import_zod2 = require("zod");
|
|
41
|
-
var
|
|
41
|
+
var import_core14 = require("@genart-dev/core");
|
|
42
42
|
var import_plugin_typography = __toESM(require("@genart-dev/plugin-typography"), 1);
|
|
43
43
|
var import_plugin_filters = __toESM(require("@genart-dev/plugin-filters"), 1);
|
|
44
44
|
var import_plugin_shapes = __toESM(require("@genart-dev/plugin-shapes"), 1);
|
|
45
45
|
var import_plugin_layout_guides = __toESM(require("@genart-dev/plugin-layout-guides"), 1);
|
|
46
|
+
var import_plugin_painting = __toESM(require("@genart-dev/plugin-painting"), 1);
|
|
47
|
+
var import_plugin_textures = __toESM(require("@genart-dev/plugin-textures"), 1);
|
|
48
|
+
var import_plugin_animation = __toESM(require("@genart-dev/plugin-animation"), 1);
|
|
49
|
+
var import_plugin_color_adjust = __toESM(require("@genart-dev/plugin-color-adjust"), 1);
|
|
50
|
+
var import_plugin_compositing = __toESM(require("@genart-dev/plugin-compositing"), 1);
|
|
51
|
+
var import_plugin_construction = __toESM(require("@genart-dev/plugin-construction"), 1);
|
|
52
|
+
var import_plugin_distribution = __toESM(require("@genart-dev/plugin-distribution"), 1);
|
|
53
|
+
var import_plugin_figure = __toESM(require("@genart-dev/plugin-figure"), 1);
|
|
54
|
+
var import_plugin_layout_composition = __toESM(require("@genart-dev/plugin-layout-composition"), 1);
|
|
55
|
+
var import_plugin_perspective = __toESM(require("@genart-dev/plugin-perspective"), 1);
|
|
56
|
+
var import_plugin_poses = __toESM(require("@genart-dev/plugin-poses"), 1);
|
|
57
|
+
var import_plugin_styles = __toESM(require("@genart-dev/plugin-styles"), 1);
|
|
58
|
+
var import_plugin_symbols = __toESM(require("@genart-dev/plugin-symbols"), 1);
|
|
59
|
+
var import_plugin_trace = __toESM(require("@genart-dev/plugin-trace"), 1);
|
|
46
60
|
|
|
47
61
|
// src/tools/workspace.ts
|
|
48
62
|
var import_promises = require("fs/promises");
|
|
@@ -471,8 +485,8 @@ async function createSketch(state, input) {
|
|
|
471
485
|
}
|
|
472
486
|
let algorithm = input.algorithm;
|
|
473
487
|
if (!algorithm) {
|
|
474
|
-
const
|
|
475
|
-
const adapter =
|
|
488
|
+
const registry5 = (0, import_core2.createDefaultRegistry)();
|
|
489
|
+
const adapter = registry5.resolve(rendererType);
|
|
476
490
|
algorithm = adapter.getAlgorithmTemplate();
|
|
477
491
|
}
|
|
478
492
|
let resolvedComponents;
|
|
@@ -697,8 +711,8 @@ async function updateAlgorithm(state, input) {
|
|
|
697
711
|
const shouldValidate = input.validate !== false;
|
|
698
712
|
let validationPassed = true;
|
|
699
713
|
if (shouldValidate) {
|
|
700
|
-
const
|
|
701
|
-
const adapter =
|
|
714
|
+
const registry5 = (0, import_core2.createDefaultRegistry)();
|
|
715
|
+
const adapter = registry5.resolve(def.renderer.type);
|
|
702
716
|
const result = adapter.validate(input.algorithm);
|
|
703
717
|
if (!result.valid) {
|
|
704
718
|
throw new Error(
|
|
@@ -821,6 +835,12 @@ async function forkSketch(state, input) {
|
|
|
821
835
|
const seed = generateNewSeed ? Math.floor(Math.random() * 1e5) : sourceDef.state.seed;
|
|
822
836
|
const title = input.title ?? `${sourceDef.title} (fork)`;
|
|
823
837
|
const ts = now2();
|
|
838
|
+
const sourceGeneration = sourceDef.lineage?.generation ?? 1;
|
|
839
|
+
const lineage = {
|
|
840
|
+
parentId: input.sourceId,
|
|
841
|
+
parentTitle: sourceDef.title,
|
|
842
|
+
generation: sourceGeneration + 1
|
|
843
|
+
};
|
|
824
844
|
const forkedDef = {
|
|
825
845
|
genart: "1.1",
|
|
826
846
|
id: input.newId,
|
|
@@ -833,6 +853,8 @@ async function forkSketch(state, input) {
|
|
|
833
853
|
colors,
|
|
834
854
|
state: buildState(parameters, colors, seed),
|
|
835
855
|
algorithm,
|
|
856
|
+
...sourceDef.compositionLevel ? { compositionLevel: sourceDef.compositionLevel } : {},
|
|
857
|
+
lineage,
|
|
836
858
|
...philosophy ? { philosophy } : {},
|
|
837
859
|
...sourceDef.themes ? { themes: [...sourceDef.themes] } : {},
|
|
838
860
|
...sourceDef.skills ? { skills: [...sourceDef.skills] } : {},
|
|
@@ -1794,6 +1816,13 @@ ${s.philosophy}`);
|
|
|
1794
1816
|
params[p.key] = p.default;
|
|
1795
1817
|
}
|
|
1796
1818
|
const colorPalette = colors.map((c) => c.default);
|
|
1819
|
+
const maxGeneration = Math.max(
|
|
1820
|
+
...sources.map((s) => s.lineage?.generation ?? 1)
|
|
1821
|
+
);
|
|
1822
|
+
const lineage = {
|
|
1823
|
+
blendSources: input.sourceIds,
|
|
1824
|
+
generation: maxGeneration + 1
|
|
1825
|
+
};
|
|
1797
1826
|
const timestamp = now5();
|
|
1798
1827
|
const newDef = {
|
|
1799
1828
|
genart: "1.1",
|
|
@@ -1802,6 +1831,7 @@ ${s.philosophy}`);
|
|
|
1802
1831
|
created: timestamp,
|
|
1803
1832
|
modified: timestamp,
|
|
1804
1833
|
...skills.length > 0 ? { skills } : {},
|
|
1834
|
+
lineage,
|
|
1805
1835
|
renderer: { type: renderer, version: sources[0].renderer.version },
|
|
1806
1836
|
canvas: { width: canvasWidth, height: canvasHeight },
|
|
1807
1837
|
...philosophy ? { philosophy } : {},
|
|
@@ -2028,14 +2058,25 @@ async function getGuidelines(input) {
|
|
|
2028
2058
|
color: "color",
|
|
2029
2059
|
colours: "color",
|
|
2030
2060
|
layout: "composition",
|
|
2031
|
-
palette: "color"
|
|
2061
|
+
palette: "color",
|
|
2062
|
+
painting: "painting",
|
|
2063
|
+
watercolor: "painting",
|
|
2064
|
+
ink: "illustration",
|
|
2065
|
+
illustration: "illustration",
|
|
2066
|
+
"mixed-media": "illustration",
|
|
2067
|
+
"mixed media": "illustration",
|
|
2068
|
+
process: "process",
|
|
2069
|
+
layering: "process",
|
|
2070
|
+
"mark-making": "process",
|
|
2071
|
+
refinement: "process",
|
|
2072
|
+
constraints: "process"
|
|
2032
2073
|
};
|
|
2033
2074
|
const category = categoryMap[topic];
|
|
2034
2075
|
if (!category) {
|
|
2035
2076
|
return {
|
|
2036
2077
|
success: false,
|
|
2037
2078
|
topic,
|
|
2038
|
-
error: `No guidelines found for topic: '${topic}'. Available topics: composition, color, parameters, animation, performance`,
|
|
2079
|
+
error: `No guidelines found for topic: '${topic}'. Available topics: composition, color, painting, illustration, process, parameters, animation, performance`,
|
|
2039
2080
|
guidelines: null,
|
|
2040
2081
|
relatedSkills: []
|
|
2041
2082
|
};
|
|
@@ -2062,6 +2103,138 @@ ${guidelines}`,
|
|
|
2062
2103
|
}))
|
|
2063
2104
|
};
|
|
2064
2105
|
}
|
|
2106
|
+
var CONTEXT_KEYWORDS = {
|
|
2107
|
+
// Composition keywords
|
|
2108
|
+
layout: ["golden-ratio", "rule-of-thirds", "visual-weight", "gestalt-grouping"],
|
|
2109
|
+
balance: ["visual-weight", "golden-ratio", "rule-of-thirds"],
|
|
2110
|
+
grid: ["rule-of-thirds", "gestalt-grouping", "rhythm-movement"],
|
|
2111
|
+
flow: ["rhythm-movement", "gestalt-grouping"],
|
|
2112
|
+
movement: ["rhythm-movement", "mark-making"],
|
|
2113
|
+
rhythm: ["rhythm-movement", "mark-making"],
|
|
2114
|
+
focal: ["rule-of-thirds", "visual-weight", "figure-ground"],
|
|
2115
|
+
negative: ["figure-ground", "visual-weight"],
|
|
2116
|
+
space: ["figure-ground", "visual-weight", "atmospheric-depth"],
|
|
2117
|
+
// Color keywords
|
|
2118
|
+
palette: ["color-harmony", "palette-generation", "color-mixing-strategy"],
|
|
2119
|
+
color: ["color-harmony", "color-temperature", "itten-contrasts", "color-mixing-strategy"],
|
|
2120
|
+
warm: ["color-temperature", "atmospheric-depth"],
|
|
2121
|
+
cool: ["color-temperature", "atmospheric-depth"],
|
|
2122
|
+
contrast: ["simultaneous-contrast", "itten-contrasts", "value-structure"],
|
|
2123
|
+
value: ["value-structure", "itten-contrasts", "layering-strategy"],
|
|
2124
|
+
gray: ["color-mixing-strategy", "value-structure"],
|
|
2125
|
+
// Painting keywords
|
|
2126
|
+
watercolor: ["watercolor-techniques", "layering-strategy", "material-behavior"],
|
|
2127
|
+
ink: ["ink-illustration", "mark-making", "material-behavior"],
|
|
2128
|
+
oil: ["painting-foundations", "layering-strategy", "material-behavior"],
|
|
2129
|
+
charcoal: ["material-behavior", "mark-making"],
|
|
2130
|
+
brush: ["mark-making", "material-behavior"],
|
|
2131
|
+
layer: ["layering-strategy", "mixed-media-workflow", "iterative-refinement"],
|
|
2132
|
+
texture: ["material-behavior", "mark-making"],
|
|
2133
|
+
// Process keywords
|
|
2134
|
+
study: ["thumbnail-studies", "creative-constraints", "iterative-refinement"],
|
|
2135
|
+
thumbnail: ["thumbnail-studies", "creative-constraints"],
|
|
2136
|
+
refine: ["iterative-refinement", "layering-strategy"],
|
|
2137
|
+
iterate: ["iterative-refinement", "thumbnail-studies"],
|
|
2138
|
+
depth: ["atmospheric-depth", "color-temperature", "value-structure"],
|
|
2139
|
+
atmosphere: ["atmospheric-depth", "color-temperature"],
|
|
2140
|
+
perspective: ["atmospheric-depth"],
|
|
2141
|
+
constraint: ["creative-constraints"],
|
|
2142
|
+
limit: ["creative-constraints", "color-mixing-strategy"],
|
|
2143
|
+
hatch: ["mark-making", "ink-illustration"],
|
|
2144
|
+
stipple: ["mark-making"],
|
|
2145
|
+
gestural: ["mark-making", "iterative-refinement"],
|
|
2146
|
+
mix: ["color-mixing-strategy", "mixed-media-workflow"],
|
|
2147
|
+
glaze: ["layering-strategy", "material-behavior"]
|
|
2148
|
+
};
|
|
2149
|
+
async function suggestSkills(state, input) {
|
|
2150
|
+
const allSkills = registry.list();
|
|
2151
|
+
const scored = /* @__PURE__ */ new Map();
|
|
2152
|
+
for (const skill of allSkills) {
|
|
2153
|
+
scored.set(skill.id, { score: 0, reasons: [] });
|
|
2154
|
+
}
|
|
2155
|
+
if (input.sketchId) {
|
|
2156
|
+
const loaded = state.getSketch(input.sketchId);
|
|
2157
|
+
if (loaded) {
|
|
2158
|
+
const sketch = loaded.definition;
|
|
2159
|
+
const usedSkills = new Set(sketch.skills ?? []);
|
|
2160
|
+
for (const skill of allSkills) {
|
|
2161
|
+
if (!usedSkills.has(skill.id)) {
|
|
2162
|
+
const entry = scored.get(skill.id);
|
|
2163
|
+
entry.score += 1;
|
|
2164
|
+
entry.reasons.push("not yet used in this sketch");
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
const level = sketch.compositionLevel;
|
|
2168
|
+
if (level) {
|
|
2169
|
+
const levelSkills = {
|
|
2170
|
+
study: ["thumbnail-studies", "creative-constraints", "iterative-refinement"],
|
|
2171
|
+
sketch: ["iterative-refinement", "mark-making", "layering-strategy", "color-mixing-strategy"],
|
|
2172
|
+
developed: ["layering-strategy", "material-behavior", "atmospheric-depth", "color-mixing-strategy"],
|
|
2173
|
+
exhibition: ["layering-strategy", "material-behavior", "atmospheric-depth", "iterative-refinement", "mark-making"]
|
|
2174
|
+
};
|
|
2175
|
+
for (const id of levelSkills[level] ?? []) {
|
|
2176
|
+
const entry = scored.get(id);
|
|
2177
|
+
if (entry) {
|
|
2178
|
+
entry.score += 3;
|
|
2179
|
+
entry.reasons.push(`recommended for ${level}-level work`);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
if (sketch.layers && sketch.layers.length > 0) {
|
|
2184
|
+
const layerTypes = sketch.layers.map((l) => l.type);
|
|
2185
|
+
if (layerTypes.some((t) => t.startsWith("painting:"))) {
|
|
2186
|
+
for (const id of ["layering-strategy", "material-behavior", "iterative-refinement"]) {
|
|
2187
|
+
const entry = scored.get(id);
|
|
2188
|
+
if (entry) {
|
|
2189
|
+
entry.score += 2;
|
|
2190
|
+
entry.reasons.push("sketch uses painting layers");
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
if (input.context) {
|
|
2198
|
+
const words = input.context.toLowerCase().split(/\W+/);
|
|
2199
|
+
for (const word of words) {
|
|
2200
|
+
const matched = CONTEXT_KEYWORDS[word];
|
|
2201
|
+
if (matched) {
|
|
2202
|
+
for (const skillId of matched) {
|
|
2203
|
+
const entry = scored.get(skillId);
|
|
2204
|
+
if (entry) {
|
|
2205
|
+
entry.score += 2;
|
|
2206
|
+
if (!entry.reasons.includes(`matches context keyword "${word}"`)) {
|
|
2207
|
+
entry.reasons.push(`matches context keyword "${word}"`);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
if (!input.sketchId && !input.context) {
|
|
2215
|
+
for (const skill of allSkills) {
|
|
2216
|
+
if (skill.category === "process") {
|
|
2217
|
+
const entry = scored.get(skill.id);
|
|
2218
|
+
entry.score += 2;
|
|
2219
|
+
entry.reasons.push("process knowledge is broadly applicable");
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
const ranked = allSkills.map((skill) => ({
|
|
2224
|
+
id: skill.id,
|
|
2225
|
+
name: skill.name,
|
|
2226
|
+
category: skill.category,
|
|
2227
|
+
complexity: skill.complexity,
|
|
2228
|
+
description: skill.description,
|
|
2229
|
+
relevanceScore: scored.get(skill.id).score,
|
|
2230
|
+
rationale: scored.get(skill.id).reasons
|
|
2231
|
+
})).filter((s) => s.relevanceScore > 0).sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, 5);
|
|
2232
|
+
return {
|
|
2233
|
+
success: true,
|
|
2234
|
+
suggestions: ranked,
|
|
2235
|
+
total: ranked.length
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2065
2238
|
|
|
2066
2239
|
// src/tools/components.ts
|
|
2067
2240
|
var import_promises5 = require("fs/promises");
|
|
@@ -2315,7 +2488,7 @@ async function captureHtml(options) {
|
|
|
2315
2488
|
try {
|
|
2316
2489
|
await page.setViewport({ width, height, deviceScaleFactor: 1 });
|
|
2317
2490
|
await page.setContent(html, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
2318
|
-
await new Promise((
|
|
2491
|
+
await new Promise((resolve5) => setTimeout(resolve5, waitMs));
|
|
2319
2492
|
const buffer = await page.screenshot({
|
|
2320
2493
|
type: imageType,
|
|
2321
2494
|
clip: { x: 0, y: 0, width, height },
|
|
@@ -2335,7 +2508,7 @@ async function captureHtmlMulti(options) {
|
|
|
2335
2508
|
try {
|
|
2336
2509
|
await page.setViewport({ width, height, deviceScaleFactor: 1 });
|
|
2337
2510
|
await page.setContent(html, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
2338
|
-
await new Promise((
|
|
2511
|
+
await new Promise((resolve5) => setTimeout(resolve5, waitMs));
|
|
2339
2512
|
const pngBuffer = await page.screenshot({
|
|
2340
2513
|
type: "png",
|
|
2341
2514
|
clip: { x: 0, y: 0, width, height }
|
|
@@ -2345,7 +2518,7 @@ async function captureHtmlMulti(options) {
|
|
|
2345
2518
|
const inlineWidth = Math.round(width * scale);
|
|
2346
2519
|
const inlineHeight = Math.round(height * scale);
|
|
2347
2520
|
await page.setViewport({ width: inlineWidth, height: inlineHeight, deviceScaleFactor: 1 });
|
|
2348
|
-
await new Promise((
|
|
2521
|
+
await new Promise((resolve5) => setTimeout(resolve5, 100));
|
|
2349
2522
|
const jpegBuffer = await page.screenshot({
|
|
2350
2523
|
type: "jpeg",
|
|
2351
2524
|
quality: jpegQuality,
|
|
@@ -2506,215 +2679,1274 @@ async function captureBatch(state, input) {
|
|
|
2506
2679
|
};
|
|
2507
2680
|
}
|
|
2508
2681
|
|
|
2509
|
-
// src/tools/
|
|
2510
|
-
var import_fs = require("fs");
|
|
2511
|
-
var import_promises7 = require("fs/promises");
|
|
2512
|
-
var import_path8 = require("path");
|
|
2513
|
-
var import_archiver = __toESM(require("archiver"), 1);
|
|
2682
|
+
// src/tools/critique.ts
|
|
2514
2683
|
var import_core9 = require("@genart-dev/core");
|
|
2515
|
-
var registry3 = (0, import_core9.
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2684
|
+
var registry3 = (0, import_core9.createDefaultSkillRegistry)();
|
|
2685
|
+
var ALL_ASPECTS = [
|
|
2686
|
+
"composition",
|
|
2687
|
+
"color",
|
|
2688
|
+
"rhythm",
|
|
2689
|
+
"unity",
|
|
2690
|
+
"expression"
|
|
2691
|
+
];
|
|
2692
|
+
var SEVERITY = {
|
|
2693
|
+
study: {
|
|
2694
|
+
level: "study",
|
|
2695
|
+
description: "Fast, exploratory \u2014 value the energy of discovery over polish",
|
|
2696
|
+
focus: "Is the core idea visible? Does the sketch capture a single insight?",
|
|
2697
|
+
tolerance: "High tolerance for roughness, imbalance, and incomplete resolution. Studies should feel alive, not finished."
|
|
2698
|
+
},
|
|
2699
|
+
sketch: {
|
|
2700
|
+
level: "sketch",
|
|
2701
|
+
description: "Intentional but rough \u2014 the idea should read clearly",
|
|
2702
|
+
focus: "Do composition and color serve the concept? Are parameters well-chosen?",
|
|
2703
|
+
tolerance: "Moderate tolerance. Unresolved edges and raw marks are fine, but the structure should be deliberate."
|
|
2704
|
+
},
|
|
2705
|
+
developed: {
|
|
2706
|
+
level: "developed",
|
|
2707
|
+
description: "Refined \u2014 every major decision should be justified",
|
|
2708
|
+
focus: "Do all elements work together? Is there a clear visual hierarchy? Does the palette feel cohesive?",
|
|
2709
|
+
tolerance: "Low tolerance for accidental imbalance. Rough areas should be intentional, not neglected."
|
|
2710
|
+
},
|
|
2711
|
+
exhibition: {
|
|
2712
|
+
level: "exhibition",
|
|
2713
|
+
description: "Polished \u2014 every element earns its place",
|
|
2714
|
+
focus: "Could you defend every choice? Does the piece hold up under sustained viewing? Is the concept fully realized?",
|
|
2715
|
+
tolerance: "Minimal tolerance. Each mark, color, and spatial relationship should feel inevitable."
|
|
2534
2716
|
}
|
|
2717
|
+
};
|
|
2718
|
+
function buildCompositionFramework() {
|
|
2719
|
+
return {
|
|
2720
|
+
aspect: "composition",
|
|
2721
|
+
questions: [
|
|
2722
|
+
"Where does the eye land first? Is that the intended focal point?",
|
|
2723
|
+
"Is there a clear visual hierarchy (primary, secondary, tertiary)?",
|
|
2724
|
+
"How does the composition use the edges and corners of the canvas?",
|
|
2725
|
+
"Is negative space working actively or is it leftover?",
|
|
2726
|
+
"Does the arrangement feel balanced or intentionally unbalanced?"
|
|
2727
|
+
],
|
|
2728
|
+
principles: [
|
|
2729
|
+
"Visual weight distribution \u2014 dense, dark, saturated, or detailed areas carry more weight",
|
|
2730
|
+
"Entry points and eye paths \u2014 the viewer needs a way in and a journey through the piece",
|
|
2731
|
+
"Edge tension \u2014 elements near canvas edges create tension; use this deliberately",
|
|
2732
|
+
"Rule of thirds / golden ratio \u2014 useful starting points, not rigid rules",
|
|
2733
|
+
"Figure-ground clarity \u2014 the relationship between positive and negative space"
|
|
2734
|
+
],
|
|
2735
|
+
pitfalls: [
|
|
2736
|
+
"Centering everything \u2014 creates static compositions unless intentionally symmetrical",
|
|
2737
|
+
"Filling the canvas uniformly \u2014 denies the viewer rest areas and focal emphasis",
|
|
2738
|
+
"Tangent lines \u2014 elements barely touching edges or each other create visual discomfort",
|
|
2739
|
+
"Competing focal points \u2014 multiple areas of equal emphasis confuse the eye",
|
|
2740
|
+
"Ignoring the canvas aspect ratio \u2014 composition should respond to the format"
|
|
2741
|
+
]
|
|
2742
|
+
};
|
|
2535
2743
|
}
|
|
2536
|
-
function
|
|
2537
|
-
return
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2744
|
+
function buildColorFramework() {
|
|
2745
|
+
return {
|
|
2746
|
+
aspect: "color",
|
|
2747
|
+
questions: [
|
|
2748
|
+
"Does the palette feel intentional or arbitrary?",
|
|
2749
|
+
"Is there a dominant color temperature (warm/cool) or a deliberate tension between them?",
|
|
2750
|
+
"How many distinct hues are active? Is that number serving the concept?",
|
|
2751
|
+
"Are value contrasts (light/dark) creating readable structure?",
|
|
2752
|
+
"Do any colors feel out of place \u2014 or is dissonance intentional?"
|
|
2753
|
+
],
|
|
2754
|
+
principles: [
|
|
2755
|
+
"Color harmony \u2014 analogous, complementary, triadic, or split-complementary relationships",
|
|
2756
|
+
"Value structure \u2014 squint at the piece; the composition should read in grayscale",
|
|
2757
|
+
"Temperature as depth \u2014 warm advances, cool recedes (atmospheric perspective)",
|
|
2758
|
+
"Saturation as emphasis \u2014 high saturation draws the eye; use it sparingly for focus",
|
|
2759
|
+
"Color proportion \u2014 unequal amounts create interest (e.g., 60-30-10 ratio)"
|
|
2760
|
+
],
|
|
2761
|
+
pitfalls: [
|
|
2762
|
+
"Too many fully saturated colors competing for attention",
|
|
2763
|
+
"No value range \u2014 all mid-tones flatten the piece",
|
|
2764
|
+
"Random color assignment \u2014 palette should derive from concept, not just randomness",
|
|
2765
|
+
"Ignoring simultaneous contrast \u2014 adjacent colors alter each other's appearance",
|
|
2766
|
+
"Uniform opacity everywhere \u2014 varying transparency adds depth and atmosphere"
|
|
2767
|
+
]
|
|
2547
2768
|
};
|
|
2548
|
-
return { ...sketch, state: newState };
|
|
2549
2769
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
default:
|
|
2576
|
-
throw new Error(`Unsupported export format: '${input.format}'`);
|
|
2577
|
-
}
|
|
2770
|
+
function buildRhythmFramework() {
|
|
2771
|
+
return {
|
|
2772
|
+
aspect: "rhythm",
|
|
2773
|
+
questions: [
|
|
2774
|
+
"Is there a repeating visual motif or interval?",
|
|
2775
|
+
"Does the rhythm accelerate, decelerate, or remain steady?",
|
|
2776
|
+
"Are there moments of syncopation \u2014 unexpected breaks in the pattern?",
|
|
2777
|
+
"Does the rhythm contribute to or fight against the composition?",
|
|
2778
|
+
"Is there scale variation \u2014 does the motif appear at multiple sizes?"
|
|
2779
|
+
],
|
|
2780
|
+
principles: [
|
|
2781
|
+
"Regular rhythm creates calm and order; irregular rhythm creates energy",
|
|
2782
|
+
"Progressive rhythm (gradual change) creates movement and depth",
|
|
2783
|
+
"Alternating rhythm adds complexity without chaos",
|
|
2784
|
+
"Rhythm at multiple scales (fractal repetition) creates richness",
|
|
2785
|
+
"Silence (empty intervals) is as important as sound (marked intervals)"
|
|
2786
|
+
],
|
|
2787
|
+
pitfalls: [
|
|
2788
|
+
"Perfectly regular grids without variation feel mechanical, not generative",
|
|
2789
|
+
"Random distribution reads as noise, not rhythm",
|
|
2790
|
+
"Single-scale repetition feels monotonous \u2014 vary size, spacing, or density",
|
|
2791
|
+
"Rhythm that ignores the composition's focal structure",
|
|
2792
|
+
"Over-complexity \u2014 too many overlapping rhythms create visual noise"
|
|
2793
|
+
]
|
|
2794
|
+
};
|
|
2578
2795
|
}
|
|
2579
|
-
|
|
2580
|
-
const adapter = registry3.resolve(sketch.renderer.type);
|
|
2581
|
-
const html = adapter.generateStandaloneHTML(sketch);
|
|
2582
|
-
const content = Buffer.from(html, "utf-8");
|
|
2583
|
-
await (0, import_promises7.writeFile)(outputPath, content);
|
|
2796
|
+
function buildUnityFramework() {
|
|
2584
2797
|
return {
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2798
|
+
aspect: "unity",
|
|
2799
|
+
questions: [
|
|
2800
|
+
"Does the piece feel like one cohesive work or disconnected parts?",
|
|
2801
|
+
"Is there a unifying visual language (consistent mark quality, shape vocabulary)?",
|
|
2802
|
+
"Do the parameters work together or do some feel bolted on?",
|
|
2803
|
+
"Would removing any element weaken the whole?",
|
|
2804
|
+
"Does the algorithm express a single clear idea?"
|
|
2805
|
+
],
|
|
2806
|
+
principles: [
|
|
2807
|
+
"Unity through repetition \u2014 shared elements tie the composition together",
|
|
2808
|
+
"Unity through proximity \u2014 grouped elements feel related",
|
|
2809
|
+
"Unity through continuation \u2014 aligned elements create visual connections",
|
|
2810
|
+
"Variety within unity \u2014 enough variation to hold interest, enough consistency to cohere",
|
|
2811
|
+
"Conceptual unity \u2014 all visual decisions serve the stated philosophy"
|
|
2812
|
+
],
|
|
2813
|
+
pitfalls: [
|
|
2814
|
+
"Feature accumulation \u2014 adding elements that don't serve the core concept",
|
|
2815
|
+
"Inconsistent mark quality \u2014 mixing precise geometry with organic marks without intention",
|
|
2816
|
+
"Disconnected color and form \u2014 palette that doesn't relate to the spatial structure",
|
|
2817
|
+
"Parameter sprawl \u2014 too many controls that don't interact meaningfully",
|
|
2818
|
+
"Style mixing without integration \u2014 combining techniques that don't speak to each other"
|
|
2819
|
+
]
|
|
2591
2820
|
};
|
|
2592
2821
|
}
|
|
2593
|
-
|
|
2594
|
-
const adapter = registry3.resolve(sketch.renderer.type);
|
|
2595
|
-
const html = adapter.generateStandaloneHTML(sketch);
|
|
2596
|
-
const width = input.width ?? sketch.canvas.width;
|
|
2597
|
-
const height = input.height ?? sketch.canvas.height;
|
|
2598
|
-
const result = await captureHtml({ html, width, height });
|
|
2599
|
-
await (0, import_promises7.writeFile)(input.outputPath, result.bytes);
|
|
2822
|
+
function buildExpressionFramework() {
|
|
2600
2823
|
return {
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2824
|
+
aspect: "expression",
|
|
2825
|
+
questions: [
|
|
2826
|
+
"What mood or feeling does this piece evoke?",
|
|
2827
|
+
"Is the generative process visible in the output? Should it be?",
|
|
2828
|
+
"Does the algorithm's logic contribute to the emotional quality?",
|
|
2829
|
+
"Is there a sense of the unexpected \u2014 does the piece surprise even its creator?",
|
|
2830
|
+
"Does the philosophy statement match the visual experience?"
|
|
2831
|
+
],
|
|
2832
|
+
principles: [
|
|
2833
|
+
"Generative art is a conversation between intention and emergence",
|
|
2834
|
+
"The algorithm is a medium \u2014 its constraints and affordances shape expression",
|
|
2835
|
+
"Controlled randomness creates life; pure randomness creates noise",
|
|
2836
|
+
"The seed is a collaborator \u2014 different seeds should produce meaningfully different moods",
|
|
2837
|
+
"Process and result are both the artwork \u2014 the code embodies artistic decisions"
|
|
2838
|
+
],
|
|
2839
|
+
pitfalls: [
|
|
2840
|
+
"Over-control \u2014 leaving no room for generative surprise",
|
|
2841
|
+
"Under-control \u2014 no discernible artistic intention behind the randomness",
|
|
2842
|
+
"Technique as end \u2014 impressive code that produces emotionally flat output",
|
|
2843
|
+
"Derivative work \u2014 reproducing established generative art tropes without adding perspective",
|
|
2844
|
+
"Mismatched intent \u2014 the philosophy says one thing but the visual says another"
|
|
2845
|
+
]
|
|
2607
2846
|
};
|
|
2608
2847
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2848
|
+
var ASPECT_BUILDERS = {
|
|
2849
|
+
composition: buildCompositionFramework,
|
|
2850
|
+
color: buildColorFramework,
|
|
2851
|
+
rhythm: buildRhythmFramework,
|
|
2852
|
+
unity: buildUnityFramework,
|
|
2853
|
+
expression: buildExpressionFramework
|
|
2854
|
+
};
|
|
2855
|
+
async function critiqueSketch(state, input) {
|
|
2856
|
+
state.requireWorkspace();
|
|
2857
|
+
let sketchId;
|
|
2858
|
+
if (input.sketchId) {
|
|
2859
|
+
sketchId = input.sketchId;
|
|
2860
|
+
} else if (state.selection.size > 0) {
|
|
2861
|
+
sketchId = [...state.selection][0];
|
|
2862
|
+
} else {
|
|
2863
|
+
throw new Error("No sketch specified and nothing selected");
|
|
2624
2864
|
}
|
|
2625
|
-
const
|
|
2626
|
-
const
|
|
2627
|
-
const
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
const
|
|
2636
|
-
|
|
2637
|
-
|
|
2865
|
+
const loaded = state.requireSketch(sketchId);
|
|
2866
|
+
const sketch = loaded.definition;
|
|
2867
|
+
const capture = await captureScreenshot(state, {
|
|
2868
|
+
target: "sketch",
|
|
2869
|
+
sketchId,
|
|
2870
|
+
previewSize: input.previewSize ?? 400
|
|
2871
|
+
});
|
|
2872
|
+
const aspects = input.aspects ?? [...ALL_ASPECTS];
|
|
2873
|
+
const frameworks = aspects.map((a) => ASPECT_BUILDERS[a]());
|
|
2874
|
+
const level = sketch.compositionLevel ?? "sketch";
|
|
2875
|
+
const severity = SEVERITY[level] ?? SEVERITY["sketch"];
|
|
2876
|
+
const relevantSkills = gatherRelevantSkills(aspects);
|
|
2877
|
+
const metadata = {
|
|
2638
2878
|
success: true,
|
|
2639
|
-
sketchId
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2879
|
+
sketchId,
|
|
2880
|
+
title: sketch.title,
|
|
2881
|
+
compositionLevel: level,
|
|
2882
|
+
philosophy: sketch.philosophy ?? null,
|
|
2883
|
+
severity: {
|
|
2884
|
+
level: severity.level,
|
|
2885
|
+
description: severity.description,
|
|
2886
|
+
focus: severity.focus,
|
|
2887
|
+
tolerance: severity.tolerance
|
|
2888
|
+
},
|
|
2889
|
+
frameworks,
|
|
2890
|
+
relevantSkills,
|
|
2891
|
+
instructions: [
|
|
2892
|
+
"Use the image above and the frameworks below to perform a structured self-critique.",
|
|
2893
|
+
`Calibrate your critique to the ${severity.level} level: ${severity.description}`,
|
|
2894
|
+
"For each aspect, answer the questions, check the principles, and watch for the pitfalls.",
|
|
2895
|
+
"Be honest but constructive \u2014 identify what works as well as what could improve.",
|
|
2896
|
+
"End with 2-3 specific, actionable improvements ranked by impact."
|
|
2897
|
+
]
|
|
2645
2898
|
};
|
|
2646
|
-
}
|
|
2647
|
-
async function exportAlgorithm(sketch, outputPath) {
|
|
2648
|
-
const content = Buffer.from(sketch.algorithm, "utf-8");
|
|
2649
|
-
await (0, import_promises7.writeFile)(outputPath, content);
|
|
2650
2899
|
return {
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
format: "algorithm",
|
|
2654
|
-
outputPath,
|
|
2655
|
-
fileSize: content.byteLength,
|
|
2656
|
-
renderer: sketch.renderer.type
|
|
2900
|
+
metadata,
|
|
2901
|
+
previewJpegBase64: capture.previewJpegBase64
|
|
2657
2902
|
};
|
|
2658
2903
|
}
|
|
2659
|
-
async function
|
|
2660
|
-
|
|
2661
|
-
const
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
const
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2904
|
+
async function compareSketches(state, input) {
|
|
2905
|
+
state.requireWorkspace();
|
|
2906
|
+
const ids = input.sketchIds;
|
|
2907
|
+
if (ids.length < 2) {
|
|
2908
|
+
throw new Error("compare_sketches requires at least 2 sketch IDs");
|
|
2909
|
+
}
|
|
2910
|
+
if (ids.length > 4) {
|
|
2911
|
+
throw new Error("compare_sketches supports a maximum of 4 sketches");
|
|
2912
|
+
}
|
|
2913
|
+
const sketchInfos = ids.map((id) => {
|
|
2914
|
+
const loaded = state.requireSketch(id);
|
|
2915
|
+
return {
|
|
2916
|
+
id,
|
|
2917
|
+
title: loaded.definition.title,
|
|
2918
|
+
compositionLevel: loaded.definition.compositionLevel ?? "sketch",
|
|
2919
|
+
philosophy: loaded.definition.philosophy ?? null,
|
|
2920
|
+
renderer: loaded.definition.renderer.type,
|
|
2921
|
+
seed: loaded.definition.state.seed
|
|
2922
|
+
};
|
|
2673
2923
|
});
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2924
|
+
const batchResult = await captureBatch(state, {
|
|
2925
|
+
sketchIds: ids,
|
|
2926
|
+
previewSize: input.previewSize ?? 300
|
|
2927
|
+
});
|
|
2928
|
+
const previews = batchResult.items.map((item) => ({
|
|
2929
|
+
sketchId: item.metadata["sketchId"],
|
|
2930
|
+
inlineJpegBase64: item.inlineJpegBase64
|
|
2931
|
+
}));
|
|
2932
|
+
const aspects = input.aspects ?? [...ALL_ASPECTS];
|
|
2933
|
+
const frameworks = aspects.map((a) => ASPECT_BUILDERS[a]());
|
|
2934
|
+
const comparisonQuestions = aspects.map((aspect) => ({
|
|
2935
|
+
aspect,
|
|
2936
|
+
questions: buildComparisonQuestions(aspect, sketchInfos.length)
|
|
2937
|
+
}));
|
|
2938
|
+
const metadata = {
|
|
2683
2939
|
success: true,
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2940
|
+
sketches: sketchInfos,
|
|
2941
|
+
aspects,
|
|
2942
|
+
frameworks,
|
|
2943
|
+
comparisonQuestions,
|
|
2944
|
+
instructions: [
|
|
2945
|
+
`Compare the ${ids.length} sketches shown above across the specified aspects.`,
|
|
2946
|
+
"For each aspect, use the framework questions and comparison questions to analyze differences.",
|
|
2947
|
+
"Identify which sketch handles each aspect most effectively and why.",
|
|
2948
|
+
"Note where sketches complement each other \u2014 techniques from one could improve another.",
|
|
2949
|
+
"End with a ranking per aspect and overall, with specific observations justifying each placement."
|
|
2694
2950
|
]
|
|
2695
2951
|
};
|
|
2952
|
+
return { metadata, previews };
|
|
2953
|
+
}
|
|
2954
|
+
function buildComparisonQuestions(aspect, count) {
|
|
2955
|
+
const base = {
|
|
2956
|
+
composition: [
|
|
2957
|
+
"Which sketch has the strongest focal point?",
|
|
2958
|
+
"How do the compositions differ in their use of space?",
|
|
2959
|
+
"Which creates the most effective visual hierarchy?"
|
|
2960
|
+
],
|
|
2961
|
+
color: [
|
|
2962
|
+
"Which palette feels most intentional?",
|
|
2963
|
+
"How do the value ranges compare \u2014 which has the strongest lights and darks?",
|
|
2964
|
+
"Which color temperature creates the most effective mood?"
|
|
2965
|
+
],
|
|
2966
|
+
rhythm: [
|
|
2967
|
+
"Which sketch has the most engaging visual rhythm?",
|
|
2968
|
+
"How do the rhythmic structures differ \u2014 regular vs progressive vs irregular?",
|
|
2969
|
+
"Which achieves the best balance of repetition and variation?"
|
|
2970
|
+
],
|
|
2971
|
+
unity: [
|
|
2972
|
+
"Which sketch feels most cohesive as a single work?",
|
|
2973
|
+
"Where does unity break down in each \u2014 what elements feel disconnected?",
|
|
2974
|
+
"Which has the tightest relationship between concept and execution?"
|
|
2975
|
+
],
|
|
2976
|
+
expression: [
|
|
2977
|
+
"Which sketch evokes the strongest emotional response?",
|
|
2978
|
+
"How does each sketch's generative process contribute to its expression?",
|
|
2979
|
+
"Which most successfully balances intention with emergence?"
|
|
2980
|
+
]
|
|
2981
|
+
};
|
|
2982
|
+
const questions = [...base[aspect]];
|
|
2983
|
+
if (count > 2) {
|
|
2984
|
+
questions.push(
|
|
2985
|
+
`Could elements from different sketches be combined to create something stronger?`
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2988
|
+
return questions;
|
|
2989
|
+
}
|
|
2990
|
+
function gatherRelevantSkills(aspects) {
|
|
2991
|
+
const aspectToCategory = {
|
|
2992
|
+
composition: ["composition"],
|
|
2993
|
+
color: ["color"],
|
|
2994
|
+
rhythm: ["composition"],
|
|
2995
|
+
unity: ["composition", "color"],
|
|
2996
|
+
expression: ["process"]
|
|
2997
|
+
};
|
|
2998
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2999
|
+
const result = [];
|
|
3000
|
+
for (const aspect of aspects) {
|
|
3001
|
+
const categories = aspectToCategory[aspect];
|
|
3002
|
+
for (const cat of categories) {
|
|
3003
|
+
const skills = registry3.list(cat);
|
|
3004
|
+
for (const skill of skills) {
|
|
3005
|
+
if (!seen.has(skill.id)) {
|
|
3006
|
+
seen.add(skill.id);
|
|
3007
|
+
result.push({ id: skill.id, name: skill.name, relevantTo: aspect });
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
return result;
|
|
2696
3013
|
}
|
|
2697
3014
|
|
|
2698
|
-
// src/tools/
|
|
2699
|
-
|
|
2700
|
-
|
|
3015
|
+
// src/tools/series.ts
|
|
3016
|
+
var import_promises7 = require("fs/promises");
|
|
3017
|
+
var import_path8 = require("path");
|
|
3018
|
+
var import_core10 = require("@genart-dev/core");
|
|
3019
|
+
function now6() {
|
|
3020
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
2701
3021
|
}
|
|
2702
|
-
var
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
"
|
|
2712
|
-
"
|
|
2713
|
-
"
|
|
2714
|
-
"
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
"
|
|
3022
|
+
var KEBAB_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3023
|
+
function validateKebabId2(id) {
|
|
3024
|
+
if (!KEBAB_RE2.test(id)) {
|
|
3025
|
+
throw new Error(
|
|
3026
|
+
"ID must be kebab-case: lowercase letters, numbers, hyphens"
|
|
3027
|
+
);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
var VALID_STAGES = [
|
|
3031
|
+
"studies",
|
|
3032
|
+
"drafts",
|
|
3033
|
+
"refinements",
|
|
3034
|
+
"finals"
|
|
3035
|
+
];
|
|
3036
|
+
var STAGE_TO_LEVEL = {
|
|
3037
|
+
studies: "study",
|
|
3038
|
+
drafts: "sketch",
|
|
3039
|
+
refinements: "developed",
|
|
3040
|
+
finals: "exhibition"
|
|
3041
|
+
};
|
|
3042
|
+
var LEVEL_SCALE = {
|
|
3043
|
+
study: 1,
|
|
3044
|
+
sketch: 1,
|
|
3045
|
+
developed: 1.5,
|
|
3046
|
+
exhibition: 2
|
|
3047
|
+
};
|
|
3048
|
+
async function createSeries(state, input) {
|
|
3049
|
+
const ws = state.requireWorkspace();
|
|
3050
|
+
const id = input.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
3051
|
+
if (!id) {
|
|
3052
|
+
throw new Error("Could not derive a valid ID from the label");
|
|
3053
|
+
}
|
|
3054
|
+
if (ws.series?.some((s) => s.id === id)) {
|
|
3055
|
+
throw new Error(`Series with ID '${id}' already exists in workspace`);
|
|
3056
|
+
}
|
|
3057
|
+
const stages = input.stages ?? [...VALID_STAGES];
|
|
3058
|
+
for (const stage of stages) {
|
|
3059
|
+
if (!VALID_STAGES.includes(stage)) {
|
|
3060
|
+
throw new Error(
|
|
3061
|
+
`Invalid stage: '${stage}'. Valid stages: ${VALID_STAGES.join(", ")}`
|
|
3062
|
+
);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
const sketchFiles = input.sketchFiles ?? [];
|
|
3066
|
+
for (const file of sketchFiles) {
|
|
3067
|
+
const found = ws.sketches.some((s) => s.file === file);
|
|
3068
|
+
if (!found) {
|
|
3069
|
+
throw new Error(
|
|
3070
|
+
`Sketch file '${file}' not found in workspace`
|
|
3071
|
+
);
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
const series = {
|
|
3075
|
+
id,
|
|
3076
|
+
label: input.label,
|
|
3077
|
+
narrative: input.narrative,
|
|
3078
|
+
intent: input.intent,
|
|
3079
|
+
...input.progression ? { progression: input.progression } : {},
|
|
3080
|
+
stages,
|
|
3081
|
+
sketchFiles
|
|
3082
|
+
};
|
|
3083
|
+
state.workspace = {
|
|
3084
|
+
...ws,
|
|
3085
|
+
modified: now6(),
|
|
3086
|
+
series: [...ws.series ?? [], series]
|
|
3087
|
+
};
|
|
3088
|
+
const workspaceJson = (0, import_core10.serializeWorkspace)(state.workspace);
|
|
3089
|
+
if (!state.remoteMode) {
|
|
3090
|
+
await (0, import_promises7.writeFile)(state.workspacePath, workspaceJson, "utf-8");
|
|
3091
|
+
}
|
|
3092
|
+
state.emitMutation("workspace:updated", { seriesAdded: id });
|
|
3093
|
+
return {
|
|
3094
|
+
success: true,
|
|
3095
|
+
series: {
|
|
3096
|
+
id,
|
|
3097
|
+
label: input.label,
|
|
3098
|
+
narrative: input.narrative,
|
|
3099
|
+
intent: input.intent,
|
|
3100
|
+
stages,
|
|
3101
|
+
sketchCount: sketchFiles.length
|
|
3102
|
+
},
|
|
3103
|
+
workspaceContent: workspaceJson
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
async function developConcept(_state, input) {
|
|
3107
|
+
const medium = input.medium ?? "p5";
|
|
3108
|
+
return {
|
|
3109
|
+
success: true,
|
|
3110
|
+
conceptPlan: {
|
|
3111
|
+
concept: input.concept,
|
|
3112
|
+
medium,
|
|
3113
|
+
mood: {
|
|
3114
|
+
instruction: "Define the emotional quality this concept should evoke.",
|
|
3115
|
+
prompts: [
|
|
3116
|
+
"What feeling should the viewer experience?",
|
|
3117
|
+
"Is this contemplative, energetic, unsettling, serene?",
|
|
3118
|
+
"What time of day, season, or environment does this concept suggest?"
|
|
3119
|
+
]
|
|
3120
|
+
},
|
|
3121
|
+
palette: {
|
|
3122
|
+
instruction: "Design a color strategy that serves the mood.",
|
|
3123
|
+
prompts: [
|
|
3124
|
+
"What color temperature dominates (warm/cool)?",
|
|
3125
|
+
"How many distinct hues are needed?",
|
|
3126
|
+
"Should saturation be high (bold, graphic) or low (subtle, atmospheric)?",
|
|
3127
|
+
"What value range (light-to-dark contrast) supports the concept?"
|
|
3128
|
+
]
|
|
3129
|
+
},
|
|
3130
|
+
composition: {
|
|
3131
|
+
instruction: "Plan the spatial structure.",
|
|
3132
|
+
prompts: [
|
|
3133
|
+
"Where should the viewer's eye land first?",
|
|
3134
|
+
"Is the composition centered, asymmetric, or edge-driven?",
|
|
3135
|
+
"How does negative space contribute to the concept?",
|
|
3136
|
+
"What rhythm (regular, progressive, chaotic) serves the idea?"
|
|
3137
|
+
]
|
|
3138
|
+
},
|
|
3139
|
+
skills: {
|
|
3140
|
+
instruction: "Identify design skills to load for this concept.",
|
|
3141
|
+
prompts: [
|
|
3142
|
+
"Which composition skill applies (rule-of-thirds, golden-ratio, gestalt)?",
|
|
3143
|
+
"Which color skill applies (color-harmony, color-temperature, simultaneous-contrast)?",
|
|
3144
|
+
"Are there process skills needed (layering-strategy, iterative-refinement, thumbnail-studies)?",
|
|
3145
|
+
"Consider using `suggest_skills` with the concept as context."
|
|
3146
|
+
]
|
|
3147
|
+
},
|
|
3148
|
+
seriesStructure: {
|
|
3149
|
+
instruction: "Plan the body of work.",
|
|
3150
|
+
prompts: [
|
|
3151
|
+
"How many studies should explore the core idea (3-6 recommended)?",
|
|
3152
|
+
"What aspect varies between studies (color, density, rhythm, scale)?",
|
|
3153
|
+
"Which studies should be developed further into drafts?",
|
|
3154
|
+
"What progression tells the most compelling story?"
|
|
3155
|
+
],
|
|
3156
|
+
recommendedStages: ["studies", "drafts", "refinements", "finals"]
|
|
3157
|
+
}
|
|
3158
|
+
},
|
|
3159
|
+
nextSteps: [
|
|
3160
|
+
"1. Use `create_series` with a label, narrative, and intent derived from this plan.",
|
|
3161
|
+
"2. Create 3-6 study-level sketches using `create_sketch` with compositionLevel: 'study'.",
|
|
3162
|
+
"3. Use `critique_sketch` on each study to evaluate against the concept.",
|
|
3163
|
+
"4. Use `promote_sketch` to advance the best studies to drafts.",
|
|
3164
|
+
"5. Iterate: critique \u2192 refine \u2192 promote through stages.",
|
|
3165
|
+
"6. Use `series_summary` to capture the full progression."
|
|
3166
|
+
]
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
async function seriesSummary(state, input) {
|
|
3170
|
+
const ws = state.requireWorkspace();
|
|
3171
|
+
const series = ws.series?.find((s) => s.id === input.seriesId);
|
|
3172
|
+
if (!series) {
|
|
3173
|
+
throw new Error(`Series '${input.seriesId}' not found in workspace`);
|
|
3174
|
+
}
|
|
3175
|
+
const sketchInfos = [];
|
|
3176
|
+
const loadedIds = [];
|
|
3177
|
+
for (const file of series.sketchFiles) {
|
|
3178
|
+
let found = false;
|
|
3179
|
+
for (const [id, loaded] of state.sketches) {
|
|
3180
|
+
if ((0, import_path8.basename)(loaded.path) === file) {
|
|
3181
|
+
const def = loaded.definition;
|
|
3182
|
+
sketchInfos.push({
|
|
3183
|
+
id,
|
|
3184
|
+
title: def.title,
|
|
3185
|
+
file,
|
|
3186
|
+
compositionLevel: def.compositionLevel ?? "sketch",
|
|
3187
|
+
lineage: def.lineage ?? null,
|
|
3188
|
+
renderer: def.renderer.type,
|
|
3189
|
+
canvas: `${def.canvas.width}x${def.canvas.height}`,
|
|
3190
|
+
seed: def.state.seed,
|
|
3191
|
+
parameterCount: def.parameters.length,
|
|
3192
|
+
colorCount: def.colors.length,
|
|
3193
|
+
philosophy: def.philosophy ?? null
|
|
3194
|
+
});
|
|
3195
|
+
loadedIds.push(id);
|
|
3196
|
+
found = true;
|
|
3197
|
+
break;
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
if (!found) {
|
|
3201
|
+
sketchInfos.push({ file, status: "not loaded" });
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
let previews;
|
|
3205
|
+
if (input.captureScreenshots !== false && loadedIds.length > 0) {
|
|
3206
|
+
const batchResult = await captureBatch(state, {
|
|
3207
|
+
sketchIds: loadedIds,
|
|
3208
|
+
previewSize: input.previewSize ?? 300
|
|
3209
|
+
});
|
|
3210
|
+
previews = batchResult.items.map((item) => ({
|
|
3211
|
+
sketchId: item.metadata["sketchId"],
|
|
3212
|
+
inlineJpegBase64: item.inlineJpegBase64
|
|
3213
|
+
}));
|
|
3214
|
+
}
|
|
3215
|
+
const metadata = {
|
|
3216
|
+
success: true,
|
|
3217
|
+
series: {
|
|
3218
|
+
id: series.id,
|
|
3219
|
+
label: series.label,
|
|
3220
|
+
narrative: series.narrative,
|
|
3221
|
+
intent: series.intent,
|
|
3222
|
+
progression: series.progression ?? null,
|
|
3223
|
+
stages: series.stages ?? null
|
|
3224
|
+
},
|
|
3225
|
+
sketches: sketchInfos,
|
|
3226
|
+
summary: {
|
|
3227
|
+
totalSketches: series.sketchFiles.length,
|
|
3228
|
+
loadedSketches: loadedIds.length,
|
|
3229
|
+
compositionLevels: countBy(
|
|
3230
|
+
sketchInfos.filter((s) => s["compositionLevel"]).map((s) => s["compositionLevel"])
|
|
3231
|
+
)
|
|
3232
|
+
},
|
|
3233
|
+
instructions: [
|
|
3234
|
+
"Review the series progression from studies through finals.",
|
|
3235
|
+
"Evaluate whether the narrative and intent are reflected in the body of work.",
|
|
3236
|
+
"Consider: does each sketch build on its predecessors? Is there a clear evolution?",
|
|
3237
|
+
"Identify the strongest and weakest pieces. What makes them succeed or fail?",
|
|
3238
|
+
"Document insights and decisions in the series narrative."
|
|
3239
|
+
]
|
|
3240
|
+
};
|
|
3241
|
+
return { metadata, previews };
|
|
3242
|
+
}
|
|
3243
|
+
async function promoteSketch(state, input) {
|
|
3244
|
+
const ws = state.requireWorkspace();
|
|
3245
|
+
const source = state.requireSketch(input.sketchId);
|
|
3246
|
+
const sourceDef = source.definition;
|
|
3247
|
+
if (!VALID_STAGES.includes(input.toStage)) {
|
|
3248
|
+
throw new Error(
|
|
3249
|
+
`Invalid stage: '${input.toStage}'. Valid stages: ${VALID_STAGES.join(", ")}`
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
const targetLevel = STAGE_TO_LEVEL[input.toStage];
|
|
3253
|
+
const scale = LEVEL_SCALE[targetLevel];
|
|
3254
|
+
const newId = input.newId ?? `${input.sketchId}-${input.toStage.replace(/s$/, "")}`;
|
|
3255
|
+
validateKebabId2(newId);
|
|
3256
|
+
if (state.getSketch(newId)) {
|
|
3257
|
+
throw new Error(`Sketch with ID '${newId}' already exists`);
|
|
3258
|
+
}
|
|
3259
|
+
const newWidth = Math.round(sourceDef.canvas.width * scale);
|
|
3260
|
+
const newHeight = Math.round(sourceDef.canvas.height * scale);
|
|
3261
|
+
const sourceGeneration = sourceDef.lineage?.generation ?? 1;
|
|
3262
|
+
const title = input.title ?? `${sourceDef.title} (${input.toStage.replace(/s$/, "")})`;
|
|
3263
|
+
const ts = now6();
|
|
3264
|
+
const promotedDef = {
|
|
3265
|
+
genart: "1.1",
|
|
3266
|
+
id: newId,
|
|
3267
|
+
title,
|
|
3268
|
+
created: ts,
|
|
3269
|
+
modified: ts,
|
|
3270
|
+
renderer: sourceDef.renderer,
|
|
3271
|
+
canvas: { width: newWidth, height: newHeight },
|
|
3272
|
+
parameters: [...sourceDef.parameters],
|
|
3273
|
+
colors: [...sourceDef.colors],
|
|
3274
|
+
state: {
|
|
3275
|
+
seed: sourceDef.state.seed,
|
|
3276
|
+
params: { ...sourceDef.state.params },
|
|
3277
|
+
colorPalette: [...sourceDef.state.colorPalette]
|
|
3278
|
+
},
|
|
3279
|
+
algorithm: sourceDef.algorithm,
|
|
3280
|
+
compositionLevel: targetLevel,
|
|
3281
|
+
lineage: {
|
|
3282
|
+
parentId: input.sketchId,
|
|
3283
|
+
parentTitle: sourceDef.title,
|
|
3284
|
+
generation: sourceGeneration + 1
|
|
3285
|
+
},
|
|
3286
|
+
...sourceDef.philosophy ? { philosophy: sourceDef.philosophy } : {},
|
|
3287
|
+
...sourceDef.themes ? { themes: [...sourceDef.themes] } : {},
|
|
3288
|
+
...sourceDef.skills ? { skills: [...sourceDef.skills] } : {},
|
|
3289
|
+
...sourceDef.components ? { components: sourceDef.components } : {},
|
|
3290
|
+
...sourceDef.symbols ? { symbols: sourceDef.symbols } : {},
|
|
3291
|
+
...input.agent ? { agent: input.agent } : {},
|
|
3292
|
+
...input.model ? { model: input.model } : {}
|
|
3293
|
+
};
|
|
3294
|
+
const sourceDir = (0, import_path8.dirname)(source.path);
|
|
3295
|
+
const newPath = (0, import_path8.resolve)(sourceDir, `${newId}.genart`);
|
|
3296
|
+
const json = (0, import_core10.serializeGenart)(promotedDef);
|
|
3297
|
+
if (!state.remoteMode) {
|
|
3298
|
+
await (0, import_promises7.writeFile)(newPath, json, "utf-8");
|
|
3299
|
+
}
|
|
3300
|
+
state.sketches.set(newId, { definition: promotedDef, path: newPath });
|
|
3301
|
+
const sourceRef = ws.sketches.find(
|
|
3302
|
+
(s) => s.file === (0, import_path8.basename)(source.path)
|
|
3303
|
+
);
|
|
3304
|
+
const position = sourceRef ? { x: sourceRef.position.x, y: sourceRef.position.y + sourceDef.canvas.height + 200 } : { x: 0, y: 0 };
|
|
3305
|
+
const file = (0, import_path8.basename)(newPath);
|
|
3306
|
+
state.workspace = {
|
|
3307
|
+
...ws,
|
|
3308
|
+
modified: ts,
|
|
3309
|
+
sketches: [...ws.sketches, { file, position }]
|
|
3310
|
+
};
|
|
3311
|
+
if (input.seriesId) {
|
|
3312
|
+
const seriesIndex = state.workspace.series?.findIndex(
|
|
3313
|
+
(s) => s.id === input.seriesId
|
|
3314
|
+
);
|
|
3315
|
+
if (seriesIndex !== void 0 && seriesIndex >= 0 && state.workspace.series) {
|
|
3316
|
+
const series = state.workspace.series[seriesIndex];
|
|
3317
|
+
const updatedSeries = {
|
|
3318
|
+
...series,
|
|
3319
|
+
sketchFiles: [...series.sketchFiles, file]
|
|
3320
|
+
};
|
|
3321
|
+
state.workspace = {
|
|
3322
|
+
...state.workspace,
|
|
3323
|
+
series: state.workspace.series.map(
|
|
3324
|
+
(s, i) => i === seriesIndex ? updatedSeries : s
|
|
3325
|
+
)
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
const workspaceJson = (0, import_core10.serializeWorkspace)(state.workspace);
|
|
3330
|
+
if (!state.remoteMode) {
|
|
3331
|
+
await (0, import_promises7.writeFile)(state.workspacePath, workspaceJson, "utf-8");
|
|
3332
|
+
}
|
|
3333
|
+
state.emitMutation("sketch:created", { id: newId, path: newPath });
|
|
3334
|
+
state.emitMutation("workspace:updated", { added: file });
|
|
3335
|
+
return {
|
|
3336
|
+
success: true,
|
|
3337
|
+
sourceId: input.sketchId,
|
|
3338
|
+
promotedSketch: {
|
|
3339
|
+
id: newId,
|
|
3340
|
+
title,
|
|
3341
|
+
path: newPath,
|
|
3342
|
+
compositionLevel: targetLevel,
|
|
3343
|
+
stage: input.toStage,
|
|
3344
|
+
canvas: { width: newWidth, height: newHeight },
|
|
3345
|
+
position,
|
|
3346
|
+
lineage: promotedDef.lineage
|
|
3347
|
+
},
|
|
3348
|
+
...scale > 1 ? {
|
|
3349
|
+
canvasUpscaled: `Canvas scaled ${scale}x: ${sourceDef.canvas.width}x${sourceDef.canvas.height} \u2192 ${newWidth}x${newHeight}`
|
|
3350
|
+
} : {},
|
|
3351
|
+
fileContent: json,
|
|
3352
|
+
workspaceContent: workspaceJson
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
function countBy(items) {
|
|
3356
|
+
const counts = {};
|
|
3357
|
+
for (const item of items) {
|
|
3358
|
+
counts[item] = (counts[item] ?? 0) + 1;
|
|
3359
|
+
}
|
|
3360
|
+
return counts;
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
// src/tools/reference.ts
|
|
3364
|
+
var import_promises8 = require("fs/promises");
|
|
3365
|
+
var import_path9 = require("path");
|
|
3366
|
+
var import_core11 = require("@genart-dev/core");
|
|
3367
|
+
var import_promises9 = require("fs/promises");
|
|
3368
|
+
function now7() {
|
|
3369
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3370
|
+
}
|
|
3371
|
+
var KEBAB_RE3 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3372
|
+
function validateKebabId3(id) {
|
|
3373
|
+
if (!KEBAB_RE3.test(id)) {
|
|
3374
|
+
throw new Error(
|
|
3375
|
+
"ID must be kebab-case: lowercase letters, numbers, hyphens"
|
|
3376
|
+
);
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3380
|
+
".png",
|
|
3381
|
+
".jpg",
|
|
3382
|
+
".jpeg",
|
|
3383
|
+
".gif",
|
|
3384
|
+
".webp",
|
|
3385
|
+
".bmp",
|
|
3386
|
+
".tiff",
|
|
3387
|
+
".svg"
|
|
3388
|
+
]);
|
|
3389
|
+
function isImageFile(path) {
|
|
3390
|
+
return IMAGE_EXTENSIONS.has((0, import_path9.extname)(path).toLowerCase());
|
|
3391
|
+
}
|
|
3392
|
+
var VALID_REFERENCE_TYPES = [
|
|
3393
|
+
"image",
|
|
3394
|
+
"artwork",
|
|
3395
|
+
"photograph",
|
|
3396
|
+
"texture",
|
|
3397
|
+
"palette"
|
|
3398
|
+
];
|
|
3399
|
+
async function addReference(state, input) {
|
|
3400
|
+
const ws = state.requireWorkspace();
|
|
3401
|
+
if (!isImageFile(input.image)) {
|
|
3402
|
+
throw new Error(
|
|
3403
|
+
`Not a recognized image file: ${input.image}. Supported: ${[...IMAGE_EXTENSIONS].join(", ")}`
|
|
3404
|
+
);
|
|
3405
|
+
}
|
|
3406
|
+
const id = input.id ?? (0, import_path9.basename)(input.image, (0, import_path9.extname)(input.image)).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
3407
|
+
if (!id) {
|
|
3408
|
+
throw new Error("Could not derive a valid ID from the image filename");
|
|
3409
|
+
}
|
|
3410
|
+
validateKebabId3(id);
|
|
3411
|
+
const refType = input.type ?? "image";
|
|
3412
|
+
if (!VALID_REFERENCE_TYPES.includes(refType)) {
|
|
3413
|
+
throw new Error(
|
|
3414
|
+
`Invalid reference type: '${refType}'. Valid types: ${VALID_REFERENCE_TYPES.join(", ")}`
|
|
3415
|
+
);
|
|
3416
|
+
}
|
|
3417
|
+
const workspaceDir = (0, import_path9.dirname)(state.workspacePath);
|
|
3418
|
+
const refsDir = (0, import_path9.resolve)(workspaceDir, "references");
|
|
3419
|
+
await (0, import_promises8.mkdir)(refsDir, { recursive: true });
|
|
3420
|
+
const ext = (0, import_path9.extname)(input.image);
|
|
3421
|
+
const destFilename = `${id}${ext}`;
|
|
3422
|
+
const destPath = (0, import_path9.resolve)(refsDir, destFilename);
|
|
3423
|
+
const relativePath = `references/${destFilename}`;
|
|
3424
|
+
if (!state.remoteMode) {
|
|
3425
|
+
await (0, import_promises8.copyFile)((0, import_path9.resolve)(input.image), destPath);
|
|
3426
|
+
}
|
|
3427
|
+
const ref = {
|
|
3428
|
+
id,
|
|
3429
|
+
type: refType,
|
|
3430
|
+
path: relativePath,
|
|
3431
|
+
...input.source ? { source: input.source } : {}
|
|
3432
|
+
};
|
|
3433
|
+
let attachedTo;
|
|
3434
|
+
let workspaceJson;
|
|
3435
|
+
let sketchJson;
|
|
3436
|
+
if (input.sketchId) {
|
|
3437
|
+
const loaded = state.requireSketch(input.sketchId);
|
|
3438
|
+
const existingRefs = loaded.definition.references ?? [];
|
|
3439
|
+
if (existingRefs.some((r) => r.id === id)) {
|
|
3440
|
+
throw new Error(
|
|
3441
|
+
`Reference with ID '${id}' already exists on sketch '${input.sketchId}'`
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3444
|
+
const updatedDef = {
|
|
3445
|
+
...loaded.definition,
|
|
3446
|
+
modified: now7(),
|
|
3447
|
+
references: [...existingRefs, ref]
|
|
3448
|
+
};
|
|
3449
|
+
state.sketches.set(input.sketchId, {
|
|
3450
|
+
definition: updatedDef,
|
|
3451
|
+
path: loaded.path
|
|
3452
|
+
});
|
|
3453
|
+
sketchJson = (0, import_core11.serializeGenart)(updatedDef);
|
|
3454
|
+
if (!state.remoteMode) {
|
|
3455
|
+
await (0, import_promises9.writeFile)(loaded.path, sketchJson, "utf-8");
|
|
3456
|
+
}
|
|
3457
|
+
attachedTo = `sketch:${input.sketchId}`;
|
|
3458
|
+
state.emitMutation("sketch:updated", { id: input.sketchId });
|
|
3459
|
+
} else if (input.seriesId) {
|
|
3460
|
+
const seriesIndex = ws.series?.findIndex((s) => s.id === input.seriesId);
|
|
3461
|
+
if (seriesIndex === void 0 || seriesIndex < 0 || !ws.series) {
|
|
3462
|
+
throw new Error(`Series '${input.seriesId}' not found in workspace`);
|
|
3463
|
+
}
|
|
3464
|
+
const series = ws.series[seriesIndex];
|
|
3465
|
+
const existingRefs = series.references ?? [];
|
|
3466
|
+
if (existingRefs.some((r) => r.id === id)) {
|
|
3467
|
+
throw new Error(
|
|
3468
|
+
`Reference with ID '${id}' already exists on series '${input.seriesId}'`
|
|
3469
|
+
);
|
|
3470
|
+
}
|
|
3471
|
+
const updatedSeries = {
|
|
3472
|
+
...series,
|
|
3473
|
+
references: [...existingRefs, ref]
|
|
3474
|
+
};
|
|
3475
|
+
state.workspace = {
|
|
3476
|
+
...ws,
|
|
3477
|
+
modified: now7(),
|
|
3478
|
+
series: ws.series.map(
|
|
3479
|
+
(s, i) => i === seriesIndex ? updatedSeries : s
|
|
3480
|
+
)
|
|
3481
|
+
};
|
|
3482
|
+
workspaceJson = (0, import_core11.serializeWorkspace)(state.workspace);
|
|
3483
|
+
if (!state.remoteMode) {
|
|
3484
|
+
await (0, import_promises9.writeFile)(state.workspacePath, workspaceJson, "utf-8");
|
|
3485
|
+
}
|
|
3486
|
+
attachedTo = `series:${input.seriesId}`;
|
|
3487
|
+
state.emitMutation("workspace:updated", { referenceAdded: id });
|
|
3488
|
+
} else {
|
|
3489
|
+
throw new Error("Either seriesId or sketchId must be specified");
|
|
3490
|
+
}
|
|
3491
|
+
return {
|
|
3492
|
+
success: true,
|
|
3493
|
+
reference: {
|
|
3494
|
+
id,
|
|
3495
|
+
type: refType,
|
|
3496
|
+
path: relativePath,
|
|
3497
|
+
source: input.source ?? null
|
|
3498
|
+
},
|
|
3499
|
+
attachedTo,
|
|
3500
|
+
...sketchJson ? { fileContent: sketchJson } : {},
|
|
3501
|
+
...workspaceJson ? { workspaceContent: workspaceJson } : {}
|
|
3502
|
+
};
|
|
3503
|
+
}
|
|
3504
|
+
async function analyzeReference(state, input) {
|
|
3505
|
+
state.requireWorkspace();
|
|
3506
|
+
const { ref, location } = findReference(state, input.referenceId, input.seriesId, input.sketchId);
|
|
3507
|
+
const workspaceDir = (0, import_path9.dirname)(state.workspacePath);
|
|
3508
|
+
const imagePath = (0, import_path9.resolve)(workspaceDir, ref.path);
|
|
3509
|
+
let previewJpegBase64;
|
|
3510
|
+
try {
|
|
3511
|
+
const imageBuffer = await (0, import_promises8.readFile)(imagePath);
|
|
3512
|
+
const ext = (0, import_path9.extname)(ref.path).toLowerCase();
|
|
3513
|
+
const mimeMap = {
|
|
3514
|
+
".png": "image/png",
|
|
3515
|
+
".jpg": "image/jpeg",
|
|
3516
|
+
".jpeg": "image/jpeg",
|
|
3517
|
+
".gif": "image/gif",
|
|
3518
|
+
".webp": "image/webp",
|
|
3519
|
+
".svg": "image/svg+xml"
|
|
3520
|
+
};
|
|
3521
|
+
previewJpegBase64 = imageBuffer.toString("base64");
|
|
3522
|
+
} catch {
|
|
3523
|
+
}
|
|
3524
|
+
const metadata = {
|
|
3525
|
+
success: true,
|
|
3526
|
+
referenceId: ref.id,
|
|
3527
|
+
type: ref.type,
|
|
3528
|
+
path: ref.path,
|
|
3529
|
+
source: ref.source ?? null,
|
|
3530
|
+
location,
|
|
3531
|
+
existingAnalysis: ref.analysis ?? null,
|
|
3532
|
+
analysisFramework: {
|
|
3533
|
+
composition: {
|
|
3534
|
+
instruction: "Analyze the compositional structure of this reference.",
|
|
3535
|
+
prompts: [
|
|
3536
|
+
"What is the primary compositional structure (centered, asymmetric, diagonal, radial)?",
|
|
3537
|
+
"Where does the eye land first? What creates the focal point?",
|
|
3538
|
+
"How is negative space used \u2014 actively or passively?",
|
|
3539
|
+
"What is the relationship between foreground, middle ground, and background?",
|
|
3540
|
+
"How do the edges and corners of the frame interact with the subject?"
|
|
3541
|
+
]
|
|
3542
|
+
},
|
|
3543
|
+
palette: {
|
|
3544
|
+
instruction: "Identify the color strategy.",
|
|
3545
|
+
prompts: [
|
|
3546
|
+
"What are the dominant colors (3-5 hex values)?",
|
|
3547
|
+
"What color temperature dominates \u2014 warm, cool, or neutral?",
|
|
3548
|
+
"What is the value range \u2014 high contrast or compressed?",
|
|
3549
|
+
"Is the palette analogous, complementary, triadic, or something else?",
|
|
3550
|
+
"How does saturation vary across the composition?"
|
|
3551
|
+
]
|
|
3552
|
+
},
|
|
3553
|
+
rhythm: {
|
|
3554
|
+
instruction: "Identify rhythmic and pattern qualities.",
|
|
3555
|
+
prompts: [
|
|
3556
|
+
"Is there a repeating motif or interval?",
|
|
3557
|
+
"Is the rhythm regular, progressive, alternating, or irregular?",
|
|
3558
|
+
"At how many scales does pattern appear (fractal quality)?",
|
|
3559
|
+
"How do density variations create movement?",
|
|
3560
|
+
"Where are the moments of rest vs. activity?"
|
|
3561
|
+
]
|
|
3562
|
+
},
|
|
3563
|
+
mood: {
|
|
3564
|
+
instruction: "Identify the emotional and atmospheric qualities.",
|
|
3565
|
+
prompts: [
|
|
3566
|
+
"What is the overall mood \u2014 contemplative, energetic, serene, unsettling?",
|
|
3567
|
+
"How do color, light, and space contribute to that mood?",
|
|
3568
|
+
"Is there a sense of time \u2014 moment, duration, timelessness?",
|
|
3569
|
+
"What emotional response does the work invite?"
|
|
3570
|
+
]
|
|
3571
|
+
},
|
|
3572
|
+
technique: {
|
|
3573
|
+
instruction: "Identify technical and material qualities worth studying.",
|
|
3574
|
+
prompts: [
|
|
3575
|
+
"What medium or technique is used?",
|
|
3576
|
+
"How is mark-making contributing to expression?",
|
|
3577
|
+
"Are there layering or transparency effects?",
|
|
3578
|
+
"What level of control vs. chance is visible?",
|
|
3579
|
+
"What technical approach could be translated to generative art?"
|
|
3580
|
+
]
|
|
3581
|
+
}
|
|
3582
|
+
},
|
|
3583
|
+
instructions: [
|
|
3584
|
+
"Study the reference image carefully using the framework above.",
|
|
3585
|
+
"For each category, answer the prompts and synthesize your observations.",
|
|
3586
|
+
"After analysis, use update_reference_analysis to save the structured analysis.",
|
|
3587
|
+
"The analysis should inform how you create study sketches inspired by this reference.",
|
|
3588
|
+
"Focus on qualities that can be translated to generative art \u2014 don't try to replicate literally."
|
|
3589
|
+
]
|
|
3590
|
+
};
|
|
3591
|
+
return { metadata, previewJpegBase64 };
|
|
3592
|
+
}
|
|
3593
|
+
async function updateReferenceAnalysis(state, input) {
|
|
3594
|
+
const ws = state.requireWorkspace();
|
|
3595
|
+
const { ref, location } = findReference(
|
|
3596
|
+
state,
|
|
3597
|
+
input.referenceId,
|
|
3598
|
+
input.seriesId,
|
|
3599
|
+
input.sketchId
|
|
3600
|
+
);
|
|
3601
|
+
const updatedRef = {
|
|
3602
|
+
...ref,
|
|
3603
|
+
analysis: input.analysis
|
|
3604
|
+
};
|
|
3605
|
+
let workspaceJson;
|
|
3606
|
+
let sketchJson;
|
|
3607
|
+
if (location.startsWith("sketch:")) {
|
|
3608
|
+
const sketchId = location.replace("sketch:", "");
|
|
3609
|
+
const loaded = state.requireSketch(sketchId);
|
|
3610
|
+
const updatedDef = {
|
|
3611
|
+
...loaded.definition,
|
|
3612
|
+
modified: now7(),
|
|
3613
|
+
references: (loaded.definition.references ?? []).map(
|
|
3614
|
+
(r) => r.id === input.referenceId ? updatedRef : r
|
|
3615
|
+
)
|
|
3616
|
+
};
|
|
3617
|
+
state.sketches.set(sketchId, {
|
|
3618
|
+
definition: updatedDef,
|
|
3619
|
+
path: loaded.path
|
|
3620
|
+
});
|
|
3621
|
+
sketchJson = (0, import_core11.serializeGenart)(updatedDef);
|
|
3622
|
+
if (!state.remoteMode) {
|
|
3623
|
+
await (0, import_promises9.writeFile)(loaded.path, sketchJson, "utf-8");
|
|
3624
|
+
}
|
|
3625
|
+
state.emitMutation("sketch:updated", { id: sketchId });
|
|
3626
|
+
} else {
|
|
3627
|
+
const seriesId = location.replace("series:", "");
|
|
3628
|
+
const seriesIndex = ws.series.findIndex((s) => s.id === seriesId);
|
|
3629
|
+
const series = ws.series[seriesIndex];
|
|
3630
|
+
const updatedSeries = {
|
|
3631
|
+
...series,
|
|
3632
|
+
references: (series.references ?? []).map(
|
|
3633
|
+
(r) => r.id === input.referenceId ? updatedRef : r
|
|
3634
|
+
)
|
|
3635
|
+
};
|
|
3636
|
+
state.workspace = {
|
|
3637
|
+
...ws,
|
|
3638
|
+
modified: now7(),
|
|
3639
|
+
series: ws.series.map(
|
|
3640
|
+
(s, i) => i === seriesIndex ? updatedSeries : s
|
|
3641
|
+
)
|
|
3642
|
+
};
|
|
3643
|
+
workspaceJson = (0, import_core11.serializeWorkspace)(state.workspace);
|
|
3644
|
+
if (!state.remoteMode) {
|
|
3645
|
+
await (0, import_promises9.writeFile)(state.workspacePath, workspaceJson, "utf-8");
|
|
3646
|
+
}
|
|
3647
|
+
state.emitMutation("workspace:updated", { referenceAnalyzed: input.referenceId });
|
|
3648
|
+
}
|
|
3649
|
+
return {
|
|
3650
|
+
success: true,
|
|
3651
|
+
referenceId: input.referenceId,
|
|
3652
|
+
location,
|
|
3653
|
+
analysis: input.analysis,
|
|
3654
|
+
...sketchJson ? { fileContent: sketchJson } : {},
|
|
3655
|
+
...workspaceJson ? { workspaceContent: workspaceJson } : {}
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
async function extractPalette(state, input) {
|
|
3659
|
+
state.requireWorkspace();
|
|
3660
|
+
const { ref, location } = findReference(
|
|
3661
|
+
state,
|
|
3662
|
+
input.referenceId,
|
|
3663
|
+
input.seriesId,
|
|
3664
|
+
input.sketchId
|
|
3665
|
+
);
|
|
3666
|
+
const count = input.count ?? 6;
|
|
3667
|
+
const workspaceDir = (0, import_path9.dirname)(state.workspacePath);
|
|
3668
|
+
const imagePath = (0, import_path9.resolve)(workspaceDir, ref.path);
|
|
3669
|
+
let previewJpegBase64;
|
|
3670
|
+
try {
|
|
3671
|
+
const imageBuffer = await (0, import_promises8.readFile)(imagePath);
|
|
3672
|
+
previewJpegBase64 = imageBuffer.toString("base64");
|
|
3673
|
+
} catch {
|
|
3674
|
+
}
|
|
3675
|
+
const metadata = {
|
|
3676
|
+
success: true,
|
|
3677
|
+
referenceId: ref.id,
|
|
3678
|
+
type: ref.type,
|
|
3679
|
+
path: ref.path,
|
|
3680
|
+
location,
|
|
3681
|
+
requestedColors: count,
|
|
3682
|
+
existingPalette: ref.analysis?.palette ?? null,
|
|
3683
|
+
instructions: [
|
|
3684
|
+
`Extract ${count} dominant colors from the reference image as hex values.`,
|
|
3685
|
+
"Order them from most dominant to least dominant.",
|
|
3686
|
+
"Include both saturated and neutral colors if present in the image.",
|
|
3687
|
+
"Consider the role of each color \u2014 is it a background, accent, or primary element?",
|
|
3688
|
+
"After extraction, use update_reference_analysis to save the palette.",
|
|
3689
|
+
"You can also apply the extracted palette to a sketch using set_colors or create a new theme."
|
|
3690
|
+
],
|
|
3691
|
+
extractionGuidelines: {
|
|
3692
|
+
dominance: "Prioritize colors by the area they occupy, not just their saturation.",
|
|
3693
|
+
variety: "Include the full value range (lights, midtones, darks) if present.",
|
|
3694
|
+
harmony: `Look for ${count <= 4 ? "core harmony" : "extended palette including transitional colors"}.`,
|
|
3695
|
+
neutrals: "Don't ignore grays, blacks, and whites \u2014 they often define the character of a palette."
|
|
3696
|
+
}
|
|
3697
|
+
};
|
|
3698
|
+
return { metadata, previewJpegBase64 };
|
|
3699
|
+
}
|
|
3700
|
+
function findReference(state, referenceId, seriesId, sketchId) {
|
|
3701
|
+
if (sketchId) {
|
|
3702
|
+
const loaded = state.requireSketch(sketchId);
|
|
3703
|
+
const ref = (loaded.definition.references ?? []).find(
|
|
3704
|
+
(r) => r.id === referenceId
|
|
3705
|
+
);
|
|
3706
|
+
if (ref) return { ref, location: `sketch:${sketchId}` };
|
|
3707
|
+
throw new Error(
|
|
3708
|
+
`Reference '${referenceId}' not found on sketch '${sketchId}'`
|
|
3709
|
+
);
|
|
3710
|
+
}
|
|
3711
|
+
if (seriesId) {
|
|
3712
|
+
const ws2 = state.requireWorkspace();
|
|
3713
|
+
const series = ws2.series?.find((s) => s.id === seriesId);
|
|
3714
|
+
if (!series) {
|
|
3715
|
+
throw new Error(`Series '${seriesId}' not found in workspace`);
|
|
3716
|
+
}
|
|
3717
|
+
const ref = (series.references ?? []).find((r) => r.id === referenceId);
|
|
3718
|
+
if (ref) return { ref, location: `series:${seriesId}` };
|
|
3719
|
+
throw new Error(
|
|
3720
|
+
`Reference '${referenceId}' not found on series '${seriesId}'`
|
|
3721
|
+
);
|
|
3722
|
+
}
|
|
3723
|
+
const ws = state.requireWorkspace();
|
|
3724
|
+
if (ws.series) {
|
|
3725
|
+
for (const series of ws.series) {
|
|
3726
|
+
const ref = (series.references ?? []).find((r) => r.id === referenceId);
|
|
3727
|
+
if (ref) return { ref, location: `series:${series.id}` };
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
for (const [id, loaded] of state.sketches) {
|
|
3731
|
+
const ref = (loaded.definition.references ?? []).find(
|
|
3732
|
+
(r) => r.id === referenceId
|
|
3733
|
+
);
|
|
3734
|
+
if (ref) return { ref, location: `sketch:${id}` };
|
|
3735
|
+
}
|
|
3736
|
+
throw new Error(
|
|
3737
|
+
`Reference '${referenceId}' not found in any series or sketch`
|
|
3738
|
+
);
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
// src/tools/export.ts
|
|
3742
|
+
var import_fs = require("fs");
|
|
3743
|
+
var import_promises10 = require("fs/promises");
|
|
3744
|
+
var import_path10 = require("path");
|
|
3745
|
+
var import_archiver = __toESM(require("archiver"), 1);
|
|
3746
|
+
var import_core12 = require("@genart-dev/core");
|
|
3747
|
+
var registry4 = (0, import_core12.createDefaultRegistry)();
|
|
3748
|
+
async function validateOutputPath(outputPath) {
|
|
3749
|
+
const parentDir = (0, import_path10.dirname)(outputPath);
|
|
3750
|
+
try {
|
|
3751
|
+
const s = await (0, import_promises10.stat)(parentDir);
|
|
3752
|
+
if (!s.isDirectory()) {
|
|
3753
|
+
throw new Error(`Parent directory does not exist: ${parentDir}`);
|
|
3754
|
+
}
|
|
3755
|
+
} catch (e) {
|
|
3756
|
+
if (e instanceof Error && e.message.startsWith("Parent directory")) throw e;
|
|
3757
|
+
throw new Error(`Parent directory does not exist: ${parentDir}`);
|
|
3758
|
+
}
|
|
3759
|
+
try {
|
|
3760
|
+
await (0, import_promises10.stat)(outputPath);
|
|
3761
|
+
throw new Error(
|
|
3762
|
+
`File already exists at ${outputPath}. Delete it first or use a different path.`
|
|
3763
|
+
);
|
|
3764
|
+
} catch (e) {
|
|
3765
|
+
if (e instanceof Error && e.message.startsWith("File already exists")) throw e;
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
function algorithmExtension(rendererType) {
|
|
3769
|
+
return rendererType === "glsl" ? ".glsl" : ".js";
|
|
3770
|
+
}
|
|
3771
|
+
function applyOverrides2(sketch, overrides) {
|
|
3772
|
+
if (overrides.seed === void 0 && overrides.params === void 0) {
|
|
3773
|
+
return sketch;
|
|
3774
|
+
}
|
|
3775
|
+
const newState = {
|
|
3776
|
+
seed: overrides.seed ?? sketch.state.seed,
|
|
3777
|
+
params: overrides.params ? { ...sketch.state.params, ...overrides.params } : sketch.state.params,
|
|
3778
|
+
colorPalette: sketch.state.colorPalette
|
|
3779
|
+
};
|
|
3780
|
+
return { ...sketch, state: newState };
|
|
3781
|
+
}
|
|
3782
|
+
async function exportSketch(state, input) {
|
|
3783
|
+
state.requireWorkspace();
|
|
3784
|
+
const loaded = state.requireSketch(input.sketchId);
|
|
3785
|
+
const sketch = applyOverrides2(loaded.definition, {
|
|
3786
|
+
seed: input.seed,
|
|
3787
|
+
params: input.params
|
|
3788
|
+
});
|
|
3789
|
+
await validateOutputPath(input.outputPath);
|
|
3790
|
+
const adapter = registry4.resolve(sketch.renderer.type);
|
|
3791
|
+
if (!adapter) {
|
|
3792
|
+
throw new Error(
|
|
3793
|
+
`Unsupported renderer type: '${sketch.renderer.type}'`
|
|
3794
|
+
);
|
|
3795
|
+
}
|
|
3796
|
+
switch (input.format) {
|
|
3797
|
+
case "html":
|
|
3798
|
+
return await exportHtml(sketch, input.outputPath);
|
|
3799
|
+
case "png":
|
|
3800
|
+
return await exportPng(sketch, input);
|
|
3801
|
+
case "svg":
|
|
3802
|
+
return await exportSvg(sketch, input);
|
|
3803
|
+
case "algorithm":
|
|
3804
|
+
return await exportAlgorithm(sketch, input.outputPath);
|
|
3805
|
+
case "zip":
|
|
3806
|
+
return await exportZip(sketch, input);
|
|
3807
|
+
default:
|
|
3808
|
+
throw new Error(`Unsupported export format: '${input.format}'`);
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
async function exportHtml(sketch, outputPath) {
|
|
3812
|
+
const adapter = registry4.resolve(sketch.renderer.type);
|
|
3813
|
+
const html = adapter.generateStandaloneHTML(sketch);
|
|
3814
|
+
const content = Buffer.from(html, "utf-8");
|
|
3815
|
+
await (0, import_promises10.writeFile)(outputPath, content);
|
|
3816
|
+
return {
|
|
3817
|
+
success: true,
|
|
3818
|
+
sketchId: sketch.id,
|
|
3819
|
+
format: "html",
|
|
3820
|
+
outputPath,
|
|
3821
|
+
fileSize: content.byteLength,
|
|
3822
|
+
renderer: sketch.renderer.type
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3825
|
+
async function exportPng(sketch, input) {
|
|
3826
|
+
const adapter = registry4.resolve(sketch.renderer.type);
|
|
3827
|
+
const html = adapter.generateStandaloneHTML(sketch);
|
|
3828
|
+
const width = input.width ?? sketch.canvas.width;
|
|
3829
|
+
const height = input.height ?? sketch.canvas.height;
|
|
3830
|
+
const result = await captureHtml({ html, width, height });
|
|
3831
|
+
await (0, import_promises10.writeFile)(input.outputPath, result.bytes);
|
|
3832
|
+
return {
|
|
3833
|
+
success: true,
|
|
3834
|
+
sketchId: sketch.id,
|
|
3835
|
+
format: "png",
|
|
3836
|
+
outputPath: input.outputPath,
|
|
3837
|
+
fileSize: result.bytes.byteLength,
|
|
3838
|
+
renderer: sketch.renderer.type
|
|
3839
|
+
};
|
|
3840
|
+
}
|
|
3841
|
+
async function exportSvg(sketch, input) {
|
|
3842
|
+
const width = input.width ?? sketch.canvas.width;
|
|
3843
|
+
const height = input.height ?? sketch.canvas.height;
|
|
3844
|
+
if (sketch.renderer.type === "svg") {
|
|
3845
|
+
const content2 = Buffer.from(sketch.algorithm, "utf-8");
|
|
3846
|
+
await (0, import_promises10.writeFile)(input.outputPath, content2);
|
|
3847
|
+
return {
|
|
3848
|
+
success: true,
|
|
3849
|
+
sketchId: sketch.id,
|
|
3850
|
+
format: "svg",
|
|
3851
|
+
outputPath: input.outputPath,
|
|
3852
|
+
fileSize: content2.byteLength,
|
|
3853
|
+
renderer: sketch.renderer.type,
|
|
3854
|
+
notice: null
|
|
3855
|
+
};
|
|
3856
|
+
}
|
|
3857
|
+
const adapter = registry4.resolve(sketch.renderer.type);
|
|
3858
|
+
const html = adapter.generateStandaloneHTML(sketch);
|
|
3859
|
+
const result = await captureHtml({ html, width, height });
|
|
3860
|
+
const b64 = Buffer.from(result.bytes).toString("base64");
|
|
3861
|
+
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3862
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
3863
|
+
width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
|
|
3864
|
+
<image width="${width}" height="${height}"
|
|
3865
|
+
href="data:image/png;base64,${b64}"/>
|
|
3866
|
+
</svg>`;
|
|
3867
|
+
const content = Buffer.from(svg, "utf-8");
|
|
3868
|
+
await (0, import_promises10.writeFile)(input.outputPath, content);
|
|
3869
|
+
return {
|
|
3870
|
+
success: true,
|
|
3871
|
+
sketchId: sketch.id,
|
|
3872
|
+
format: "svg",
|
|
3873
|
+
outputPath: input.outputPath,
|
|
3874
|
+
fileSize: content.byteLength,
|
|
3875
|
+
renderer: sketch.renderer.type,
|
|
3876
|
+
notice: "Non-SVG renderer \u2014 rasterized PNG embedded in SVG container"
|
|
3877
|
+
};
|
|
3878
|
+
}
|
|
3879
|
+
async function exportAlgorithm(sketch, outputPath) {
|
|
3880
|
+
const content = Buffer.from(sketch.algorithm, "utf-8");
|
|
3881
|
+
await (0, import_promises10.writeFile)(outputPath, content);
|
|
3882
|
+
return {
|
|
3883
|
+
success: true,
|
|
3884
|
+
sketchId: sketch.id,
|
|
3885
|
+
format: "algorithm",
|
|
3886
|
+
outputPath,
|
|
3887
|
+
fileSize: content.byteLength,
|
|
3888
|
+
renderer: sketch.renderer.type
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
async function exportZip(sketch, input) {
|
|
3892
|
+
const adapter = registry4.resolve(sketch.renderer.type);
|
|
3893
|
+
const width = input.width ?? sketch.canvas.width;
|
|
3894
|
+
const height = input.height ?? sketch.canvas.height;
|
|
3895
|
+
const html = adapter.generateStandaloneHTML(sketch);
|
|
3896
|
+
const genartJson = (0, import_core12.serializeGenart)(sketch);
|
|
3897
|
+
const algorithm = sketch.algorithm;
|
|
3898
|
+
const algExt = algorithmExtension(sketch.renderer.type);
|
|
3899
|
+
const captureResult = await captureHtml({ html, width, height });
|
|
3900
|
+
const output = (0, import_fs.createWriteStream)(input.outputPath);
|
|
3901
|
+
const archive = (0, import_archiver.default)("zip", { zlib: { level: 9 } });
|
|
3902
|
+
const finished = new Promise((resolve5, reject) => {
|
|
3903
|
+
output.on("close", resolve5);
|
|
3904
|
+
archive.on("error", reject);
|
|
3905
|
+
});
|
|
3906
|
+
archive.pipe(output);
|
|
3907
|
+
archive.append(html, { name: `${sketch.id}.html` });
|
|
3908
|
+
archive.append(Buffer.from(captureResult.bytes), { name: `${sketch.id}.png` });
|
|
3909
|
+
archive.append(algorithm, { name: `${sketch.id}${algExt}` });
|
|
3910
|
+
archive.append(genartJson, { name: `${sketch.id}.genart` });
|
|
3911
|
+
await archive.finalize();
|
|
3912
|
+
await finished;
|
|
3913
|
+
const s = await (0, import_promises10.stat)(input.outputPath);
|
|
3914
|
+
return {
|
|
3915
|
+
success: true,
|
|
3916
|
+
sketchId: sketch.id,
|
|
3917
|
+
format: "zip",
|
|
3918
|
+
outputPath: input.outputPath,
|
|
3919
|
+
fileSize: s.size,
|
|
3920
|
+
renderer: sketch.renderer.type,
|
|
3921
|
+
contents: [
|
|
3922
|
+
`${sketch.id}.html`,
|
|
3923
|
+
`${sketch.id}.png`,
|
|
3924
|
+
`${sketch.id}${algExt}`,
|
|
3925
|
+
`${sketch.id}.genart`
|
|
3926
|
+
]
|
|
3927
|
+
};
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
// src/tools/design.ts
|
|
3931
|
+
function requireSketchId(state, args) {
|
|
3932
|
+
return args.sketchId ?? state.requireSelectedSketchId();
|
|
3933
|
+
}
|
|
3934
|
+
var BLEND_MODES = [
|
|
3935
|
+
"normal",
|
|
3936
|
+
"multiply",
|
|
3937
|
+
"screen",
|
|
3938
|
+
"overlay",
|
|
3939
|
+
"darken",
|
|
3940
|
+
"lighten",
|
|
3941
|
+
"color-dodge",
|
|
3942
|
+
"color-burn",
|
|
3943
|
+
"hard-light",
|
|
3944
|
+
"soft-light",
|
|
3945
|
+
"difference",
|
|
3946
|
+
"exclusion",
|
|
3947
|
+
"hue",
|
|
3948
|
+
"saturation",
|
|
3949
|
+
"color",
|
|
2718
3950
|
"luminosity"
|
|
2719
3951
|
];
|
|
2720
3952
|
function generateLayerId() {
|
|
@@ -2724,8 +3956,8 @@ async function designAddLayer(state, args) {
|
|
|
2724
3956
|
const sketchId = requireSketchId(state, args);
|
|
2725
3957
|
const loaded = state.requireSketch(sketchId);
|
|
2726
3958
|
const stack = state.getLayerStack(sketchId);
|
|
2727
|
-
const
|
|
2728
|
-
const layerTypeDef =
|
|
3959
|
+
const registry5 = state.pluginRegistry;
|
|
3960
|
+
const layerTypeDef = registry5?.resolveLayerType(args.type);
|
|
2729
3961
|
if (!layerTypeDef) {
|
|
2730
3962
|
throw new Error(
|
|
2731
3963
|
`Unknown layer type: '${args.type}'. Use design_list_layers types from registered plugins.`
|
|
@@ -2832,10 +4064,7 @@ async function designUpdateLayer(state, args) {
|
|
|
2832
4064
|
stack.updateProperties(args.layerId, updates);
|
|
2833
4065
|
}
|
|
2834
4066
|
if (args.name !== void 0) {
|
|
2835
|
-
|
|
2836
|
-
stack.updateProperties(args.layerId, { ...current.properties });
|
|
2837
|
-
const mutableLayer = stack.get(args.layerId);
|
|
2838
|
-
mutableLayer.name = args.name;
|
|
4067
|
+
stack.updateMeta(args.layerId, { name: args.name });
|
|
2839
4068
|
}
|
|
2840
4069
|
await state.saveSketch(sketchId);
|
|
2841
4070
|
return { updated: true, layerId: args.layerId, sketchId };
|
|
@@ -2933,9 +4162,7 @@ async function designToggleVisibility(state, args) {
|
|
|
2933
4162
|
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2934
4163
|
}
|
|
2935
4164
|
const newVisible = args.visible ?? !layer.visible;
|
|
2936
|
-
|
|
2937
|
-
mutableLayer.visible = newVisible;
|
|
2938
|
-
stack.updateProperties(args.layerId, { ...layer.properties });
|
|
4165
|
+
stack.updateMeta(args.layerId, { visible: newVisible });
|
|
2939
4166
|
await state.saveSketch(sketchId);
|
|
2940
4167
|
return {
|
|
2941
4168
|
layerId: args.layerId,
|
|
@@ -2951,9 +4178,7 @@ async function designLockLayer(state, args) {
|
|
|
2951
4178
|
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2952
4179
|
}
|
|
2953
4180
|
const newLocked = args.locked ?? !layer.locked;
|
|
2954
|
-
|
|
2955
|
-
mutableLayer.locked = newLocked;
|
|
2956
|
-
stack.updateProperties(args.layerId, { ...layer.properties });
|
|
4181
|
+
stack.updateMeta(args.layerId, { locked: newLocked });
|
|
2957
4182
|
await state.saveSketch(sketchId);
|
|
2958
4183
|
return {
|
|
2959
4184
|
layerId: args.layerId,
|
|
@@ -2974,8 +4199,8 @@ async function designCaptureComposite(state, args) {
|
|
|
2974
4199
|
}
|
|
2975
4200
|
|
|
2976
4201
|
// src/tools/design-plugins.ts
|
|
2977
|
-
function registerPluginMcpTools(server,
|
|
2978
|
-
for (const tool of
|
|
4202
|
+
function registerPluginMcpTools(server, registry5, state) {
|
|
4203
|
+
for (const tool of registry5.getMcpTools()) {
|
|
2979
4204
|
const inputSchema = tool.definition.inputSchema;
|
|
2980
4205
|
server.tool(
|
|
2981
4206
|
tool.name,
|
|
@@ -3020,7 +4245,7 @@ function registerPluginMcpTools(server, registry4, state) {
|
|
|
3020
4245
|
}
|
|
3021
4246
|
|
|
3022
4247
|
// src/resources/index.ts
|
|
3023
|
-
var
|
|
4248
|
+
var import_core13 = require("@genart-dev/core");
|
|
3024
4249
|
function registerResources(server, state) {
|
|
3025
4250
|
registerSkillsResource(server);
|
|
3026
4251
|
registerCanvasPresetsResource(server);
|
|
@@ -3028,7 +4253,7 @@ function registerResources(server, state) {
|
|
|
3028
4253
|
registerRenderersResource(server);
|
|
3029
4254
|
}
|
|
3030
4255
|
function registerSkillsResource(server) {
|
|
3031
|
-
const skillRegistry = (0,
|
|
4256
|
+
const skillRegistry = (0, import_core13.createDefaultSkillRegistry)();
|
|
3032
4257
|
server.resource(
|
|
3033
4258
|
"skills",
|
|
3034
4259
|
"genart://skills",
|
|
@@ -3079,7 +4304,7 @@ function registerCanvasPresetsResource(server) {
|
|
|
3079
4304
|
mimeType: "application/json",
|
|
3080
4305
|
text: JSON.stringify(
|
|
3081
4306
|
{
|
|
3082
|
-
presets:
|
|
4307
|
+
presets: import_core13.CANVAS_PRESETS.map((p) => ({
|
|
3083
4308
|
id: p.id,
|
|
3084
4309
|
label: p.label,
|
|
3085
4310
|
category: p.category,
|
|
@@ -3136,7 +4361,7 @@ function registerGalleryResource(server, state) {
|
|
|
3136
4361
|
);
|
|
3137
4362
|
}
|
|
3138
4363
|
function registerRenderersResource(server) {
|
|
3139
|
-
const
|
|
4364
|
+
const registry5 = (0, import_core13.createDefaultRegistry)();
|
|
3140
4365
|
server.resource(
|
|
3141
4366
|
"renderers",
|
|
3142
4367
|
"genart://renderers",
|
|
@@ -3145,9 +4370,9 @@ function registerRenderersResource(server) {
|
|
|
3145
4370
|
mimeType: "application/json"
|
|
3146
4371
|
},
|
|
3147
4372
|
async () => {
|
|
3148
|
-
const types =
|
|
4373
|
+
const types = registry5.list();
|
|
3149
4374
|
const renderers = types.map((type) => {
|
|
3150
|
-
const adapter =
|
|
4375
|
+
const adapter = registry5.resolve(type);
|
|
3151
4376
|
return {
|
|
3152
4377
|
type: adapter.type,
|
|
3153
4378
|
displayName: adapter.displayName,
|
|
@@ -3159,7 +4384,7 @@ function registerRenderersResource(server) {
|
|
|
3159
4384
|
}))
|
|
3160
4385
|
};
|
|
3161
4386
|
});
|
|
3162
|
-
const defaultAdapter =
|
|
4387
|
+
const defaultAdapter = registry5.getDefault();
|
|
3163
4388
|
return {
|
|
3164
4389
|
contents: [
|
|
3165
4390
|
{
|
|
@@ -3186,6 +4411,9 @@ function registerPrompts(server, state) {
|
|
|
3186
4411
|
registerCreateGenerativeArt(server);
|
|
3187
4412
|
registerExploreVariations(server, state);
|
|
3188
4413
|
registerApplyDesignTheory(server, state);
|
|
4414
|
+
registerCritiqueAndIterate(server, state);
|
|
4415
|
+
registerDevelopArtisticConcept(server, state);
|
|
4416
|
+
registerStudyReference(server, state);
|
|
3189
4417
|
}
|
|
3190
4418
|
function registerCreateGenerativeArt(server) {
|
|
3191
4419
|
server.prompt(
|
|
@@ -3466,21 +4694,200 @@ function registerApplyDesignTheory(server, state) {
|
|
|
3466
4694
|
content: {
|
|
3467
4695
|
type: "text",
|
|
3468
4696
|
text: [
|
|
3469
|
-
`Apply design theory to improve a generative art sketch.`,
|
|
4697
|
+
`Apply design theory to improve a generative art sketch.`,
|
|
4698
|
+
``,
|
|
4699
|
+
sketchContext,
|
|
4700
|
+
``,
|
|
4701
|
+
theoryGuides[args.theory],
|
|
4702
|
+
``,
|
|
4703
|
+
`## Workflow`,
|
|
4704
|
+
`1. Use \`get_selection\` or \`open_sketch\` to examine the current sketch`,
|
|
4705
|
+
`2. Analyze how the theory applies to the existing algorithm`,
|
|
4706
|
+
`3. Use \`fork_sketch\` to create a theory-applied variant`,
|
|
4707
|
+
`4. Modify the fork's algorithm and parameters using the theory principles above`,
|
|
4708
|
+
`5. Use \`capture_screenshot\` to compare before and after`,
|
|
4709
|
+
`6. Update the philosophy field to document the design rationale`,
|
|
4710
|
+
``,
|
|
4711
|
+
`**Attribution:** Always pass your \`agent\` name and \`model\` identifier when calling tools that create or modify sketches.`
|
|
4712
|
+
].join("\n")
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
]
|
|
4716
|
+
};
|
|
4717
|
+
}
|
|
4718
|
+
);
|
|
4719
|
+
}
|
|
4720
|
+
function registerCritiqueAndIterate(server, state) {
|
|
4721
|
+
server.prompt(
|
|
4722
|
+
"critique-and-iterate",
|
|
4723
|
+
"Capture a sketch, self-critique it, identify improvements, fork, apply changes, compare, and document the iteration",
|
|
4724
|
+
{
|
|
4725
|
+
sketchId: import_zod.z.string().describe("ID of the sketch to critique and iterate on"),
|
|
4726
|
+
aspects: import_zod.z.string().optional().describe("Comma-separated aspects to focus on (composition, color, rhythm, unity, expression). Default: all"),
|
|
4727
|
+
iterations: import_zod.z.string().optional().describe("Number of improvement iterations (default: 1)")
|
|
4728
|
+
},
|
|
4729
|
+
async (args) => {
|
|
4730
|
+
const sketch = state.getSketch(args.sketchId);
|
|
4731
|
+
const iterations = args.iterations ? parseInt(args.iterations, 10) : 1;
|
|
4732
|
+
const aspectList = args.aspects ? args.aspects.split(",").map((a) => a.trim()) : ["composition", "color", "rhythm", "unity", "expression"];
|
|
4733
|
+
let sketchContext = "";
|
|
4734
|
+
if (sketch) {
|
|
4735
|
+
const def = sketch.definition;
|
|
4736
|
+
sketchContext = [
|
|
4737
|
+
`## Current Sketch: "${def.title}"`,
|
|
4738
|
+
`- **ID:** ${def.id}`,
|
|
4739
|
+
`- **Renderer:** ${def.renderer.type}`,
|
|
4740
|
+
`- **Canvas:** ${def.canvas.width}\xD7${def.canvas.height}`,
|
|
4741
|
+
`- **Composition Level:** ${def.compositionLevel ?? "sketch"}`,
|
|
4742
|
+
def.philosophy ? `- **Philosophy:** ${def.philosophy}` : `- **Philosophy:** not set`,
|
|
4743
|
+
`- **Parameters:** ${def.parameters?.length ?? 0} defined`,
|
|
4744
|
+
`- **Colors:** ${def.colors?.length ?? 0} defined`
|
|
4745
|
+
].join("\n");
|
|
4746
|
+
} else {
|
|
4747
|
+
sketchContext = `## Sketch: ${args.sketchId}
|
|
4748
|
+
*(Not currently loaded \u2014 use open_sketch first)*`;
|
|
4749
|
+
}
|
|
4750
|
+
return {
|
|
4751
|
+
messages: [
|
|
4752
|
+
{
|
|
4753
|
+
role: "user",
|
|
4754
|
+
content: {
|
|
4755
|
+
type: "text",
|
|
4756
|
+
text: [
|
|
4757
|
+
`Perform a structured critique-and-iterate cycle on a generative art sketch.`,
|
|
3470
4758
|
``,
|
|
3471
4759
|
sketchContext,
|
|
3472
4760
|
``,
|
|
3473
|
-
|
|
4761
|
+
`## Focus Aspects`,
|
|
4762
|
+
aspectList.map((a) => `- ${a}`).join("\n"),
|
|
3474
4763
|
``,
|
|
3475
|
-
`##
|
|
3476
|
-
`1. Use \`get_selection\` or \`open_sketch\` to examine the current sketch`,
|
|
3477
|
-
`2. Analyze how the theory applies to the existing algorithm`,
|
|
3478
|
-
`3. Use \`fork_sketch\` to create a theory-applied variant`,
|
|
3479
|
-
`4. Modify the fork's algorithm and parameters using the theory principles above`,
|
|
3480
|
-
`5. Use \`capture_screenshot\` to compare before and after`,
|
|
3481
|
-
`6. Update the philosophy field to document the design rationale`,
|
|
4764
|
+
`## Iterations: ${iterations}`,
|
|
3482
4765
|
``,
|
|
3483
|
-
|
|
4766
|
+
`## Process`,
|
|
4767
|
+
``,
|
|
4768
|
+
`For each iteration:`,
|
|
4769
|
+
``,
|
|
4770
|
+
`### Step 1: Capture & Critique`,
|
|
4771
|
+
`1. Use \`critique_sketch\` with sketchId="${args.sketchId}" and aspects=[${aspectList.map((a) => `"${a}"`).join(", ")}]`,
|
|
4772
|
+
`2. Study the returned screenshot carefully`,
|
|
4773
|
+
`3. Answer each framework question honestly \u2014 what works and what doesn't`,
|
|
4774
|
+
`4. Note the severity calibration for this composition level`,
|
|
4775
|
+
``,
|
|
4776
|
+
`### Step 2: Identify Improvements`,
|
|
4777
|
+
`Based on the critique, identify 2-3 specific, actionable improvements:`,
|
|
4778
|
+
`- Rank them by expected visual impact`,
|
|
4779
|
+
`- Be precise: "shift the focal cluster from center to upper-left third" not "improve composition"`,
|
|
4780
|
+
`- Consider which improvements can be achieved via parameter changes vs algorithm changes`,
|
|
4781
|
+
``,
|
|
4782
|
+
`### Step 3: Fork & Apply`,
|
|
4783
|
+
`1. Use \`fork_sketch\` to create a new version (preserve the original for comparison)`,
|
|
4784
|
+
`2. Apply the identified improvements:`,
|
|
4785
|
+
` - Use \`set_parameters\` or \`set_colors\` for parameter-level changes`,
|
|
4786
|
+
` - Use \`update_algorithm\` for algorithmic changes`,
|
|
4787
|
+
`3. Use \`capture_screenshot\` to verify each change visually`,
|
|
4788
|
+
``,
|
|
4789
|
+
`### Step 4: Compare`,
|
|
4790
|
+
`1. Use \`compare_sketches\` with the original and improved sketch IDs`,
|
|
4791
|
+
`2. Evaluate: did each intended improvement actually improve the piece?`,
|
|
4792
|
+
`3. Note any unintended consequences \u2014 improvements in one aspect sometimes degrade another`,
|
|
4793
|
+
``,
|
|
4794
|
+
`### Step 5: Document`,
|
|
4795
|
+
`After all iterations:`,
|
|
4796
|
+
`1. Update the improved sketch's philosophy field to document what changed and why`,
|
|
4797
|
+
`2. Summarize the iteration journey: what was tried, what worked, what was learned`,
|
|
4798
|
+
`3. If the original was better in some aspects, note what to preserve in future iterations`,
|
|
4799
|
+
``,
|
|
4800
|
+
`## Guidelines`,
|
|
4801
|
+
`- Be your own harshest (but fairest) critic \u2014 the goal is genuine improvement`,
|
|
4802
|
+
`- Small, focused changes are better than sweeping rewrites`,
|
|
4803
|
+
`- If a change doesn't work, revert it before trying the next improvement`,
|
|
4804
|
+
`- The final piece should feel like a natural evolution, not a different sketch`,
|
|
4805
|
+
`- Always pass your \`agent\` name and \`model\` identifier when calling tools that create or modify sketches`
|
|
4806
|
+
].join("\n")
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
]
|
|
4810
|
+
};
|
|
4811
|
+
}
|
|
4812
|
+
);
|
|
4813
|
+
}
|
|
4814
|
+
function registerDevelopArtisticConcept(server, _state) {
|
|
4815
|
+
server.prompt(
|
|
4816
|
+
"develop-artistic-concept",
|
|
4817
|
+
"Develop an artistic concept through a full studio workflow: concept planning, studies, development, critique, iteration, and documentation",
|
|
4818
|
+
{
|
|
4819
|
+
concept: import_zod.z.string().describe("The artistic concept or theme to explore"),
|
|
4820
|
+
medium: import_zod.z.enum(["p5", "three", "glsl", "canvas2d", "svg"]).optional().describe("Preferred renderer/medium (default: p5)"),
|
|
4821
|
+
depth: import_zod.z.enum(["quick", "standard", "deep"]).optional().describe("How deeply to explore: quick (3 studies), standard (6 studies), deep (9+ studies). Default: standard")
|
|
4822
|
+
},
|
|
4823
|
+
async (args) => {
|
|
4824
|
+
const medium = args.medium ?? "p5";
|
|
4825
|
+
const depth = args.depth ?? "standard";
|
|
4826
|
+
const studyCount = depth === "quick" ? 3 : depth === "deep" ? 9 : 6;
|
|
4827
|
+
return {
|
|
4828
|
+
messages: [
|
|
4829
|
+
{
|
|
4830
|
+
role: "user",
|
|
4831
|
+
content: {
|
|
4832
|
+
type: "text",
|
|
4833
|
+
text: [
|
|
4834
|
+
`Develop the following artistic concept through a full studio workflow.`,
|
|
4835
|
+
``,
|
|
4836
|
+
`## Concept`,
|
|
4837
|
+
`${args.concept}`,
|
|
4838
|
+
``,
|
|
4839
|
+
`## Medium: ${medium}`,
|
|
4840
|
+
`## Depth: ${depth} (${studyCount} studies)`,
|
|
4841
|
+
``,
|
|
4842
|
+
`## Phase 1: Conceptual Planning`,
|
|
4843
|
+
`1. Use \`develop_concept\` with your concept and medium to get a structured plan`,
|
|
4844
|
+
`2. Define: mood, color strategy, compositional approach, and relevant skills`,
|
|
4845
|
+
`3. Create a series with \`create_series\` \u2014 write a narrative and intent statement`,
|
|
4846
|
+
``,
|
|
4847
|
+
`## Phase 2: Thumbnail Studies`,
|
|
4848
|
+
`1. Create ${studyCount} quick study-level sketches with \`create_sketch\` (compositionLevel: "study")`,
|
|
4849
|
+
`2. Each study should explore a different aspect of the concept:`,
|
|
4850
|
+
` - Vary composition (centered vs asymmetric vs edge-driven)`,
|
|
4851
|
+
` - Vary color (warm vs cool, saturated vs muted)`,
|
|
4852
|
+
` - Vary rhythm (regular vs progressive vs chaotic)`,
|
|
4853
|
+
` - Vary density (sparse vs dense vs gradient)`,
|
|
4854
|
+
`3. Use small canvases (600x600 or similar) \u2014 studies are fast explorations`,
|
|
4855
|
+
`4. Use \`capture_batch\` to see all studies at once`,
|
|
4856
|
+
``,
|
|
4857
|
+
`## Phase 3: Selection & Critique`,
|
|
4858
|
+
`1. Use \`series_summary\` to see the full set of studies with screenshots`,
|
|
4859
|
+
`2. Use \`critique_sketch\` on the 2-3 most promising studies`,
|
|
4860
|
+
`3. Identify which studies best capture the concept's intent`,
|
|
4861
|
+
`4. Note what works in each \u2014 composition choices, color relationships, rhythmic qualities`,
|
|
4862
|
+
``,
|
|
4863
|
+
`## Phase 4: Development`,
|
|
4864
|
+
`1. Use \`promote_sketch\` to advance the best 1-2 studies to "drafts" stage`,
|
|
4865
|
+
`2. Refine the promoted sketches:`,
|
|
4866
|
+
` - Add more parameters for fine control`,
|
|
4867
|
+
` - Develop the color palette with more nuance`,
|
|
4868
|
+
` - Strengthen compositional structure`,
|
|
4869
|
+
` - Load relevant skills with \`load_skill\` for guidance`,
|
|
4870
|
+
`3. Use \`critique_sketch\` after each round of changes`,
|
|
4871
|
+
``,
|
|
4872
|
+
`## Phase 5: Critique & Iteration`,
|
|
4873
|
+
`1. Use \`compare_sketches\` to evaluate drafts against each other`,
|
|
4874
|
+
`2. For the strongest draft, use the critique-and-iterate workflow:`,
|
|
4875
|
+
` - Critique \u2192 identify improvements \u2192 fork \u2192 apply \u2192 compare`,
|
|
4876
|
+
`3. Promote the best iteration to "refinements" stage`,
|
|
4877
|
+
`4. Continue refining until the piece feels resolved`,
|
|
4878
|
+
``,
|
|
4879
|
+
`## Phase 6: Final & Documentation`,
|
|
4880
|
+
`1. Promote the best refinement to "finals" stage (canvas will upscale)`,
|
|
4881
|
+
`2. Update the philosophy field with the full artistic statement`,
|
|
4882
|
+
`3. Use \`series_summary\` to capture the complete progression`,
|
|
4883
|
+
`4. Document: what was the concept? How did it evolve? What was discovered?`,
|
|
4884
|
+
``,
|
|
4885
|
+
`## Guidelines`,
|
|
4886
|
+
`- Each phase should feel like a natural progression, not a checklist`,
|
|
4887
|
+
`- Trust the studies \u2014 let unexpected results redirect the exploration`,
|
|
4888
|
+
`- The final piece should feel inevitable, like it couldn't have been any other way`,
|
|
4889
|
+
`- Always pass your \`agent\` name and \`model\` identifier when calling tools`,
|
|
4890
|
+
`- Use \`auto_arrange\` periodically to keep the workspace organized`
|
|
3484
4891
|
].join("\n")
|
|
3485
4892
|
}
|
|
3486
4893
|
}
|
|
@@ -3489,6 +4896,83 @@ function registerApplyDesignTheory(server, state) {
|
|
|
3489
4896
|
}
|
|
3490
4897
|
);
|
|
3491
4898
|
}
|
|
4899
|
+
function registerStudyReference(server, _state) {
|
|
4900
|
+
server.prompt(
|
|
4901
|
+
"study-reference",
|
|
4902
|
+
"Study a reference image: analyze it, identify key qualities, create a generative study sketch inspired by it, and document learnings",
|
|
4903
|
+
{
|
|
4904
|
+
referenceId: import_zod.z.string().describe("ID of the reference to study"),
|
|
4905
|
+
seriesId: import_zod.z.string().optional().describe("Series the reference belongs to (also where the study sketch will be added)"),
|
|
4906
|
+
sketchId: import_zod.z.string().optional().describe("Sketch the reference belongs to"),
|
|
4907
|
+
medium: import_zod.z.enum(["p5", "three", "glsl", "canvas2d", "svg"]).optional().describe("Renderer for the study sketch (default: p5)"),
|
|
4908
|
+
focus: import_zod.z.string().optional().describe("Specific quality to focus on: composition, palette, rhythm, mood, technique, or a custom focus")
|
|
4909
|
+
},
|
|
4910
|
+
async (args) => {
|
|
4911
|
+
const medium = args.medium ?? "p5";
|
|
4912
|
+
const focus = args.focus ?? "all key qualities";
|
|
4913
|
+
return {
|
|
4914
|
+
messages: [
|
|
4915
|
+
{
|
|
4916
|
+
role: "user",
|
|
4917
|
+
content: {
|
|
4918
|
+
type: "text",
|
|
4919
|
+
text: [
|
|
4920
|
+
`Study a reference image and create a generative art sketch inspired by it.`,
|
|
4921
|
+
``,
|
|
4922
|
+
`## Reference: ${args.referenceId}`,
|
|
4923
|
+
args.seriesId ? `## Series: ${args.seriesId}` : "",
|
|
4924
|
+
`## Medium: ${medium}`,
|
|
4925
|
+
`## Focus: ${focus}`,
|
|
4926
|
+
``,
|
|
4927
|
+
`## Phase 1: Analyze the Reference`,
|
|
4928
|
+
`1. Use \`analyze_reference\` with referenceId="${args.referenceId}"${args.seriesId ? ` seriesId="${args.seriesId}"` : ""}${args.sketchId ? ` sketchId="${args.sketchId}"` : ""} to get the analysis framework and image`,
|
|
4929
|
+
`2. Study the image carefully using the framework prompts`,
|
|
4930
|
+
`3. Answer each category: composition, palette, rhythm, mood, technique`,
|
|
4931
|
+
`4. Use \`update_reference_analysis\` to save your structured analysis`,
|
|
4932
|
+
``,
|
|
4933
|
+
`## Phase 2: Extract Key Qualities`,
|
|
4934
|
+
`From your analysis, identify 2-4 key qualities that are most interesting for generative art:`,
|
|
4935
|
+
`- These could be: a specific compositional structure, a color relationship, a rhythmic pattern, a mood quality`,
|
|
4936
|
+
`- Focus on qualities that can be *translated* into code, not literally replicated`,
|
|
4937
|
+
`- Consider what makes this reference compelling \u2014 what would be lost if you removed each quality?`,
|
|
4938
|
+
``,
|
|
4939
|
+
`## Phase 3: Extract Palette`,
|
|
4940
|
+
`1. Use \`extract_palette\` to study the reference's color strategy`,
|
|
4941
|
+
`2. Extract 5-8 hex colors that capture the essential palette`,
|
|
4942
|
+
`3. Save the palette in the reference analysis`,
|
|
4943
|
+
``,
|
|
4944
|
+
`## Phase 4: Create Study Sketch`,
|
|
4945
|
+
`1. Use \`create_sketch\` with compositionLevel: "study" to create a quick exploration`,
|
|
4946
|
+
`2. Translate the key qualities into generative parameters and algorithm choices:`,
|
|
4947
|
+
` - Composition \u2192 element placement, density distribution, negative space`,
|
|
4948
|
+
` - Palette \u2192 color definitions, themes derived from the reference palette`,
|
|
4949
|
+
` - Rhythm \u2192 repetition patterns, interval variations, scale relationships`,
|
|
4950
|
+
` - Mood \u2192 overall tone, animation speed, mark quality`,
|
|
4951
|
+
` - Technique \u2192 rendering approach, layering, transparency`,
|
|
4952
|
+
`3. Add the reference to the sketch with \`add_reference\``,
|
|
4953
|
+
`4. Document in the philosophy field how the reference influenced the study`,
|
|
4954
|
+
`5. Use \`capture_screenshot\` to verify the result`,
|
|
4955
|
+
``,
|
|
4956
|
+
`## Phase 5: Compare & Document`,
|
|
4957
|
+
`1. Use \`analyze_reference\` again to see the reference alongside your study`,
|
|
4958
|
+
`2. Evaluate: which qualities translated well? Which were lost or transformed?`,
|
|
4959
|
+
`3. Note what you learned \u2014 what worked, what surprised you, what to try next`,
|
|
4960
|
+
`4. Update the study sketch's philosophy with these insights`,
|
|
4961
|
+
``,
|
|
4962
|
+
`## Guidelines`,
|
|
4963
|
+
`- The goal is *inspiration*, not replication \u2014 a study should be recognizably generative`,
|
|
4964
|
+
`- A good study captures the *spirit* of the reference while being authentically algorithmic`,
|
|
4965
|
+
`- Use small canvases (600x600) \u2014 studies are explorations, not finished pieces`,
|
|
4966
|
+
`- If the reference suggests multiple interesting directions, create multiple studies`,
|
|
4967
|
+
`- Always pass your \`agent\` name and \`model\` identifier when calling tools`
|
|
4968
|
+
].filter(Boolean).join("\n")
|
|
4969
|
+
}
|
|
4970
|
+
}
|
|
4971
|
+
]
|
|
4972
|
+
};
|
|
4973
|
+
}
|
|
4974
|
+
);
|
|
4975
|
+
}
|
|
3492
4976
|
|
|
3493
4977
|
// src/server.ts
|
|
3494
4978
|
function jsonResult(data) {
|
|
@@ -3503,22 +4987,36 @@ function toolError(message) {
|
|
|
3503
4987
|
};
|
|
3504
4988
|
}
|
|
3505
4989
|
async function initializePluginRegistry() {
|
|
3506
|
-
const
|
|
4990
|
+
const registry5 = (0, import_core14.createPluginRegistry)({
|
|
3507
4991
|
surface: "mcp",
|
|
3508
4992
|
supportsInteractiveTools: false,
|
|
3509
4993
|
supportsRendering: false
|
|
3510
4994
|
});
|
|
3511
|
-
await
|
|
3512
|
-
await
|
|
3513
|
-
await
|
|
3514
|
-
await
|
|
3515
|
-
|
|
4995
|
+
await registry5.register(import_plugin_typography.default);
|
|
4996
|
+
await registry5.register(import_plugin_filters.default);
|
|
4997
|
+
await registry5.register(import_plugin_shapes.default);
|
|
4998
|
+
await registry5.register(import_plugin_layout_guides.default);
|
|
4999
|
+
await registry5.register(import_plugin_painting.default);
|
|
5000
|
+
await registry5.register(import_plugin_textures.default);
|
|
5001
|
+
await registry5.register(import_plugin_animation.default);
|
|
5002
|
+
await registry5.register(import_plugin_color_adjust.default);
|
|
5003
|
+
await registry5.register(import_plugin_compositing.default);
|
|
5004
|
+
await registry5.register(import_plugin_construction.default);
|
|
5005
|
+
await registry5.register(import_plugin_distribution.default);
|
|
5006
|
+
await registry5.register(import_plugin_figure.default);
|
|
5007
|
+
await registry5.register(import_plugin_layout_composition.default);
|
|
5008
|
+
await registry5.register(import_plugin_perspective.default);
|
|
5009
|
+
await registry5.register(import_plugin_poses.default);
|
|
5010
|
+
await registry5.register(import_plugin_styles.default);
|
|
5011
|
+
await registry5.register(import_plugin_symbols.default);
|
|
5012
|
+
await registry5.register(import_plugin_trace.default);
|
|
5013
|
+
return registry5;
|
|
3516
5014
|
}
|
|
3517
5015
|
function createServer(state) {
|
|
3518
5016
|
const server = new import_mcp.McpServer(
|
|
3519
5017
|
{
|
|
3520
5018
|
name: "@genart/mcp-server",
|
|
3521
|
-
version: "0.
|
|
5019
|
+
version: "0.4.0"
|
|
3522
5020
|
},
|
|
3523
5021
|
{
|
|
3524
5022
|
capabilities: {
|
|
@@ -3528,9 +5026,9 @@ function createServer(state) {
|
|
|
3528
5026
|
}
|
|
3529
5027
|
}
|
|
3530
5028
|
);
|
|
3531
|
-
const registryReady = initializePluginRegistry().then((
|
|
3532
|
-
state.pluginRegistry =
|
|
3533
|
-
registerPluginMcpTools(server,
|
|
5029
|
+
const registryReady = initializePluginRegistry().then((registry5) => {
|
|
5030
|
+
state.pluginRegistry = registry5;
|
|
5031
|
+
registerPluginMcpTools(server, registry5, state);
|
|
3534
5032
|
});
|
|
3535
5033
|
server._pluginsReady = registryReady;
|
|
3536
5034
|
registerWorkspaceTools(server, state);
|
|
@@ -3545,6 +5043,9 @@ function createServer(state) {
|
|
|
3545
5043
|
registerKnowledgeTools(server, state);
|
|
3546
5044
|
registerDesignTools(server, state);
|
|
3547
5045
|
registerCaptureTools(server, state);
|
|
5046
|
+
registerCritiqueTools(server, state);
|
|
5047
|
+
registerSeriesTools(server, state);
|
|
5048
|
+
registerReferenceTools(server, state);
|
|
3548
5049
|
registerExportTools(server, state);
|
|
3549
5050
|
registerResources(server, state);
|
|
3550
5051
|
registerPrompts(server, state);
|
|
@@ -4309,6 +5810,276 @@ function registerCaptureTools(server, state) {
|
|
|
4309
5810
|
}
|
|
4310
5811
|
);
|
|
4311
5812
|
}
|
|
5813
|
+
function registerCritiqueTools(server, state) {
|
|
5814
|
+
server.tool(
|
|
5815
|
+
"critique_sketch",
|
|
5816
|
+
"Capture a sketch screenshot and return a structured self-critique framework (questions, principles, pitfalls) per aspect. Severity calibrates to compositionLevel.",
|
|
5817
|
+
{
|
|
5818
|
+
sketchId: import_zod2.z.string().optional().describe("Sketch to critique (default: selected sketch)"),
|
|
5819
|
+
aspects: import_zod2.z.array(import_zod2.z.enum(["composition", "color", "rhythm", "unity", "expression"])).optional().describe("Aspects to critique (default: all five)"),
|
|
5820
|
+
previewSize: import_zod2.z.number().optional().describe("Max dimension for inline preview JPEG (default: 400)")
|
|
5821
|
+
},
|
|
5822
|
+
async (args) => {
|
|
5823
|
+
try {
|
|
5824
|
+
const result = await critiqueSketch(state, args);
|
|
5825
|
+
return {
|
|
5826
|
+
content: [
|
|
5827
|
+
{ type: "text", text: JSON.stringify(result.metadata, null, 2) },
|
|
5828
|
+
{ type: "image", data: result.previewJpegBase64, mimeType: "image/jpeg" }
|
|
5829
|
+
]
|
|
5830
|
+
};
|
|
5831
|
+
} catch (e) {
|
|
5832
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
5833
|
+
}
|
|
5834
|
+
}
|
|
5835
|
+
);
|
|
5836
|
+
server.tool(
|
|
5837
|
+
"compare_sketches",
|
|
5838
|
+
"Side-by-side capture of 2-4 sketches with a structured comparison framework across specified aspects.",
|
|
5839
|
+
{
|
|
5840
|
+
sketchIds: import_zod2.z.array(import_zod2.z.string()).describe("IDs of 2-4 sketches to compare"),
|
|
5841
|
+
aspects: import_zod2.z.array(import_zod2.z.enum(["composition", "color", "rhythm", "unity", "expression"])).optional().describe("Aspects to compare (default: all five)"),
|
|
5842
|
+
previewSize: import_zod2.z.number().optional().describe("Max dimension for inline preview JPEGs (default: 300)")
|
|
5843
|
+
},
|
|
5844
|
+
async (args) => {
|
|
5845
|
+
try {
|
|
5846
|
+
const result = await compareSketches(state, args);
|
|
5847
|
+
const content = [
|
|
5848
|
+
{ type: "text", text: JSON.stringify(result.metadata, null, 2) }
|
|
5849
|
+
];
|
|
5850
|
+
for (const preview of result.previews) {
|
|
5851
|
+
content.push({
|
|
5852
|
+
type: "text",
|
|
5853
|
+
text: `--- Sketch: ${preview.sketchId} ---`
|
|
5854
|
+
});
|
|
5855
|
+
content.push({
|
|
5856
|
+
type: "image",
|
|
5857
|
+
data: preview.inlineJpegBase64,
|
|
5858
|
+
mimeType: "image/jpeg"
|
|
5859
|
+
});
|
|
5860
|
+
}
|
|
5861
|
+
return { content };
|
|
5862
|
+
} catch (e) {
|
|
5863
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
5864
|
+
}
|
|
5865
|
+
}
|
|
5866
|
+
);
|
|
5867
|
+
}
|
|
5868
|
+
function registerSeriesTools(server, state) {
|
|
5869
|
+
server.tool(
|
|
5870
|
+
"create_series",
|
|
5871
|
+
"Create a new curated series of sketches with narrative, intent, and studio workflow stages",
|
|
5872
|
+
{
|
|
5873
|
+
label: import_zod2.z.string().describe("Display label for the series"),
|
|
5874
|
+
narrative: import_zod2.z.string().describe("Prose narrative describing the artistic exploration"),
|
|
5875
|
+
intent: import_zod2.z.string().describe("Short statement of artistic intent"),
|
|
5876
|
+
progression: import_zod2.z.string().optional().describe(
|
|
5877
|
+
"Series progression type (e.g. 'linear', 'branching', 'iterative')"
|
|
5878
|
+
),
|
|
5879
|
+
stages: import_zod2.z.array(import_zod2.z.enum(["studies", "drafts", "refinements", "finals"])).optional().describe(
|
|
5880
|
+
"Ordered stages in the studio workflow (default: all four)"
|
|
5881
|
+
),
|
|
5882
|
+
sketchFiles: import_zod2.z.array(import_zod2.z.string()).optional().describe("File names of existing sketches to include")
|
|
5883
|
+
},
|
|
5884
|
+
async (args) => {
|
|
5885
|
+
try {
|
|
5886
|
+
const result = await createSeries(state, args);
|
|
5887
|
+
return jsonResult(result);
|
|
5888
|
+
} catch (e) {
|
|
5889
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
5890
|
+
}
|
|
5891
|
+
}
|
|
5892
|
+
);
|
|
5893
|
+
server.tool(
|
|
5894
|
+
"develop_concept",
|
|
5895
|
+
"Generate a structured concept development plan with mood, palette, composition, skills, and series structure recommendations",
|
|
5896
|
+
{
|
|
5897
|
+
concept: import_zod2.z.string().describe("The artistic concept or theme to develop"),
|
|
5898
|
+
medium: import_zod2.z.enum(["p5", "three", "glsl", "canvas2d", "svg"]).optional().describe("Preferred renderer/medium (default: p5)")
|
|
5899
|
+
},
|
|
5900
|
+
async (args) => {
|
|
5901
|
+
try {
|
|
5902
|
+
const result = await developConcept(state, args);
|
|
5903
|
+
return jsonResult(result);
|
|
5904
|
+
} catch (e) {
|
|
5905
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
5906
|
+
}
|
|
5907
|
+
}
|
|
5908
|
+
);
|
|
5909
|
+
server.tool(
|
|
5910
|
+
"series_summary",
|
|
5911
|
+
"Capture all sketches in a series with narrative context for holistic evaluation",
|
|
5912
|
+
{
|
|
5913
|
+
seriesId: import_zod2.z.string().describe("ID of the series to summarize"),
|
|
5914
|
+
captureScreenshots: import_zod2.z.boolean().optional().describe("Capture screenshots of each sketch (default: true)"),
|
|
5915
|
+
previewSize: import_zod2.z.number().optional().describe("Preview image size in pixels (default: 300)")
|
|
5916
|
+
},
|
|
5917
|
+
async (args) => {
|
|
5918
|
+
try {
|
|
5919
|
+
const result = await seriesSummary(state, args);
|
|
5920
|
+
const content = [
|
|
5921
|
+
{ type: "text", text: JSON.stringify(result.metadata, null, 2) }
|
|
5922
|
+
];
|
|
5923
|
+
if (result.previews) {
|
|
5924
|
+
for (const preview of result.previews) {
|
|
5925
|
+
content.push({
|
|
5926
|
+
type: "image",
|
|
5927
|
+
data: preview.inlineJpegBase64,
|
|
5928
|
+
mimeType: "image/jpeg"
|
|
5929
|
+
});
|
|
5930
|
+
}
|
|
5931
|
+
}
|
|
5932
|
+
return { content };
|
|
5933
|
+
} catch (e) {
|
|
5934
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
5935
|
+
}
|
|
5936
|
+
}
|
|
5937
|
+
);
|
|
5938
|
+
server.tool(
|
|
5939
|
+
"promote_sketch",
|
|
5940
|
+
"Promote a sketch to the next studio workflow stage \u2014 fork, upscale canvas, update compositionLevel, and add to series",
|
|
5941
|
+
{
|
|
5942
|
+
sketchId: import_zod2.z.string().describe("ID of the sketch to promote"),
|
|
5943
|
+
toStage: import_zod2.z.enum(["studies", "drafts", "refinements", "finals"]).describe("Target stage to promote to"),
|
|
5944
|
+
seriesId: import_zod2.z.string().optional().describe("Series to add the promoted sketch to"),
|
|
5945
|
+
newId: import_zod2.z.string().optional().describe(
|
|
5946
|
+
"URL-safe kebab-case ID for the promoted sketch (default: auto-generated)"
|
|
5947
|
+
),
|
|
5948
|
+
title: import_zod2.z.string().optional().describe("Title for the promoted sketch (default: auto-generated)"),
|
|
5949
|
+
agent: import_zod2.z.string().optional().describe("CLI agent name"),
|
|
5950
|
+
model: import_zod2.z.string().optional().describe("AI model identifier")
|
|
5951
|
+
},
|
|
5952
|
+
async (args) => {
|
|
5953
|
+
try {
|
|
5954
|
+
const result = await promoteSketch(state, args);
|
|
5955
|
+
return jsonResult(result);
|
|
5956
|
+
} catch (e) {
|
|
5957
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
5958
|
+
}
|
|
5959
|
+
}
|
|
5960
|
+
);
|
|
5961
|
+
}
|
|
5962
|
+
function registerReferenceTools(server, state) {
|
|
5963
|
+
server.tool(
|
|
5964
|
+
"add_reference",
|
|
5965
|
+
"Import an image as a reference for inspiration. Copies the image to the workspace references/ directory and attaches it to a series or sketch.",
|
|
5966
|
+
{
|
|
5967
|
+
image: import_zod2.z.string().describe("Path to the reference image file"),
|
|
5968
|
+
type: import_zod2.z.enum(["image", "artwork", "photograph", "texture", "palette"]).optional().describe("Reference type (default: image)"),
|
|
5969
|
+
source: import_zod2.z.string().optional().describe("Source attribution (artist, URL, collection)"),
|
|
5970
|
+
seriesId: import_zod2.z.string().optional().describe("Series to attach the reference to"),
|
|
5971
|
+
sketchId: import_zod2.z.string().optional().describe("Sketch to attach the reference to"),
|
|
5972
|
+
id: import_zod2.z.string().optional().describe("Custom reference ID (default: derived from filename)")
|
|
5973
|
+
},
|
|
5974
|
+
async (args) => {
|
|
5975
|
+
try {
|
|
5976
|
+
const result = await addReference(state, args);
|
|
5977
|
+
return jsonResult(result);
|
|
5978
|
+
} catch (e) {
|
|
5979
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
5980
|
+
}
|
|
5981
|
+
}
|
|
5982
|
+
);
|
|
5983
|
+
server.tool(
|
|
5984
|
+
"analyze_reference",
|
|
5985
|
+
"Return a structured analysis framework for a reference image, with the image for visual inspection. The agent fills in the analysis using the framework prompts.",
|
|
5986
|
+
{
|
|
5987
|
+
referenceId: import_zod2.z.string().describe("ID of the reference to analyze"),
|
|
5988
|
+
seriesId: import_zod2.z.string().optional().describe("Series the reference belongs to (speeds up lookup)"),
|
|
5989
|
+
sketchId: import_zod2.z.string().optional().describe("Sketch the reference belongs to (speeds up lookup)"),
|
|
5990
|
+
previewSize: import_zod2.z.number().optional().describe("Max dimension for preview image (default: native)")
|
|
5991
|
+
},
|
|
5992
|
+
async (args) => {
|
|
5993
|
+
try {
|
|
5994
|
+
const result = await analyzeReference(state, args);
|
|
5995
|
+
const content = [
|
|
5996
|
+
{ type: "text", text: JSON.stringify(result.metadata, null, 2) }
|
|
5997
|
+
];
|
|
5998
|
+
if (result.previewJpegBase64) {
|
|
5999
|
+
const ext = (result.metadata["path"] ?? ".png").split(".").pop() ?? "png";
|
|
6000
|
+
const mimeMap = {
|
|
6001
|
+
png: "image/png",
|
|
6002
|
+
jpg: "image/jpeg",
|
|
6003
|
+
jpeg: "image/jpeg",
|
|
6004
|
+
gif: "image/gif",
|
|
6005
|
+
webp: "image/webp",
|
|
6006
|
+
svg: "image/svg+xml"
|
|
6007
|
+
};
|
|
6008
|
+
content.push({
|
|
6009
|
+
type: "image",
|
|
6010
|
+
data: result.previewJpegBase64,
|
|
6011
|
+
mimeType: mimeMap[ext] ?? "image/png"
|
|
6012
|
+
});
|
|
6013
|
+
}
|
|
6014
|
+
return { content };
|
|
6015
|
+
} catch (e) {
|
|
6016
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
6017
|
+
}
|
|
6018
|
+
}
|
|
6019
|
+
);
|
|
6020
|
+
server.tool(
|
|
6021
|
+
"update_reference_analysis",
|
|
6022
|
+
"Save a structured analysis (composition, palette, rhythm, mood, technique) back to a reference after studying it with analyze_reference.",
|
|
6023
|
+
{
|
|
6024
|
+
referenceId: import_zod2.z.string().describe("ID of the reference to update"),
|
|
6025
|
+
seriesId: import_zod2.z.string().optional().describe("Series the reference belongs to"),
|
|
6026
|
+
sketchId: import_zod2.z.string().optional().describe("Sketch the reference belongs to"),
|
|
6027
|
+
analysis: import_zod2.z.object({
|
|
6028
|
+
composition: import_zod2.z.string().optional().describe("Compositional structure observations"),
|
|
6029
|
+
palette: import_zod2.z.array(import_zod2.z.string()).optional().describe("Dominant colors as hex values"),
|
|
6030
|
+
rhythm: import_zod2.z.string().optional().describe("Visual rhythm and pattern observations"),
|
|
6031
|
+
mood: import_zod2.z.string().optional().describe("Mood and emotional qualities"),
|
|
6032
|
+
technique: import_zod2.z.string().optional().describe("Technique and medium observations"),
|
|
6033
|
+
keyQualities: import_zod2.z.array(import_zod2.z.string()).optional().describe("Key qualities worth studying")
|
|
6034
|
+
}).describe("Structured analysis to save")
|
|
6035
|
+
},
|
|
6036
|
+
async (args) => {
|
|
6037
|
+
try {
|
|
6038
|
+
const result = await updateReferenceAnalysis(state, args);
|
|
6039
|
+
return jsonResult(result);
|
|
6040
|
+
} catch (e) {
|
|
6041
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
6042
|
+
}
|
|
6043
|
+
}
|
|
6044
|
+
);
|
|
6045
|
+
server.tool(
|
|
6046
|
+
"extract_palette",
|
|
6047
|
+
"Return a reference image for color palette extraction, with guidelines for identifying dominant colors. The agent extracts hex colors visually.",
|
|
6048
|
+
{
|
|
6049
|
+
referenceId: import_zod2.z.string().describe("ID of the reference to extract palette from"),
|
|
6050
|
+
seriesId: import_zod2.z.string().optional().describe("Series the reference belongs to"),
|
|
6051
|
+
sketchId: import_zod2.z.string().optional().describe("Sketch the reference belongs to"),
|
|
6052
|
+
count: import_zod2.z.number().optional().describe("Number of colors to extract (default: 6)")
|
|
6053
|
+
},
|
|
6054
|
+
async (args) => {
|
|
6055
|
+
try {
|
|
6056
|
+
const result = await extractPalette(state, args);
|
|
6057
|
+
const content = [
|
|
6058
|
+
{ type: "text", text: JSON.stringify(result.metadata, null, 2) }
|
|
6059
|
+
];
|
|
6060
|
+
if (result.previewJpegBase64) {
|
|
6061
|
+
const ext = (result.metadata["path"] ?? ".png").split(".").pop() ?? "png";
|
|
6062
|
+
const mimeMap = {
|
|
6063
|
+
png: "image/png",
|
|
6064
|
+
jpg: "image/jpeg",
|
|
6065
|
+
jpeg: "image/jpeg",
|
|
6066
|
+
gif: "image/gif",
|
|
6067
|
+
webp: "image/webp",
|
|
6068
|
+
svg: "image/svg+xml"
|
|
6069
|
+
};
|
|
6070
|
+
content.push({
|
|
6071
|
+
type: "image",
|
|
6072
|
+
data: result.previewJpegBase64,
|
|
6073
|
+
mimeType: mimeMap[ext] ?? "image/png"
|
|
6074
|
+
});
|
|
6075
|
+
}
|
|
6076
|
+
return { content };
|
|
6077
|
+
} catch (e) {
|
|
6078
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
);
|
|
6082
|
+
}
|
|
4312
6083
|
function registerExportTools(server, state) {
|
|
4313
6084
|
server.tool(
|
|
4314
6085
|
"export_sketch",
|
|
@@ -4573,7 +6344,7 @@ function registerDesignTools(server, state) {
|
|
|
4573
6344
|
}
|
|
4574
6345
|
);
|
|
4575
6346
|
}
|
|
4576
|
-
function registerKnowledgeTools(server,
|
|
6347
|
+
function registerKnowledgeTools(server, state) {
|
|
4577
6348
|
server.tool(
|
|
4578
6349
|
"list_skills",
|
|
4579
6350
|
"List all available design knowledge skills (Phase 5)",
|
|
@@ -4609,7 +6380,7 @@ function registerKnowledgeTools(server, _state) {
|
|
|
4609
6380
|
"get_guidelines",
|
|
4610
6381
|
"Return design guidelines and best practices for a topic (Phase 5)",
|
|
4611
6382
|
{
|
|
4612
|
-
topic: import_zod2.z.enum(["composition", "color", "parameters", "animation", "performance"]).describe("Guideline topic"),
|
|
6383
|
+
topic: import_zod2.z.enum(["composition", "color", "process", "painting", "illustration", "parameters", "animation", "performance"]).describe("Guideline topic"),
|
|
4613
6384
|
renderer: import_zod2.z.enum(["p5", "three", "glsl", "canvas2d", "svg"]).optional().describe("Renderer-specific guidance")
|
|
4614
6385
|
},
|
|
4615
6386
|
async (args) => {
|
|
@@ -4621,12 +6392,28 @@ function registerKnowledgeTools(server, _state) {
|
|
|
4621
6392
|
}
|
|
4622
6393
|
}
|
|
4623
6394
|
);
|
|
6395
|
+
server.tool(
|
|
6396
|
+
"suggest_skills",
|
|
6397
|
+
"Recommend relevant design skills based on sketch context and/or free-text description",
|
|
6398
|
+
{
|
|
6399
|
+
sketchId: import_zod2.z.string().optional().describe("ID of a loaded sketch to analyze for skill recommendations"),
|
|
6400
|
+
context: import_zod2.z.string().optional().describe("Free-text description of what you're working on (e.g., 'atmospheric landscape with watercolor')")
|
|
6401
|
+
},
|
|
6402
|
+
async (args) => {
|
|
6403
|
+
try {
|
|
6404
|
+
const result = await suggestSkills(state, args);
|
|
6405
|
+
return jsonResult(result);
|
|
6406
|
+
} catch (e) {
|
|
6407
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
);
|
|
4624
6411
|
}
|
|
4625
6412
|
|
|
4626
6413
|
// src/state.ts
|
|
4627
6414
|
var import_events = require("events");
|
|
4628
|
-
var
|
|
4629
|
-
var
|
|
6415
|
+
var import_promises11 = require("fs/promises");
|
|
6416
|
+
var import_path11 = require("path");
|
|
4630
6417
|
|
|
4631
6418
|
// src/sidecar.ts
|
|
4632
6419
|
function isSidecarMode() {
|
|
@@ -4641,8 +6428,8 @@ function notifyMutation(type, payload) {
|
|
|
4641
6428
|
}
|
|
4642
6429
|
|
|
4643
6430
|
// src/state.ts
|
|
4644
|
-
var
|
|
4645
|
-
var
|
|
6431
|
+
var import_core15 = require("@genart-dev/core");
|
|
6432
|
+
var import_promises12 = require("fs/promises");
|
|
4646
6433
|
var EditorState = class extends import_events.EventEmitter {
|
|
4647
6434
|
/** Absolute path to the active .genart-workspace file, or null. */
|
|
4648
6435
|
workspacePath = null;
|
|
@@ -4683,7 +6470,7 @@ var EditorState = class extends import_events.EventEmitter {
|
|
|
4683
6470
|
}
|
|
4684
6471
|
/** Resolve a file path, respecting the sandbox basePath when set. */
|
|
4685
6472
|
resolvePath(file) {
|
|
4686
|
-
if ((0,
|
|
6473
|
+
if ((0, import_path11.isAbsolute)(file)) {
|
|
4687
6474
|
if (this.basePath && !file.startsWith(this.basePath)) {
|
|
4688
6475
|
throw new Error(`Path escapes sandbox: ${file}`);
|
|
4689
6476
|
}
|
|
@@ -4691,10 +6478,10 @@ var EditorState = class extends import_events.EventEmitter {
|
|
|
4691
6478
|
}
|
|
4692
6479
|
if ((file.startsWith("~/") || file === "~") && !this.basePath) {
|
|
4693
6480
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4694
|
-
return (0,
|
|
6481
|
+
return (0, import_path11.resolve)(home, file.slice(2));
|
|
4695
6482
|
}
|
|
4696
6483
|
const base = this.basePath ?? process.cwd();
|
|
4697
|
-
const resolved = (0,
|
|
6484
|
+
const resolved = (0, import_path11.resolve)(base, file);
|
|
4698
6485
|
if (this.basePath && !resolved.startsWith(this.basePath)) {
|
|
4699
6486
|
throw new Error(`Path escapes sandbox: ${resolved}`);
|
|
4700
6487
|
}
|
|
@@ -4702,7 +6489,7 @@ var EditorState = class extends import_events.EventEmitter {
|
|
|
4702
6489
|
}
|
|
4703
6490
|
/** Resolve a sketch file reference (relative to workspace dir) to an absolute path. */
|
|
4704
6491
|
resolveSketchPath(file) {
|
|
4705
|
-
if ((0,
|
|
6492
|
+
if ((0, import_path11.isAbsolute)(file)) {
|
|
4706
6493
|
if (this.basePath && !file.startsWith(this.basePath)) {
|
|
4707
6494
|
throw new Error(`Path escapes sandbox: ${file}`);
|
|
4708
6495
|
}
|
|
@@ -4710,11 +6497,11 @@ var EditorState = class extends import_events.EventEmitter {
|
|
|
4710
6497
|
}
|
|
4711
6498
|
if (!this.workspacePath) {
|
|
4712
6499
|
if (this.basePath) {
|
|
4713
|
-
return (0,
|
|
6500
|
+
return (0, import_path11.resolve)(this.basePath, file);
|
|
4714
6501
|
}
|
|
4715
6502
|
throw new Error("No workspace is currently open");
|
|
4716
6503
|
}
|
|
4717
|
-
const resolved = (0,
|
|
6504
|
+
const resolved = (0, import_path11.resolve)((0, import_path11.dirname)(this.workspacePath), file);
|
|
4718
6505
|
if (this.basePath && !resolved.startsWith(this.basePath)) {
|
|
4719
6506
|
throw new Error(`Path escapes sandbox: ${resolved}`);
|
|
4720
6507
|
}
|
|
@@ -4725,9 +6512,9 @@ var EditorState = class extends import_events.EventEmitter {
|
|
|
4725
6512
|
if (this.basePath && !absPath.startsWith(this.basePath)) {
|
|
4726
6513
|
throw new Error(`Path escapes sandbox: ${absPath}`);
|
|
4727
6514
|
}
|
|
4728
|
-
const raw = await (0,
|
|
6515
|
+
const raw = await (0, import_promises11.readFile)(absPath, "utf-8");
|
|
4729
6516
|
const json = JSON.parse(raw);
|
|
4730
|
-
const ws = (0,
|
|
6517
|
+
const ws = (0, import_core15.parseWorkspace)(json);
|
|
4731
6518
|
this.workspacePath = absPath;
|
|
4732
6519
|
this.workspace = ws;
|
|
4733
6520
|
this.sketches.clear();
|
|
@@ -4744,9 +6531,9 @@ var EditorState = class extends import_events.EventEmitter {
|
|
|
4744
6531
|
if (this.basePath && !absPath.startsWith(this.basePath)) {
|
|
4745
6532
|
throw new Error(`Path escapes sandbox: ${absPath}`);
|
|
4746
6533
|
}
|
|
4747
|
-
const raw = await (0,
|
|
6534
|
+
const raw = await (0, import_promises11.readFile)(absPath, "utf-8");
|
|
4748
6535
|
const json = JSON.parse(raw);
|
|
4749
|
-
const definition = (0,
|
|
6536
|
+
const definition = (0, import_core15.parseGenart)(json);
|
|
4750
6537
|
this.sketches.set(definition.id, { definition, path: absPath });
|
|
4751
6538
|
this.emitMutation("sketch:loaded", { id: definition.id, path: absPath });
|
|
4752
6539
|
return definition;
|
|
@@ -4782,18 +6569,18 @@ var EditorState = class extends import_events.EventEmitter {
|
|
|
4782
6569
|
if (!this.workspace || !this.workspacePath) {
|
|
4783
6570
|
throw new Error("No workspace is currently open");
|
|
4784
6571
|
}
|
|
4785
|
-
const json = (0,
|
|
6572
|
+
const json = (0, import_core15.serializeWorkspace)(this.workspace);
|
|
4786
6573
|
if (!this.remoteMode) {
|
|
4787
|
-
await (0,
|
|
6574
|
+
await (0, import_promises12.writeFile)(this.workspacePath, json, "utf-8");
|
|
4788
6575
|
}
|
|
4789
6576
|
this.emitMutation("workspace:saved", { path: this.workspacePath });
|
|
4790
6577
|
}
|
|
4791
6578
|
/** Save a sketch to disk. */
|
|
4792
6579
|
async saveSketch(id) {
|
|
4793
6580
|
const loaded = this.requireSketch(id);
|
|
4794
|
-
const json = (0,
|
|
6581
|
+
const json = (0, import_core15.serializeGenart)(loaded.definition);
|
|
4795
6582
|
if (!this.remoteMode) {
|
|
4796
|
-
await (0,
|
|
6583
|
+
await (0, import_promises12.writeFile)(loaded.path, json, "utf-8");
|
|
4797
6584
|
}
|
|
4798
6585
|
this.emitMutation("sketch:saved", { id, path: loaded.path });
|
|
4799
6586
|
}
|
|
@@ -4827,7 +6614,7 @@ var EditorState = class extends import_events.EventEmitter {
|
|
|
4827
6614
|
if (stack) return stack;
|
|
4828
6615
|
const loaded = this.requireSketch(sketchId);
|
|
4829
6616
|
const initialLayers = loaded.definition.layers ?? [];
|
|
4830
|
-
stack = (0,
|
|
6617
|
+
stack = (0, import_core15.createLayerStack)(initialLayers, (changeType) => {
|
|
4831
6618
|
this.syncLayersToDefinition(sketchId);
|
|
4832
6619
|
const mutationType = `design:${changeType}`;
|
|
4833
6620
|
this.emitMutation(mutationType, { sketchId, changeType });
|
|
@@ -4865,9 +6652,40 @@ var EditorState = class extends import_events.EventEmitter {
|
|
|
4865
6652
|
canvasHeight: def.canvas.height,
|
|
4866
6653
|
rendererId: def.renderer.type
|
|
4867
6654
|
};
|
|
6655
|
+
const sketch = {
|
|
6656
|
+
getSymbols() {
|
|
6657
|
+
return loaded.definition.symbols ?? {};
|
|
6658
|
+
},
|
|
6659
|
+
setSymbols(symbols) {
|
|
6660
|
+
loaded.definition = { ...loaded.definition, symbols };
|
|
6661
|
+
},
|
|
6662
|
+
getComponents() {
|
|
6663
|
+
return loaded.definition.components ?? {};
|
|
6664
|
+
},
|
|
6665
|
+
setComponents(components) {
|
|
6666
|
+
loaded.definition = { ...loaded.definition, components };
|
|
6667
|
+
},
|
|
6668
|
+
getThirdParty() {
|
|
6669
|
+
const def2 = loaded.definition;
|
|
6670
|
+
return def2["thirdParty"] ?? [];
|
|
6671
|
+
},
|
|
6672
|
+
setThirdParty(notices) {
|
|
6673
|
+
loaded.definition["thirdParty"] = notices;
|
|
6674
|
+
},
|
|
6675
|
+
getRenderer() {
|
|
6676
|
+
return loaded.definition.renderer.type;
|
|
6677
|
+
},
|
|
6678
|
+
getGenartVersion() {
|
|
6679
|
+
return loaded.definition.genart;
|
|
6680
|
+
},
|
|
6681
|
+
setGenartVersion(version) {
|
|
6682
|
+
loaded.definition = { ...loaded.definition, genart: version };
|
|
6683
|
+
}
|
|
6684
|
+
};
|
|
4868
6685
|
return {
|
|
4869
6686
|
layers: layerStack,
|
|
4870
6687
|
sketchState,
|
|
6688
|
+
sketch,
|
|
4871
6689
|
canvasWidth: def.canvas.width,
|
|
4872
6690
|
canvasHeight: def.canvas.height,
|
|
4873
6691
|
async resolveAsset(_assetId) {
|