[object Object]

← back to Dw Photo Capture

Batch OCR: load GEMINI_API_KEY from secrets + default to gemini engine

a138b398c6f6282e905da7c066921610a5e19668 · 2026-09-18 14:35:15 -0700 · Steve Abrams

The pm2 process launched without GEMINI_API_KEY exported, so the gemini
engine read as unavailable and identify silently fell back to the retired
Ollama (qwen2.5vl) path. Load the key from the canonical secrets-manager/.env
the same way the Shopify token is loaded, and prefer gemini whenever a key is
present (override with OCR_ENGINE=macvision). Verified live: identify now
reaches gemini-2.5-flash; only a credits top-up remains.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uns3k4K15WBcFJNr3GcUd3

Files touched

Diff

commit a138b398c6f6282e905da7c066921610a5e19668
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Sep 18 14:35:15 2026 -0700

    Batch OCR: load GEMINI_API_KEY from secrets + default to gemini engine
    
    The pm2 process launched without GEMINI_API_KEY exported, so the gemini
    engine read as unavailable and identify silently fell back to the retired
    Ollama (qwen2.5vl) path. Load the key from the canonical secrets-manager/.env
    the same way the Shopify token is loaded, and prefer gemini whenever a key is
    present (override with OCR_ENGINE=macvision). Verified live: identify now
    reaches gemini-2.5-flash; only a credits top-up remains.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01Uns3k4K15WBcFJNr3GcUd3
---
 server.js | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/server.js b/server.js
index 5c07408..d6977db 100644
--- a/server.js
+++ b/server.js
@@ -86,12 +86,20 @@ const AUTH_PASS = process.env.AUTH_PASS || 'DW2024!';
 const SHOP = 'designer-laboratory-sandbox.myshopify.com';
 const API = '2024-10';
 let TOKEN = process.env.SHOPIFY_ADMIN_TOKEN || '';
-if (!TOKEN) {
-  try {
-    for (const l of fs.readFileSync(path.join(process.env.HOME, 'Projects/secrets-manager/.env'), 'utf8').split('\n'))
-      if (l.startsWith('SHOPIFY_ADMIN_TOKEN=')) { TOKEN = l.split('=').slice(1).join('=').trim(); break; }
-  } catch (e) {}
-}
+// Load selected secrets from the canonical secrets-manager/.env into process.env.
+// pm2 launches this process WITHOUT these exported, so without this loop GEMINI_API_KEY
+// is empty at runtime and the gemini OCR engine silently falls back to the retired
+// Ollama path (resolveEngines → engineAvailable('gemini')===false). Loading it here is
+// durable across restarts. Never overwrites an already-set env value.
+try {
+  const NEED = ['SHOPIFY_ADMIN_TOKEN', 'GEMINI_API_KEY'];
+  for (const l of fs.readFileSync(path.join(process.env.HOME, 'Projects/secrets-manager/.env'), 'utf8').split('\n')) {
+    const i = l.indexOf('='); if (i < 0) continue;
+    const k = l.slice(0, i).trim();
+    if (NEED.includes(k) && !process.env[k]) process.env[k] = l.slice(i + 1).trim();
+  }
+} catch (e) {}
+if (!TOKEN) TOKEN = process.env.SHOPIFY_ADMIN_TOKEN || '';
 
 fs.mkdirSync(PHOTOS, { recursive: true });
 
@@ -653,7 +661,10 @@ const GEMINI_VISION_MODEL = process.env.GEMINI_VISION_MODEL || 'gemini-2.5-flash
 const GCV_API_KEY = process.env.GCV_API_KEY || process.env.GOOGLE_VISION_API_KEY || '';
 const IS_DARWIN = process.platform === 'darwin';
 const HAS_MACVISION = IS_DARWIN && fs.existsSync(path.join(ROOT, 'bin/ocr'));
-const DEFAULT_ENGINE = (process.env.OCR_ENGINE || (HAS_MACVISION ? 'macvision' : 'gemini')).toLowerCase();
+// Prefer gemini whenever a key is present: macvision's identify path routes to the
+// retired Ollama (qwen2.5vl), so gemini is the only working OCR engine. Override with
+// OCR_ENGINE=macvision to force the local path back. (Steve chose gemini, 2026-09-18.)
+const DEFAULT_ENGINE = (process.env.OCR_ENGINE || (GEMINI_API_KEY ? 'gemini' : (HAS_MACVISION ? 'macvision' : 'gemini'))).toLowerCase();
 const ALL_ENGINES = ['macvision', 'gemini', 'gcv'];
 // Rough per-scan cost so the caller always sees the $ (Steve's "always show costs" rule).
 const ENGINE_COST_USD = { macvision: 0, gemini: 0.0006, gcv: 0.0015 };
@@ -2609,7 +2620,7 @@ async function createNewItem(p, b64, dryRun) {
   const fm = buildFmMaster(p, mint, vreg);
   const specsObj = { material, collection: p.collection || '', width: p.width || '', roll_length: p.roll_length || '',
     repeat: p.repeat || '', pattern_match: p.pattern_match || '', substrate: p.substrate || '',
-    how_sold: p.how_sold || '', price_code: p.price_code || '' };
+    how_sold: p.how_sold || '', price_code: p.price_code || '', notes: p.notes || '' };
   const flags = [mint.flag, fm.vid_flag, dup ? `mfr# ${mfr} already in catalog as ${dup.dw_sku || dup.product_id}` : null].filter(Boolean);
   // FULL 3-system preview — exactly what Shopify / dw_unified / FileMaker WOULD receive.
   const preview = {

← bad2f48 auto-data-snapshot: 2026-09-18T14:19:46 (2 data files) — dat  ·  back to Dw Photo Capture  ·  Photo capture: linear 2-photo flow, Measure Depth, White Bal 14ba745 →