[object Object]

← back to Slack Idea Board

idea-board: add 'Good for an Agent Abrams build / project?' column (local-Ollama aa_fit/aa_target/aa_reason, backfills legacy cache)

5babc6e6a9943562726aba048c5cd43e8162f61c · 2026-07-23 12:09:31 -0700 · Steve Abrams

Files touched

Diff

commit 5babc6e6a9943562726aba048c5cd43e8162f61c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 23 12:09:31 2026 -0700

    idea-board: add 'Good for an Agent Abrams build / project?' column (local-Ollama aa_fit/aa_target/aa_reason, backfills legacy cache)
---
 public/index.html | 20 +++++++++++++++-----
 server.js         | 28 +++++++++++++++++++++-------
 2 files changed, 36 insertions(+), 12 deletions(-)

diff --git a/public/index.html b/public/index.html
index b489a6c..bce897e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -22,7 +22,9 @@
     text-transform:uppercase;color:var(--dim);padding:10px 16px;border-bottom:1px solid var(--line)}
   tbody td{padding:14px 16px;border-bottom:1px solid var(--line);vertical-align:top}
   tbody tr:hover{background:#12151d}
-  .col1{width:38%} .col2{width:30%} .col3{width:32%}
+  .col1{width:30%} .col2{width:22%} .col3{width:22%} .col4{width:26%}
+  .aatarget{display:inline-block;margin-top:6px;font-size:11.5px;font-weight:600;color:var(--gold);
+    border:1px solid var(--line);border-radius:999px;padding:2px 9px;background:var(--chip)}
   a.link{color:#7db4ff;text-decoration:none;font-weight:600;word-break:break-word}
   a.link:hover{text-decoration:underline}
   .about{color:var(--text);margin-top:5px}
@@ -82,9 +84,10 @@
   <thead><tr>
     <th class="col1">1 · Link &amp; what the idea is</th>
     <th class="col2">2 · Good for your builds? Why</th>
-    <th class="col3">3 · Next step</th>
+    <th class="col3">3 · Good for an Agent Abrams build / project?</th>
+    <th class="col4">4 · Next step</th>
   </tr></thead>
-  <tbody id="rows"><tr><td colspan="3" class="empty">Loading your feed…</td></tr></tbody>
+  <tbody id="rows"><tr><td colspan="4" class="empty">Loading your feed…</td></tr></tbody>
 </table>
 <div id="toast" role="status" aria-live="polite"></div>
 <script>
@@ -162,11 +165,17 @@ function render(){
     document.getElementById('stat').textContent =
       `${shown.length} shown / ${d.count} total · ${d.pending} analyzing · ${d.model}`;
     const rows = document.getElementById('rows');
-    if(!shown.length){ rows.innerHTML='<tr><td colspan=3 class=empty>Nothing in this filter yet. Drop an x.com/GitHub link in #claude-to-steve or #claude-chat.</td></tr>'; return; }
+    if(!shown.length){ rows.innerHTML='<tr><td colspan=4 class=empty>Nothing in this filter yet. Drop an x.com/GitHub link in #claude-to-steve or #claude-chat.</td></tr>'; return; }
     rows.innerHTML = shown.map(i=>{
       const host = (()=>{try{return new URL(i.url).hostname.replace('www.','')}catch{return i.url}})();
       const c2 = i.pending ? '<span class="pending">analyzing…</span>'
         : `<span class="fit ${i.fit}">${esc(i.fit)}</span><div class="reason">${esc(i.fit_reason)}</div>`;
+      // Agent Abrams / existing-project fit. aa_fit may be undefined on records enriched
+      // before this column existed — those show "analyzing…" until the backfill fills them.
+      const cAA = (i.pending || i.aa_fit===undefined) ? '<span class="pending">analyzing…</span>'
+        : `<span class="fit ${i.aa_fit}">${esc(i.aa_fit)}</span>`
+          + (i.aa_target && i.aa_target!=='—' ? `<div class="aatarget">${esc(i.aa_target)}</div>` : '')
+          + (i.aa_reason ? `<div class="reason">${esc(i.aa_reason)}</div>` : '');
       const c3 = i.pending ? '<span class="pending">…</span>'
         : (()=>{
             const step = esc(i.next_step||''), sc = stepClass(i.next_step);
@@ -189,7 +198,8 @@ function render(){
           <div class="when">🕓 ${fmt(i.when)}</div>
         </td>
         <td class="col2">${c2}</td>
-        <td class="col3">${c3}</td>
+        <td class="col3">${cAA}</td>
+        <td class="col4">${c3}</td>
       </tr>`;
     }).join('');
 }
diff --git a/server.js b/server.js
index c0485c6..638ca46 100644
--- a/server.js
+++ b/server.js
@@ -1,6 +1,7 @@
-// slack-idea-board — polls Steve's Slack idea feed(s) and renders a 3-column
+// slack-idea-board — polls Steve's Slack idea feed(s) and renders a 4-column
 // triage board: [1] link + what the idea is · [2] good for your builds? why ·
-// [3] next step (Build new project / Add new skill with repo / Create agent).
+// [3] good for an Agent Abrams build / project? · [4] next step (Build new
+// project / Add new skill with repo / Create agent).
 // AI enrichment is LOCAL Ollama ($0). Zero external deps (Node built-in http).
 const http = require('http');
 const fs = require('fs');
@@ -35,6 +36,10 @@ const CHAN_NAME = { 'C09SW7VQ0RK': 'claude-to-steve', 'C0BGQ2QLR35': 'claude-cha
 
 const BUILD_CONTEXT = `Steve builds: AI agents & Claude Code skills (vendor scrapers, automation), Designer Wallcoverings e-commerce (Shopify, catalog pipelines, dashboards), web apps, marketplaces, and LOCAL-AI pipelines (Ollama/exo). He drops links (X/Twitter posts, GitHub repos, tools, demos about AI agents, coding, automation, design) as build inspiration.`;
 
+// Context for the second fit dimension: does the idea suit an "Agent Abrams" build,
+// or slot into one of Steve's existing projects?
+const AA_CONTEXT = `"Agent Abrams" is Steve's public AI-agent brand + fleet at agentabrams.com — e.g. the AbramsEgo agent command center, builds.agentabrams.com (fleet dashboard + nightly recap films), the CNCP command center, and public directory sites (lawyers / doctors / animals / costa-rica / LA-eats / commercial real estate). "Any projects" means any of Steve's existing ~/Projects builds — DW Shopify + vendor scrapers, catalog viewers, marketing tools, dashboards, and monitoring canaries. When assessing this dimension, name ONE concrete Agent Abrams build or existing project the idea could power or slot into.`;
+
 function loadEnv(p) { const o = {}; try { for (const l of fs.readFileSync(p, 'utf8').split('\n')) { const m = l.match(/^([A-Z0-9_]+)=(.*)$/); if (m) o[m[1]] = m[2].replace(/^['"]|['"]$/g, ''); } } catch {} return o; }
 let CACHE = {}; try { CACHE = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')); } catch {}
 function saveCache() { try { fs.writeFileSync(CACHE_FILE, JSON.stringify(CACHE, null, 2)); } catch {} }
@@ -61,7 +66,9 @@ function ideaAbout(m) {
 async function ollamaEnrich(idea) {
   const prompt = `${BUILD_CONTEXT}
 
-A new item was posted to Steve's idea feed. Assess it FOR HIS BUILDS.
+${AA_CONTEXT}
+
+A new item was posted to Steve's idea feed. Assess it FOR HIS BUILDS, and separately for whether it fits an AGENT ABRAMS build or any existing project.
 
 URL: ${idea.url}
 Link title: ${idea.title || '(none)'}
@@ -73,6 +80,9 @@ Respond with ONLY a JSON object, no prose:
   "about": "<1-2 sentence plain-English summary of what this idea/link IS>",
   "fit": "<Yes | Maybe | No>",
   "fit_reason": "<1-2 sentences: is it good for Steve's builds and why / why not>",
+  "aa_fit": "<Yes | Maybe | No — is it good for an Agent Abrams build or any existing project?>",
+  "aa_target": "<name of ONE Agent Abrams build or existing project it best fits, or — if none>",
+  "aa_reason": "<1 sentence: which agentabrams.com build or existing project this could power, and how>",
   "next_step": "<exactly one of: Build new project | Add new skill with repo | Create agent | Skip>",
   "next_detail": "<1 sentence: concrete suggestion — e.g. a project/skill/agent name and what it does>"
 }`;
@@ -84,7 +94,10 @@ Respond with ONLY a JSON object, no prose:
   const parsed = JSON.parse(j.response);
   return {
     about: parsed.about || idea.title || '', fit: parsed.fit || 'Maybe',
-    fit_reason: parsed.fit_reason || '', next_step: parsed.next_step || 'Skip',
+    fit_reason: parsed.fit_reason || '',
+    aa_fit: parsed.aa_fit || 'Maybe', aa_target: parsed.aa_target || '—',
+    aa_reason: parsed.aa_reason || '',
+    next_step: parsed.next_step || 'Skip',
     next_detail: parsed.next_detail || '',
   };
 }
@@ -127,12 +140,13 @@ let ENRICH_BUSY = false;
 const CONCURRENCY = parseInt(process.env.ENRICH_CONCURRENCY || '4', 10); // parallel local-model calls
 async function enrichOne(idea) {
   try { CACHE[idea.id] = { ...await ollamaEnrich(idea), enriched_at: new Date().toISOString() }; }
-  catch (e) { CACHE[idea.id] = { about: idea.title || '', fit: '?', fit_reason: 'AI enrich failed: ' + e.message, next_step: 'Skip', next_detail: '', error: true }; }
+  catch (e) { CACHE[idea.id] = { about: idea.title || '', fit: '?', fit_reason: 'AI enrich failed: ' + e.message, aa_fit: '?', aa_target: '—', aa_reason: '', next_step: 'Skip', next_detail: '', error: true }; }
 }
 async function enrichNew(ideas, cap = 24) {
   if (ENRICH_BUSY) return; ENRICH_BUSY = true;
   try {
-    const todo = ideas.filter(i => !CACHE[i.id]).slice(0, cap);
+    // Re-enrich records that predate the aa_fit dimension so the backlog backfills too.
+    const todo = ideas.filter(i => !CACHE[i.id] || CACHE[i.id].aa_fit === undefined).slice(0, cap);
     for (let i = 0; i < todo.length; i += CONCURRENCY) {
       await Promise.all(todo.slice(i, i + CONCURRENCY).map(enrichOne));
       saveCache();
@@ -141,7 +155,7 @@ async function enrichNew(ideas, cap = 24) {
 }
 // Background drainer: keep enriching the backlog independent of page polls.
 async function drain() {
-  try { const ideas = await collectIdeas(); if (ideas.some(i => !CACHE[i.id])) await enrichNew(ideas, 24); } catch {}
+  try { const ideas = await collectIdeas(); if (ideas.some(i => !CACHE[i.id] || CACHE[i.id].aa_fit === undefined)) await enrichNew(ideas, 24); } catch {}
   setTimeout(drain, 2000);
 }
 

← 189d8c3 Pin autonomous Claude CLI invocation to Opus (--model opus)  ·  back to Slack Idea Board  ·  idea-board: filter chips can slice by the Agent Abrams verdi 0a99aaa →