[object Object]

← back to Wallco Ai

wallco: cactus collection generator + publisher (10 Omni Tucson colorways)

f90b1a4dedeaca23bfe3100df7f985d73c5a8152 · 2026-05-19 23:50:48 -0700 · Steve Abrams

Files touched

Diff

commit f90b1a4dedeaca23bfe3100df7f985d73c5a8152
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 19 23:50:48 2026 -0700

    wallco: cactus collection generator + publisher (10 Omni Tucson colorways)
---
 scripts/generate-cactus.js | 98 ++++++++++++++++++++++++++++++++++++++++++++++
 scripts/publish-cactus.js  | 36 +++++++++++++++++
 2 files changed, 134 insertions(+)

diff --git a/scripts/generate-cactus.js b/scripts/generate-cactus.js
new file mode 100755
index 0000000..2766d73
--- /dev/null
+++ b/scripts/generate-cactus.js
@@ -0,0 +1,98 @@
+#!/usr/bin/env node
+// Generate 10 cactus wallcovering designs, each in a distinct designer-curated
+// colorway (Omni Tucson / Sonoran luxury palette, NOT fluorescent multi-color).
+//
+// Settlement-safe by construction:
+//   - SINGLE INK COLOR figure layer on a SINGLE TONE ground (Part A3 fails)
+//   - Uniform upright cactus orientation (Part A1 unlikely to fire)
+//   - Edge-to-edge tessellation (Part A2 fails)
+//   - Cactus is NOT in the Part B prohibited list (bananas/grapes/birds/butterflies)
+//   - Explicit anti-prompt against all Part A/B foliage + Part B animals
+//
+// Steve's brief: "create our own versions of fliepaper.com cactus patterns.
+// in 10 colors. colors must be id colors and not these bright ugly fluorescent
+// multi color monstrosities."
+// Palette inspiration: Omni Tucson National Resort + Sonoran desert luxury
+// vocabulary (Catalina Mountain twilight, Spanish Colonial adobe, cochineal
+// indigenous textile, prickly pear fruit, saguaro skin, mesquite charcoal,
+// natural indigo, caliche limestone).
+
+const { spawnSync } = require('child_process');
+const path = require('path');
+
+const ROOT = path.join(__dirname, '..');
+
+const COLORWAYS = [
+  { slug: 'catalina-dusk',      name: 'Catalina Dusk',      ground_hex: '#7A4A52', ground_name: 'Catalina Mountain twilight mauve',      figure_hex: '#E8DCC4', figure_name: 'tortilla-bone cream' },
+  { slug: 'adobe-sundown',      name: 'Adobe Sundown',      ground_hex: '#A8623A', ground_name: 'Spanish Colonial adobe terracotta',     figure_hex: '#E8DCC4', figure_name: 'tortilla-bone cream' },
+  { slug: 'tortilla-bone',      name: 'Tortilla Bone',      ground_hex: '#E8DCC4', ground_name: 'chalky stucco cream',                   figure_hex: '#2A2520', figure_name: 'mesquite charcoal' },
+  { slug: 'mesquite-charcoal',  name: 'Mesquite Charcoal',  ground_hex: '#2A2520', ground_name: 'fire-blackened mesquite charcoal',      figure_hex: '#E8DCC4', figure_name: 'tortilla-bone cream' },
+  { slug: 'saguaro-patina',     name: 'Saguaro Patina',     ground_hex: '#345E51', ground_name: 'aged saguaro skin green-grey',          figure_hex: '#E8DCC4', figure_name: 'tortilla-bone cream' },
+  { slug: 'cochineal-oxblood',  name: 'Cochineal Oxblood',  ground_hex: '#6B2A22', ground_name: 'cochineal indigenous-textile deep red', figure_hex: '#E8DCC4', figure_name: 'tortilla-bone cream' },
+  { slug: 'caliche-limestone',  name: 'Caliche Limestone',  ground_hex: '#B0A68C', ground_name: 'warm caliche-limestone taupe',          figure_hex: '#2A2520', figure_name: 'mesquite charcoal' },
+  { slug: 'indigo-atelier',     name: 'Indigo Atelier',     ground_hex: '#1E3550', ground_name: 'natural indigo-dye deep blue',          figure_hex: '#E8DCC4', figure_name: 'tortilla-bone cream' },
+  { slug: 'sonoran-espresso',   name: 'Sonoran Espresso',   ground_hex: '#3A2820', ground_name: 'aged mesquite-barrel espresso brown',   figure_hex: '#E8DCC4', figure_name: 'tortilla-bone cream' },
+  { slug: 'prickly-pear-plum',  name: 'Prickly Pear Plum',  ground_hex: '#824050', ground_name: 'prickly-pear-fruit plum magenta',       figure_hex: '#E8DCC4', figure_name: 'tortilla-bone cream' },
+];
+
+const COMPOSITION =
+  'Hand-painted gouache silhouettes of Sonoran desert cacti as seen around the Omni Tucson National Resort — ' +
+  'saguaro with raised arms, prickly-pear paddle pads, barrel cactus rosettes, tall agave fronds, ' +
+  'ocotillo whip stems, and cholla branches — arranged in a tessellated seamless wallpaper repeat. ' +
+  'All cactus silhouettes are upright and oriented to vertical; no rotation, no tossing, no directional variation. ' +
+  'Every cactus is rendered in the EXACT same single ink color (no internal color variation within the figure layer). ' +
+  'Edge-to-edge tessellation with even spacing between motifs. ' +
+  'Designer-grade, archival illustration quality, reads as a high-end Spanish Colonial / Pueblo Revival wallcovering, ' +
+  'indigenous Tohono O\'odham textile rhythm.';
+
+const ANTI_PROMPT =
+  'No banana, no banana leaves, no banana pods, no palm, no palm fronds, no monstera, no philodendron, ' +
+  'no general foliage other than cacti, no tropical jungle. No bananas, no grapes, no birds, no butterflies. ' +
+  'No multi-color rainbow figures, no fluorescent neon colors, no day-glo, no chromatic gradient on the cacti. ' +
+  'No watermark, no signature, no text, no logos. Square aspect ratio, seamless tile repeat, no edge artifacts, flat-paper rendering (no room mockup, no perspective).';
+
+console.log('Generating ' + COLORWAYS.length + ' cactus designs (Omni Tucson / Sonoran-luxury colorways)');
+console.log('Backend: ' + (process.env.GEN_BACKEND || 'replicate') + ' | Category: cactus\n');
+
+let success = 0, fail = 0;
+const ids = [];
+const startedAt = Date.now();
+
+for (let i = 0; i < COLORWAYS.length; i++) {
+  const cw = COLORWAYS[i];
+  const prompt =
+    `Cactus Repeat — ${cw.name} colorway. ` +
+    `Ground: solid ${cw.ground_name} (${cw.ground_hex}). ` +
+    `Figure: single-ink ${cw.figure_name} (${cw.figure_hex}) — ALL cactus silhouettes in this exact tone. ` +
+    COMPOSITION + ' ' +
+    ANTI_PROMPT;
+
+  console.log('[' + (i+1) + '/' + COLORWAYS.length + '] ' + cw.name + ' (' + cw.ground_hex + ' × ' + cw.figure_hex + ')');
+
+  const r = spawnSync('node', [
+    path.join(__dirname, 'generate_designs.js'),
+    '--n', '1',
+    '--kind', 'seamless_tile',
+    '--category', 'cactus',
+    '--prompts', prompt,
+  ], { cwd: ROOT, encoding: 'utf8', env: process.env });
+
+  if (r.status === 0) {
+    success++;
+    const m = (r.stdout || '').match(/id=(\d+)/);
+    if (m) ids.push(parseInt(m[1], 10));
+    process.stdout.write('  OK' + (m ? ' (id=' + m[1] + ')' : '') + '\n');
+  } else {
+    fail++;
+    process.stdout.write('  FAIL ' + (r.stderr || r.stdout || '').slice(0, 240) + '\n');
+  }
+}
+
+const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
+console.log('\nDone in ' + elapsed + 's — ' + success + ' ok, ' + fail + ' fail');
+console.log('Inserted ids: ' + JSON.stringify(ids));
+console.log('\nNext: run scripts/publish-cactus.js to flip is_published=TRUE and surface in /designs.');
+
+// Exit non-zero on total failure so a `&&` chain to publish+deploy aborts safely.
+if (success === 0) process.exit(2);
+if (success < Math.ceil(COLORWAYS.length * 0.5)) process.exit(3); // <50% = abort
diff --git a/scripts/publish-cactus.js b/scripts/publish-cactus.js
new file mode 100755
index 0000000..baa8308
--- /dev/null
+++ b/scripts/publish-cactus.js
@@ -0,0 +1,36 @@
+#!/usr/bin/env node
+// After generate-cactus.js completes, flip the cactus rows to is_published=TRUE,
+// sync designs.json, hot-reload wallco. Mirrors publish-geometric-block-leaves.js.
+
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+
+const DB_NAME = 'dw_unified';
+const PSQL_CMD = (process.platform === 'linux')
+  ? `sudo -n -u postgres psql ${DB_NAME} -At -q`
+  : `psql ${DB_NAME} -At -q`;
+const q = (sql) => execSync(PSQL_CMD, { input: sql, encoding: 'utf8' }).trim();
+
+console.log('Flipping is_published=TRUE for category=cactus');
+const result = q(`UPDATE spoon_all_designs SET is_published=TRUE WHERE category='cactus' AND is_published=FALSE RETURNING id;`);
+const ids = result.split('\n').filter(Boolean);
+console.log('Published ' + ids.length + ' rows: ' + JSON.stringify(ids));
+
+console.log('Syncing data/designs.json…');
+const DATA_FILE = path.join(__dirname, '..', 'data', 'designs.json');
+const cols = 'id, brand, kind, width_in, height_in, panels, generator, prompt, negative_prompt, seed, steps, cfg_scale, pd_source_ids, image_url, local_path, dominant_hex, palette, motifs, tags, category, is_published, notes, created_at, parent_design_id, chat_session_id, request_text, verticals, source_shopify_id, source_dw_sku, source_url, gd_score, gd_critique, id_score, id_critique, combined_score, rating_summary, rated_at, user_score_avg, user_vote_count, poll_wins';
+const allRowsJson = q(`SELECT json_agg(t ORDER BY id) FROM (SELECT ${cols} FROM spoon_all_designs) t;`);
+const allRows = JSON.parse(allRowsJson);
+fs.writeFileSync(DATA_FILE + '.tmp', JSON.stringify(allRows, null, 2), 'utf8');
+fs.renameSync(DATA_FILE + '.tmp', DATA_FILE);
+console.log('designs.json now has ' + allRows.length + ' rows');
+
+console.log('Hot-reloading wallco-ai DESIGNS cache…');
+try {
+  execSync('curl -s -X POST http://localhost:9792/admin/reload-designs --max-time 3', { encoding: 'utf8', stdio: 'inherit' });
+} catch (e) {
+  console.error('reload failed (server may be down): ' + e.message);
+}
+
+console.log('\nDone. Verify at http://localhost:9792/designs?cat=cactus');

← be80861 sql migration: add all_designs.inspired_by_sku + inspired_by  ·  back to Wallco Ai  ·  inspiration_generator: pull real archival seeds, generate NE 27a2a98 →