Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
442335385This file is pinned to the selected release. Download the original to open it in your design tool.
#!/usr/bin/env node
// Keep README inventory, its overview, and desktop browsing on the same taxonomy.
// Reads metadata only; never executes package setup or publishes the catalog.
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { readDshRegistry } from '../../cli/scripts/lib/dshRegistry.mjs';
const root = fileURLToPath(new URL('../../', import.meta.url));
const check = process.argv.includes('--check');
const read = path => readFileSync(resolve(root, path), 'utf8');
const { categories } = JSON.parse(read('store/browse-categories.json'));
const entries = readDshRegistry(resolve(root, 'store'), { strict: true });
const agents = entries.filter(e => e.kind !== 'viewer');
const viewers = entries.filter(e => e.kind === 'viewer');
const escape = s => String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const domainCategory = new Map();
for (const category of categories) {
for (const domain of new Set([category.name, ...category.domains].map(x => x.toLowerCase()))) {
if (domainCategory.has(domain)) throw Error(`Ambiguous browsing domain: ${domain}`);
domainCategory.set(domain, category.name);
}
}
const groupFor = e => ['autonomous/autonomous-grid', 'local/ollama'].includes(e.id)
? 'Local AI' : domainCategory.get(e.category?.trim().toLowerCase());
for (const entry of agents) if (!groupFor(entry)) throw Error(`Unmapped listed harness: ${entry.id} (${entry.category})`);
const groups = categories.map(c => ({ ...c, entries: agents.filter(e => groupFor(e) === c.name).sort((a, b) => a.name.localeCompare(b.name)) })).filter(c => c.entries.length);
const engines = [
['claude','Claude Code'],['codex','Codex'],['cursor','Cursor'],['opencode','OpenCode'],
['pi','Pi'],['hermes','Hermes'],['commandcode','Command Code'],['devin','Devin'],
['muse','Muse Code'],['amp','Amp'],['agy','Antigravity'],['copilot','GitHub Copilot'],
['grok','Grok Build'],['kilo','Kilo Code'],
];
function output(path, content) {
const full = resolve(root, path);
if (check) {
if (!existsSync(full) || readFileSync(full, 'utf8') !== content) throw Error(`${path} is stale; run node store/tools/presentation.mjs`);
} else {
mkdirSync(dirname(full), { recursive: true });
writeFileSync(full, content);
}
}
const dart = `// Generated from store/browse-categories.json by store/tools/presentation.mjs.\n// Run that generator after changing the taxonomy; do not edit this file.\nconst storeCategoryDomains = <String, Set<String>>{\n${categories.map(c => ` '${c.name}': {${c.domains.map(d => `'${d}'`).join(', ')}},`).join('\n')}\n};\n`;
output('desktop/lib/store/store_categories.g.dart', dart);
if (process.argv.includes('--categories-only')) process.exit(0);
const inventory = [
'<!-- store-catalog:start -->',
'### Coding and beyond', '',
`Start with a coding agent you already use. Explore ${agents.length} domain-specific harnesses when your`,
'next idea takes you further.', '',
'| Category | Agents and harnesses |', '|---|---|',
`| **Coding** | [${engines.map(([,name]) => name).join(', ')}](docs/engines.md) |`,
...groups.map(c => `| ${c.name} | ${c.entries.map(e => `[${e.name}](${e.path}/)`).join(', ')} |`), '',
`These are the ${agents.length} harnesses currently listed in the Store catalog. They combine upstream`,
'open-source tools and original workflows, with instructions, setup, checks, and live views for each craft.', '',
`The ${viewers.length} [shared viewers](store/viewers/) cover CAD, 3D models, documents, games, film, video,`,
'MuJoCo, web pages, isolated web previews, and studios. Viewer packages install alongside the',
'harnesses that need them. Experimental packages marked unlisted are not included above.',
'<!-- store-catalog:end -->',
].join('\n');
let readme = read('README.md');
const marked = /<!-- store-catalog:start -->[\s\S]*?<!-- store-catalog:end -->/;
if (marked.test(readme)) readme = readme.replace(marked, inventory);
else {
const start = readme.indexOf('### 19 harnesses today');
const end = readme.indexOf('**The harness we\'d love to see next', start);
if (start < 0 || end < 0) throw Error('Cannot find README catalog section');
readme = readme.slice(0, start) + inventory + '\n\n' + readme.slice(end);
}
output('README.md', readme);
// Reuse the app's vendored marks and the reviewed original package vectors.
// Artwork provenance remains at its source; missing featured marks fail closed.
const identities = read('desktop/lib/widgets/engine_identity.dart');
const marks = new Map();
for (const match of identities.matchAll(/'([^']+)': EngineIdentity\(([\s\S]*?)\n \),/g)) {
const asset = match[2].match(/asset: '([^']+)'/)?.[1];
if (asset) marks.set(match[1], `desktop/${asset}`);
}
function mark(id, label, x, y, size) {
const vector = `store/branding/${id.replace('autonomous/', '')}.svg`;
// The new original marks stay vectors in the diagram, and are also bundled
// as PNGs for the app. No letter placeholders for featured packages.
if (['autonomous/roundtable', 'autonomous/jev-browser', 'autonomous/godogen'].includes(id)) {
const body = read(vector).replace(/^<svg[^>]*>/, '').replace(/<\/svg>\s*$/, '').replace(/<title[\s\S]*?<\/title>/, '').trim();
return `<svg x="${x}" y="${y}" width="${size}" height="${size}" viewBox="0 0 64 64">${body}</svg>`;
}
const oldAsset = `.github/assets/engines/${id.replace('autonomous/', '')}.png`;
const asset = existsSync(resolve(root, oldAsset)) ? oldAsset : marks.get(id);
const ground = ['cursor', 'opencode', 'autonomous/kicad'].includes(id) ? '#29322f' : '#efeee9';
const tile = `<rect x="${x}" y="${y}" width="${size}" height="${size}" rx="12" fill="${ground}"/>`;
if (asset) {
const png = readFileSync(resolve(root, asset)).toString('base64');
return tile + `<image x="${x+5}" y="${y+5}" width="${size-10}" height="${size-10}" href="data:image/png;base64,${png}"/>`;
}
throw Error(`Missing featured logo: ${id} (${label})`);
}
const width = 1280, columns = 3, gap = 20, margin = 32;
const contentWidth = width - 2 * margin;
const cardWidth = (contentWidth - (columns - 1) * gap) / columns;
const rows = Math.ceil(groups.length / columns);
const codingY = 24, codingHeight = 342;
const craftY = codingY + codingHeight + gap, cardHeight = 244, rowStep = cardHeight + gap;
const height = craftY + rows * rowStep - gap + margin;
const parts = [
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-labelledby="title desc">`,
`<title id="title">OpenHarness: coding first, then ${agents.length} harnesses across ${groups.length} more categories</title>`,
'<desc id="desc">Coding is the main category, with fourteen supported agents. Below it, harnesses for Design, Engineering, Media, Music, Productivity, Science and Data, Simulation, Games, Research, and Local AI. The full catalog is in the README table.</desc>',
'<style>text{font-family:Inter,Arial,sans-serif;fill:#172a24}</style>',
`<rect width="${width}" height="${height}" rx="26" fill="#f3f2ec"/>`,
`<rect x="${margin}" y="${codingY}" width="${contentWidth}" height="${codingHeight}" rx="18" fill="#e4ede7" stroke="#b5c8b9"/>`,
`<text x="${margin+24}" y="73" font-size="36" font-weight="700">Coding</text>`,
];
engines.forEach(([id,name],i) => {
const slotWidth = (contentWidth - 48) / 7;
const centerX = margin + 24 + ((i % 7) + 0.5) * slotWidth;
const y = 100 + Math.floor(i / 7) * 132;
parts.push(mark(id,name,centerX-38,y,76));
parts.push(`<text x="${centerX}" y="${y+101}" text-anchor="middle" font-size="20">${escape(name)}</text>`);
});
groups.forEach((c, i) => {
const row = Math.floor(i / columns);
const cardsInRow = Math.min(columns, groups.length - row * columns);
const rowWidth = cardsInRow * cardWidth + (cardsInRow - 1) * gap;
const x = (width - rowWidth) / 2 + (i % columns) * (cardWidth + gap), y = craftY + row * rowStep;
parts.push(`<rect x="${x}" y="${y}" width="${cardWidth}" height="${cardHeight}" rx="16" fill="#fff" stroke="#dddeda"/>`);
parts.push(`<text x="${x+24}" y="${y+50}" font-size="30" font-weight="700">${escape(c.name)}</text>`);
const selected = c.featured.map(id => c.entries.find(e => e.id === `autonomous/${id}`)).filter(Boolean);
const slotWidth = (cardWidth - 48) / 3;
const groupX = x + (cardWidth - selected.length * slotWidth) / 2;
selected.forEach((e, j) => {
const centerX = groupX + (j + 0.5) * slotWidth;
parts.push(mark(e.id, e.name, centerX-42, y+80, 84));
const labels = e.name.length > 12 ? e.name.split(' ') : [e.name];
labels.slice(0, 2).forEach((line,k) => parts.push(`<text x="${centerX}" y="${y+194+k*25}" text-anchor="middle" font-size="20">${escape(line)}</text>`));
});
});
parts.push('</svg>\n');
output('.github/assets/store/workshop-overview.svg', parts.join('\n'));
console.log(`${check?'Checked':'Updated'} presentation: Coding + ${groups.length} craft categories, ${agents.length} listed harnesses, ${viewers.length} viewers.`);