legion: rename My Booth -> My Legion, Mansion -> My Mansion (1.0.0)

Route 'legion' with a legacy '#/booth' alias; BoothDashboard -> LegionDashboard;
nav, footer, hotspots, WebMCP enum, gate copy all renamed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
artheru 2026-06-11 02:33:41 +08:00
parent 80d7140d1f
commit c2a8616255
8 changed files with 39 additions and 38 deletions

View File

@ -1 +0,0 @@
{"sessionId":"17e654e8-7dd5-4856-85c5-0cfeb7ab736b","pid":3488121,"procStart":"252909119","acquiredAt":1781038486936}

View File

@ -1,6 +1,6 @@
{
"name": "amerc-site",
"version": "0.99.0-boothform",
"version": "1.0.0-legion",
"private": true,
"type": "module",
"scripts": {

View File

@ -34,8 +34,8 @@ export default function Account({ auth, setRoute }) {
<p className="acct-email">{u.email}</p>
<span className={`acct-role acct-role-${u.role}`}>{u.role}</span>
<div className="acct-links">
<button className="px-action" onClick={() => setRoute('booth')}>My Booth</button>
<button className="px-action" onClick={() => setRoute('mansion')}>Mansion</button>
<button className="px-action" onClick={() => setRoute('legion')}>My Legion</button>
<button className="px-action" onClick={() => setRoute('mansion')}>My Mansion</button>
<button className="px-action" onClick={() => auth.logout()}>Log out</button>
</div>
</div>

View File

@ -96,11 +96,11 @@ export function AgentBrowse({ auth, setRoute }) {
</button>
))}
{loaded && classes.length > 0 && (
<button className="ap-card ap-card-add" onClick={() => setRoute('booth')}>
<button className="ap-card ap-card-add" onClick={() => setRoute('legion')}>
<span className="ap-add-plus" aria-hidden="true"></span>
<strong>Publish your own</strong>
<p>Register an agent class and run it from your booth your mercenary joins the roster right here.</p>
<span className="ap-add-cta">Open My Booth </span>
<p>Register an agent class and field it from your legion your mercenary joins the roster right here.</p>
<span className="ap-add-cta">Open My Legion </span>
</button>
)}
{loaded && !classes.length && (q || tag) && (
@ -115,11 +115,11 @@ export function AgentBrowse({ auth, setRoute }) {
<div className="ap-empty-start">
<h3>No agents here yet be the first.</h3>
<ol>
<li><b>Publish a class</b> in <button className="ap-link" onClick={() => setRoute('booth')}>My Booth</button> name it, tag it (e.g. <code>backend</code>, <code>model</code>).</li>
<li><b>Publish a class</b> in <button className="ap-link" onClick={() => setRoute('legion')}>My Legion</button> name it, tag it (e.g. <code>backend</code>, <code>model</code>).</li>
<li><b>Run an instance</b> your broker connects and it goes <span style={{ color: STATUS_COLOR.online }}>online</span>.</li>
<li><b>Use it</b> embed it as a chatbox or drive it through a web terminal.</li>
</ol>
<button className="px-action" onClick={() => setRoute('booth')}>Open My Booth </button>
<button className="px-action" onClick={() => setRoute('legion')}>Open My Legion </button>
</div>
)}
</div>
@ -166,7 +166,7 @@ function ClassModal({ id, auth, onClose }) {
<button className="px-action" disabled={i.status !== 'online'} onClick={() => setUse(i)}>Use </button>
</div>
))}
{!data.instances.length && <p className="ap-hint">No instances published. The owner can publish one in My Booth.</p>}
{!data.instances.length && <p className="ap-hint">No instances published. The owner can field one in My Legion.</p>}
</div>
{use && <UsePanel instance={use} onClose={() => setUse(null)} />}
</div>
@ -224,7 +224,7 @@ export function UsePanel({ instance, onClose }) {
);
}
// ---- My Booth: publish classes + instances, manage ------------------------
// ---- My Legion: publish classes + instances, manage ------------------------
export function KeysCard() {
const [keys, setKeys] = useState([]);
const [name, setName] = useState('');
@ -267,7 +267,7 @@ export function KeysCard() {
);
}
export function BoothDashboard({ auth, setRoute }) {
export function LegionDashboard({ auth, setRoute }) {
const [classes, setClasses] = useState([]);
const [insts, setInsts] = useState([]);
const [err, setErr] = useState('');
@ -296,7 +296,7 @@ export function BoothDashboard({ auth, setRoute }) {
if (!auth.user) return (
<div className="ap ap-gate ap-gate-rich">
<span className="ap-gate-ico" aria-hidden="true">🗡</span>
<h2>Your booth awaits</h2>
<h2>Your legion awaits</h2>
<p>Log in to publish agent classes, run instances, and put your mercenaries to work as an embedded chatbox, a live web terminal, or behind a public URL.</p>
<div className="ap-gate-cta">
<button className="gm-cta-btn gm-cta-primary" onClick={() => setRoute('signin')}>Log in / Sign up</button>
@ -318,7 +318,7 @@ export function BoothDashboard({ auth, setRoute }) {
return (
<div className="ap">
<div className="ap-head"><h2>My Booth</h2><span className="ap-by">{auth.user.handle}</span></div>
<div className="ap-head"><h2>My Legion</h2><span className="ap-by">{auth.user.handle}</span></div>
{err && <p className="px-auth-err">{err}</p>}
<div className="ap-booth-grid">
<form className="ap-publish" onSubmit={publishClass}>
@ -358,12 +358,12 @@ export function BoothDashboard({ auth, setRoute }) {
) : !classes.length ? (
<div className="ap-booth-welcome">
<span className="ap-booth-welcome-ico" aria-hidden="true">🗡</span>
<h3>Welcome to your booth, {auth.user.handle}.</h3>
<h3>Welcome to your legion, {auth.user.handle}.</h3>
<p>This is where you arm and dispatch your mercenaries. Three steps to go live:</p>
<ol>
<li><b>Publish a class</b> name your agent and tag its skills, in the form on the left.</li>
<li><b>Run an instance</b> your broker connects and it turns <span className="ap-on-word">online</span>.</li>
<li><b>Deliver it</b> embed a chatbox, drive a web terminal, or expose a local port via the <b>Mansion</b>.</li>
<li><b>Deliver it</b> embed a chatbox, drive a web terminal, or expose a local port via <b>My Mansion</b>.</li>
</ol>
<p className="ap-hint">Your broker authenticates with an instance accesskey (shown when you publish one) or an agent key mint one below.</p>
</div>

View File

@ -12,7 +12,7 @@ export default function Mansion({ auth, setRoute }) {
<header className="mn-hero">
<div className="mn-hero-emblem" aria-hidden="true">🏰</div>
<div className="mn-hero-body">
<h2>The Mansion</h2>
<h2>My Mansion</h2>
<p>A hall of amerc services that put your work on the open internet each lives behind amerc's edge and is brought to life by your own broker. No servers to rent, no ports to forward.</p>
{loggedIn
? <span className="mn-hero-you"><i className="mn-live-dot" /> {auth.user.handle}'s rooms are open</span>
@ -30,7 +30,7 @@ export default function Mansion({ auth, setRoute }) {
<SoonRoom
ico="🤖" name="Hosted webagent" tag="runtime"
desc="Let amerc host the broker for you — keep an agent class always-on without leaving your own machine running."
feats={['always-on instances', 'managed runtime', 'one-click from My Booth']}
feats={['always-on instances', 'managed runtime', 'one-click from My Legion']}
/>
</div>
</div>

View File

@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useAuth, AuthPanel, AdminConsole } from './Auth.jsx';
import { AgentBrowse, BoothDashboard } from './AgentPlatform.jsx';
import { AgentBrowse, LegionDashboard } from './AgentPlatform.jsx';
import Mansion from './Mansion.jsx';
import Account from './Account.jsx';
import TavernGame from './TavernGame.jsx';
@ -10,25 +10,26 @@ import { api } from './api.js';
const Menu = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" /></svg>);
const X = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>);
const RELEASE = '0.99.0-boothform';
const RELEASE = '1.0.0-legion';
const NAV = [
{ id: 'home', label: 'Tavern' },
{ id: 'agents', label: 'Browse Agents' },
{ id: 'mansion', label: 'Mansion' },
{ id: 'booth', label: 'My Booth' },
{ id: 'mansion', label: 'My Mansion' },
{ id: 'legion', label: 'My Legion' },
];
const VALID_ROUTES = ['home', 'agents', 'mansion', 'booth', 'account', 'admin', 'signin', 'status', 'changelog'];
const VALID_ROUTES = ['home', 'agents', 'mansion', 'legion', 'account', 'admin', 'signin', 'status', 'changelog'];
const ROUTE_TITLES = {
home: 'amerc — hire agents like mercenaries', agents: 'Browse Agents · amerc', mansion: 'The Mansion · amerc',
booth: 'My Booth · amerc', account: 'Account · amerc', signin: 'Sign in · amerc', status: 'System status · amerc',
home: 'amerc — hire agents like mercenaries', agents: 'Browse Agents · amerc', mansion: 'My Mansion · amerc',
legion: 'My Legion · amerc', account: 'Account · amerc', signin: 'Sign in · amerc', status: 'System status · amerc',
changelog: 'Changelog · amerc', admin: 'Quartermaster · amerc',
};
function useHashRoute() {
const normalize = () => {
// first path segment is the route; the rest (e.g. #/agents/<slug>) is a sub-path
const base = window.location.hash.replace(/^#\/?/, '').split('/')[0];
let base = window.location.hash.replace(/^#\/?/, '').split('/')[0];
if (base === 'booth') base = 'legion'; // legacy alias for the renamed route
return VALID_ROUTES.includes(base) ? base : 'home';
};
const [route, setRouteState] = useState(normalize);
@ -127,7 +128,7 @@ function Home({ setRoute }) {
<p>Hire, run, and deliver AI agents across any software boundary as an embedded chatbox, a live web terminal, or a public URL. <b>No infrastructure, just skills.</b></p>
<div className="gm-cta">
<button className="gm-cta-btn gm-cta-primary" onClick={() => setRoute('agents')}>Browse Agents </button>
<button className="gm-cta-btn gm-cta-secondary" onClick={() => setRoute('booth')}>Publish yours</button>
<button className="gm-cta-btn gm-cta-secondary" onClick={() => setRoute('legion')}>Publish yours</button>
</div>
{stats && (
<div className="hero-live">
@ -148,8 +149,8 @@ function Home({ setRoute }) {
const HF_STEPS = [
{ icon: '🍺', title: 'Browse & hire', body: 'Walk the tavern floor and pick a mercenary by skill and tag. See runtime, online status, and what each agent can do.', cta: ['Browse Agents', 'agents'] },
{ icon: '🗡️', title: 'Publish & run', body: 'Register your own agent class in My Booth and run instances — your broker brings the runtime. No infrastructure to manage.', cta: ['Open My Booth', 'booth'] },
{ icon: '🔌', title: 'Deliver anywhere', body: 'Serve users as an embedded chatbox, drive a live web terminal, or expose a local port to the internet via Showcase.', cta: ['The Mansion', 'mansion'] },
{ icon: '🗡️', title: 'Publish & run', body: 'Register your own agent class in My Legion and run instances — your broker brings the runtime. No infrastructure to manage.', cta: ['Open My Legion', 'legion'] },
{ icon: '🔌', title: 'Deliver anywhere', body: 'Serve users as an embedded chatbox, drive a live web terminal, or expose a local port to the internet via Showcase.', cta: ['My Mansion', 'mansion'] },
];
const HF_USES = [
{ icon: '💬', title: 'Chatbox', body: 'Drop one <script> tag on any site; the agent appears as a chat widget with your page skills injected.' },
@ -334,7 +335,7 @@ function HomeFeatures({ setRoute }) {
<ActivityFeed />
<div className="hf-cta">
<button className="px-action" onClick={() => setRoute('agents')}>Browse Agents</button>
<button className="px-action" onClick={() => setRoute('booth')}>Publish an Agent</button>
<button className="px-action" onClick={() => setRoute('legion')}>Publish an Agent</button>
</div>
</section>
);
@ -359,8 +360,8 @@ function Footer({ setRoute }) {
</div>
<div className="ft-col"><h4>Product</h4>
<button onClick={() => setRoute('agents')}>Browse Agents</button>
<button onClick={() => setRoute('mansion')}>Mansion</button>
<button onClick={() => setRoute('booth')}>My Booth</button>
<button onClick={() => setRoute('mansion')}>My Mansion</button>
<button onClick={() => setRoute('legion')}>My Legion</button>
</div>
<div className="ft-col"><h4>Resources</h4>
<a href="https://docs.amerc.ai/">Docs</a>
@ -409,7 +410,7 @@ function Gatehouse({ auth, setRoute }) {
<div className="gate-form">
<div className="px-board gate-board">
<header className="px-board-head"><span className="px-board-tag">ONE ACCOUNT</span><h2>Welcome, mercenary</h2><p>Log in, or create your account in seconds.</p></header>
<AuthPanel auth={auth} onDone={() => setRoute('booth')} />
<AuthPanel auth={auth} onDone={() => setRoute('legion')} />
</div>
</div>
</div>
@ -476,6 +477,7 @@ function StatusPage() {
}
const CHANGELOG = [
{ v: '1.0', date: 'Jun 2026', title: 'Legion & My Mansion', notes: ['My Booth is now your Legion — your war-band of agent classes and the instances you field', 'The Mansion became My Mansion, your estate of live services'] },
{ v: '0.99', date: 'Jun 2026', title: 'Clearer publish form', notes: ['The Booth publish form now labels every field, so the pre-filled Kind and Launch command are no longer ambiguous'] },
{ v: '0.98', date: 'Jun 2026', title: 'Live API console', notes: ['The home-page API peek now shows the response status and timing, with a run button to re-fetch'] },
{ v: '0.97', date: 'Jun 2026', title: 'Clearer sign-in', notes: ['The join form now uses plain labels (Email, Password) and a clear Log in button'] },
@ -544,7 +546,7 @@ function Scene({ route, setRoute, auth }) {
if (route === 'home') return <Home setRoute={setRoute} auth={auth} />;
if (route === 'agents') return <Page setRoute={setRoute}><AgentBrowse auth={auth} setRoute={setRoute} /></Page>;
if (route === 'mansion') return <Page setRoute={setRoute}><Mansion auth={auth} setRoute={setRoute} /></Page>;
if (route === 'booth') return <Page setRoute={setRoute}><BoothDashboard auth={auth} setRoute={setRoute} /></Page>;
if (route === 'legion') return <Page setRoute={setRoute}><LegionDashboard auth={auth} setRoute={setRoute} /></Page>;
if (route === 'account') return <Page setRoute={setRoute}><Account auth={auth} setRoute={setRoute} /></Page>;
if (route === 'status') return <Page setRoute={setRoute}><StatusPage /></Page>;
if (route === 'changelog') return <Page setRoute={setRoute}><ChangelogPage /></Page>;

View File

@ -126,10 +126,10 @@ export default function TavernGame({ onHotspot, stats }) {
<div className="gm-overlay">
<button className="gm-hot" style={{ left: '38%', top: '1%', width: '24%', height: '16%' }} onClick={() => onHotspot?.('agents')} title="Browse Agents (screen)" />
<button className="gm-label gm-label-screen" style={{ left: '50%', top: '18%' }} onClick={() => onHotspot?.('agents')}> Browse Agents</button>
<button className="gm-hot" style={{ left: '44%', top: '50%', width: '14%', height: '24%' }} onClick={() => onHotspot?.('booth')} title="My Booth" />
<button className="gm-label gm-label-booth" style={{ left: '50%', top: '90%' }} onClick={() => onHotspot?.('booth')}> Enter My Booth</button>
<button className="gm-hot" style={{ left: '44%', top: '50%', width: '14%', height: '24%' }} onClick={() => onHotspot?.('legion')} title="My Legion" />
<button className="gm-label gm-label-booth" style={{ left: '50%', top: '90%' }} onClick={() => onHotspot?.('legion')}> Enter My Legion</button>
<Npc x="11%" y="4%" w="13%" h="20%" who="Barkeep" say="What'll it be? The staff roster is up on the board." onClick={() => onHotspot?.('agents')} />
<Npc x="11%" y="52%" w="13%" h="22%" who="Patron" say="I run three mercenaries straight from my booth." onClick={() => onHotspot?.('booth')} />
<Npc x="11%" y="52%" w="13%" h="22%" who="Patron" say="I field three mercenaries straight from my legion." onClick={() => onHotspot?.('legion')} />
<Npc x="72%" y="52%" w="13%" h="22%" who="Patron" say="Showcase put my little app on the open internet!" onClick={() => onHotspot?.('mansion')} />
</div>
</div>

View File

@ -47,7 +47,7 @@ export function registerAmercTools() {
mc.registerTool({
name: 'amerc_open',
description: 'Open a section of the amerc tavern in the browser so the user can see it.',
inputSchema: { type: 'object', properties: { section: { type: 'string', enum: ['home', 'agents', 'mansion', 'booth', 'account', 'status', 'changelog', 'signin'], description: 'which section to open' } }, required: ['section'] },
inputSchema: { type: 'object', properties: { section: { type: 'string', enum: ['home', 'agents', 'mansion', 'legion', 'account', 'status', 'changelog', 'signin'], description: 'which section to open' } }, required: ['section'] },
execute: async (input) => { window.location.hash = `#/${input.section}`; return { ok: true, opened: input.section }; },
});
} catch { /* WebMCP API shape differs — ignore, the site is unaffected */ }