app
Hero Studio
Public Made by Adomby adom
The hero-image lab with a human gate: `hero-studio pose <owner/slug>` opens the studio in front of the human, posed for their package, and blocks until they click Generate, producing the provenance-stamped 2000x1250 PNG the registry's hero gate requires. The image carries the title and artwork; the exported image also bakes the type pill (top right) and full wiki slug (bottom right); the page shows the hero bare, and only the homepage marquee overlays its own dynamic info. Every hero wears its P
master
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
#!/usr/bin/env node
// Desktop-bridge adapter (hero-studio#31): the Adom Bridge v2 rename replaced
// the `adom-desktop` binary with `adom-bridge-cli` and moved pup's verbs to a
// `pup_*` prefix -- and the relay now refuses raw HTTP posts entirely. This
// module is the ONE place that knows which binary and which verb names a box
// has: it prefers adom-bridge-cli, resolves the live verb names from
// bridge_list output (manifests have claimed prefixes the box does not
// answer, so never hardcode), and falls back to legacy adom-desktop.
import { execFile } from 'child_process';
const run = (bin, args, timeout, target, thread) => new Promise((res) => {
let a = target ? [...args, '--target', target] : [...args];
// Caller identity rides every call (#42.2): adom-bridge-cli takes the
// global --ai-thread flag; legacy adom-desktop reads ADOM_AI_THREAD env.
if (thread && bin === 'adom-bridge-cli') a = ['--ai-thread', thread, ...a];
const env = thread ? Object.assign({}, process.env, { ADOM_AI_THREAD: thread }) : process.env;
execFile(bin, a, { timeout, env }, (err, stdout, stderr) => res({
ok: !err,
out: String(stdout || ''),
errOut: String(stderr || ''),
fail: err ? (err.killed ? 'timed out after ' + timeout + 'ms' : 'exit ' + (err.code ?? '?')) : '',
}));
});
const which = (bin) => new Promise((res) => {
execFile('sh', ['-c', 'command -v ' + bin], { timeout: 4000 }, (err) => res(!err));
});
// The verbs the studio uses, by suffix; the adapter resolves each suffix to
// whatever full name the box actually answers on (pup_*, browser_*, ...).
const SUFFIXES = ['open_window', 'navigate', 'screenshot', 'eval', 'status', 'alert_window'];
export async function makeBridge(target, thread) {
thread = thread || process.env.ADOM_AI_THREAD || '';
const hasNew = await which('adom-bridge-cli');
const bin = hasNew ? 'adom-bridge-cli' : 'adom-desktop';
const verbs = {};
if (hasNew) {
// Resolve live verb names from bridge_list; a suffix seen there wins.
const bl = await run(bin, ['bridge_list'], 15000, target, thread);
for (const sfx of SUFFIXES) {
const m = (bl.out + bl.errOut).match(new RegExp('\\b([a-z][a-z0-9]*)_' + sfx + '\\b'));
if (m) verbs[sfx] = m[0];
}
// pup_* is the known-good prefix on renamed boxes; fill unresolved
// suffixes with it rather than the legacy browser_* names.
for (const sfx of SUFFIXES) if (!verbs[sfx]) verbs[sfx] = 'pup_' + sfx;
} else {
for (const sfx of SUFFIXES) verbs[sfx] = 'browser_' + sfx;
}
return {
bin,
generation: hasNew ? 'adom-bridge-cli' : 'adom-desktop (legacy)',
verbs,
/// call('open_window', {...}) -> {ok, out, errOut, fail}; args JSON-encoded
call: (suffix, args, timeout = 20000) =>
run(bin, [verbs[suffix], JSON.stringify(args ?? {})], timeout, target, thread),
/// plain verb (no JSON args), e.g. bridge_list itself
raw: (verb, timeout = 15000) => run(bin, [verb], timeout, target, thread),
};
}