Files
app/scripts/team.mjs
zeekay 95b3d9070d agents: the composer's Agents shelf, and a new agent from a persona
useAgentShelf is the + menu's Agents: the org's agents with the default
cast filling in, the cast first, each by its face; a pick seats it in
the open conversation or lets it out. Create agent and Browse personas
open the New agent sheet through make(), drawn by AgentMaker; Manage
agents is Dev's Customize > Agents. The Shelf types copy @hanzo/build's
plus.tsx until build publishes them.

The default cast is every member @hanzo/personas publishes as the team,
read from the package's team/ directory rather than a list kept here.

The sheet starts from any of the package's 1,042 figures: the name, the
PERSONA.md prompt word for word, and a face to draw. The build writes the
roster to assets/personas, so it is fetched when the list opens. A face
is drawn by zen-image, named on every call (the endpoint refuses a call
with none, so Draw never worked); Create draws one when no picture was
chosen, and Regenerate draws again.
2026-10-20 11:39:13 -07:00

57 lines
2.9 KiB
JavaScript

// What the rooms read from @hanzo/personas, from the pinned package.
//
// The core team: each member as markdown (team/<id>.md), written to
// src/team.gen.ts as strings, so no host needs an asset loader. Every member
// the package publishes is in it: ORDER is only the order the crew is drawn in,
// and a member it does not name comes after, in the package's order.
//
// The figures: the roster under personas/, for "Start from a persona". Written
// to assets/personas/ (a build product, ignored by git): index.json lists every
// figure, and <slug>.md is a figure's PERSONA.md where one is written. A host
// serves assets/ at its root, so the list is fetched when it is opened and a
// figure's prompt when it is picked; neither weighs on a bundle.
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const require = createRequire(import.meta.url)
const root = dirname(require.resolve('@hanzo/personas/team.js'))
const here = join(dirname(fileURLToPath(import.meta.url)), '..')
const { version } = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
const ORDER = ['dev', 'des', 'vi', 'feynman', 'einstein', 'nora', 'maya', 'leo']
const published = readdirSync(join(root, 'team'))
.filter((f) => f.endsWith('.md'))
.map((f) => f.slice(0, -3))
.sort()
const ids = [...ORDER, ...published.filter((id) => !ORDER.includes(id))]
const body = ids.map((id) => ` ${id}: ${JSON.stringify(readFileSync(join(root, 'team', `${id}.md`), 'utf8'))},`).join('\n')
writeFileSync(
join(here, 'src', 'team.gen.ts'),
`// Generated by scripts/team.mjs from @hanzo/personas ${version}. Do not edit.\n` +
`export const FILES = {\n${body}\n} as const\n`,
)
console.log(`team: ${ids.length} members from @hanzo/personas ${version}`)
// A PERSONA.md's front matter field, or undefined.
const field = (md, key) => new RegExp(`^${key}:\\s*(.+)$`, 'm').exec(md.split(/^---$/m)[1] ?? '')?.[1].trim()
const out = join(here, 'assets', 'personas')
rmSync(out, { recursive: true, force: true })
mkdirSync(out, { recursive: true })
const figures = readdirSync(join(root, 'personas'))
.sort()
.map((slug) => {
const dir = join(root, 'personas', slug)
const { name, category } = JSON.parse(readFileSync(join(dir, 'profile.json'), 'utf8'))
const at = join(dir, 'PERSONA.md')
if (!existsSync(at)) return { slug, name, category }
const md = readFileSync(at, 'utf8')
writeFileSync(join(out, `${slug}.md`), md)
const based = field(md, 'based_on')
return { slug, name, category, called: field(md, 'name'), about: field(md, 'description'), ...(based ? { based } : {}) }
})
writeFileSync(join(out, 'index.json'), JSON.stringify(figures))
console.log(`figures: ${figures.length}, ${figures.filter((f) => f.called).length} with a PERSONA.md`)