public documentation (0.39.0)
- amerc-api: GET /docs + /docs/:id are public (reads); writes still require auth - DocsApp/ConsoleShell: publicView mode renders docs read-only when logged out, with a 'Log in to edit' header CTA; edit/new/delete hidden for anonymous - Docs nav link now shown to everyone - seeded 5 quality guides: Quickstart, Publish an agent, Embed a chatbox, Showcase, API reference
This commit is contained in:
parent
e711a7c904
commit
c8f6d0f892
@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#0e0a14" />
|
||||
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
|
||||
<meta name="version" content="0.38.0-npc" />
|
||||
<meta name="version" content="0.39.0-docs" />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<!-- Open Graph / social share -->
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amerc-site",
|
||||
"version": "0.38.0-npc",
|
||||
"version": "0.39.0-docs",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@ -285,11 +285,11 @@ const server = http.createServer(async (req, res) => {
|
||||
const needAuth = () => send(res, 401, { ok: false, error: 'login or api key required' });
|
||||
|
||||
// DOCUMENTS
|
||||
if (path === '/docs' && method === 'GET') { if (!id) return needAuth(); return send(res, 200, { ok: true, docs: db.prepare('SELECT id,title,folder,updated_by,created_at,updated_at FROM documents ORDER BY folder,title').all() }); }
|
||||
if (path === '/docs' && method === 'GET') { return send(res, 200, { ok: true, docs: db.prepare('SELECT id,title,folder,updated_by,created_at,updated_at FROM documents ORDER BY folder,title').all() }); }
|
||||
if (path === '/docs' && method === 'POST') { if (!id) return needAuth(); const b = await readBody(req) || {}; const now = Date.now();
|
||||
const info = db.prepare('INSERT INTO documents(title,folder,body,updated_by,created_at,updated_at) VALUES(?,?,?,?,?,?)').run(b.title || 'Untitled', b.folder || '', b.body || '', who(id), now, now);
|
||||
return send(res, 200, { ok: true, id: Number(info.lastInsertRowid) }); }
|
||||
if ((m = path.match(/^\/docs\/(\d+)$/)) && method === 'GET') { if (!id) return needAuth(); const d = db.prepare('SELECT * FROM documents WHERE id=?').get(Number(m[1])); return d ? send(res, 200, { ok: true, doc: d }) : send(res, 404, { ok: false, error: 'not found' }); }
|
||||
if ((m = path.match(/^\/docs\/(\d+)$/)) && method === 'GET') { const d = db.prepare('SELECT * FROM documents WHERE id=?').get(Number(m[1])); return d ? send(res, 200, { ok: true, doc: d }) : send(res, 404, { ok: false, error: 'not found' }); }
|
||||
if ((m = path.match(/^\/docs\/(\d+)$/)) && method === 'PATCH') { if (!id) return needAuth(); const d = db.prepare('SELECT * FROM documents WHERE id=?').get(Number(m[1])); if (!d) return send(res, 404, { ok: false, error: 'not found' }); const b = await readBody(req) || {};
|
||||
db.prepare('UPDATE documents SET title=?,folder=?,body=?,updated_by=?,updated_at=? WHERE id=?').run(b.title ?? d.title, b.folder ?? d.folder, b.body ?? d.body, who(id), Date.now(), d.id); return send(res, 200, { ok: true }); }
|
||||
if ((m = path.match(/^\/docs\/(\d+)$/)) && method === 'DELETE') { if (id?.kind !== 'user') return send(res, 403, { ok: false, error: 'human login required to delete' }); db.prepare('DELETE FROM documents WHERE id=?').run(Number(m[1])); return send(res, 200, { ok: true }); }
|
||||
|
||||
@ -42,11 +42,11 @@ function ConsoleLogin({ auth }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function ConsoleShell({ title, accent = '#27d8ff', current, requireRole, children }) {
|
||||
export default function ConsoleShell({ title, accent = '#27d8ff', current, requireRole, publicView, children }) {
|
||||
const auth = useAuth();
|
||||
if (!auth.ready) return <div className="cs-app"><div className="cs-loading">Loading…</div></div>;
|
||||
if (!auth.user) return <div className="cs-app" style={{ '--accent': accent }}><ConsoleLogin auth={auth} /></div>;
|
||||
if (requireRole && auth.user.role !== requireRole) return (
|
||||
if (!auth.user && !publicView) return <div className="cs-app" style={{ '--accent': accent }}><ConsoleLogin auth={auth} /></div>;
|
||||
if (requireRole && auth.user && auth.user.role !== requireRole) return (
|
||||
<div className="cs-app" style={{ '--accent': accent }}>
|
||||
<div className="cs-login-wrap"><div className="cs-login" style={{ textAlign: 'center' }}>
|
||||
<div className="cs-brand"><span className="cs-gem" />amerc</div>
|
||||
@ -65,8 +65,9 @@ export default function ConsoleShell({ title, accent = '#27d8ff', current, requi
|
||||
{APPS.map((a) => <a key={a.label} href={a.href} className={a.label.toLowerCase() === current ? 'on' : ''}>{a.label}</a>)}
|
||||
</nav>
|
||||
<div className="cs-user">
|
||||
<span>{auth.user.handle}{auth.user.role === 'admin' ? ' · admin' : ''}</span>
|
||||
<button type="button" onClick={() => auth.logout()}>Log out</button>
|
||||
{auth.user
|
||||
? <><span>{auth.user.handle}{auth.user.role === 'admin' ? ' · admin' : ''}</span><button type="button" onClick={() => auth.logout()}>Log out</button></>
|
||||
: <a className="cs-btn cs-primary" href="https://amerc.ai/#/signin">Log in to edit</a>}
|
||||
</div>
|
||||
</header>
|
||||
<main className="cs-main">{typeof children === 'function' ? children(auth) : children}</main>
|
||||
|
||||
@ -5,7 +5,7 @@ import { api } from './api.js';
|
||||
|
||||
marked.setOptions({ breaks: true });
|
||||
|
||||
function DocsWorkspace() {
|
||||
function DocsWorkspace({ canEdit }) {
|
||||
const [docs, setDocs] = useState([]);
|
||||
const [sel, setSel] = useState(null);
|
||||
const [doc, setDoc] = useState(null);
|
||||
@ -39,7 +39,7 @@ function DocsWorkspace() {
|
||||
<aside className="docs-side">
|
||||
<div className="docs-side-top">
|
||||
<input placeholder="search docs…" value={q} onChange={(e) => setQ(e.target.value)} />
|
||||
<button className="cs-btn cs-primary" onClick={create}>+ New</button>
|
||||
{canEdit && <button className="cs-btn cs-primary" onClick={create}>+ New</button>}
|
||||
</div>
|
||||
<div className="docs-tree">
|
||||
{Object.keys(folders).sort().map((f) => (
|
||||
@ -64,10 +64,10 @@ function DocsWorkspace() {
|
||||
: <h2>{doc.title}</h2>}
|
||||
<div className="docs-actions">
|
||||
{editing && <input className="docs-folder-input" placeholder="folder" value={draft.folder} onChange={(e) => setDraft({ ...draft, folder: e.target.value })} />}
|
||||
{!editing && <button className="cs-btn" onClick={() => setEditing(true)}>Edit</button>}
|
||||
{canEdit && !editing && <button className="cs-btn" onClick={() => setEditing(true)}>Edit</button>}
|
||||
{editing && <button className="cs-btn cs-primary" onClick={save}>Save</button>}
|
||||
{editing && <button className="cs-btn" onClick={() => { setEditing(false); setDraft({ title: doc.title, folder: doc.folder, body: doc.body }); }}>Cancel</button>}
|
||||
<button className="cs-btn" onClick={del}>Delete</button>
|
||||
{canEdit && <button className="cs-btn" onClick={del}>Delete</button>}
|
||||
</div>
|
||||
</div>
|
||||
<p className="docs-byline">updated by {doc.updated_by || '—'}</p>
|
||||
@ -82,5 +82,5 @@ function DocsWorkspace() {
|
||||
}
|
||||
|
||||
export default function DocsApp() {
|
||||
return <ConsoleShell title="docs" accent="#27d8ff" current="docs">{() => <DocsWorkspace />}</ConsoleShell>;
|
||||
return <ConsoleShell title="docs" accent="#27d8ff" current="docs" publicView>{(auth) => <DocsWorkspace canEdit={!!auth.user} />}</ConsoleShell>;
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import Account from './Account.jsx';
|
||||
import TavernGame from './TavernGame.jsx';
|
||||
import { api } from './api.js';
|
||||
|
||||
const RELEASE = '0.38.0-npc';
|
||||
const RELEASE = '0.39.0-docs';
|
||||
|
||||
const NAV = [
|
||||
{ id: 'home', label: 'Tavern' },
|
||||
@ -47,7 +47,7 @@ function PixelTopbar({ route, setRoute, auth }) {
|
||||
{NAV.map((item) => (
|
||||
<button key={item.id} className={`px-nav-btn${route === item.id ? ' active' : ''}`} onClick={() => go(item.id)} type="button">{item.label}</button>
|
||||
))}
|
||||
{loggedIn && <a className="px-nav-btn" href="https://docs.amerc.ai/">Docs</a>}
|
||||
<a className="px-nav-btn" href="https://docs.amerc.ai/">Docs</a>
|
||||
{isAdmin && <a className="px-nav-btn" href="https://pm.amerc.ai/">PM</a>}
|
||||
{isAdmin && <button className={`px-nav-btn quartermaster${route === 'admin' ? ' active' : ''}`} onClick={() => go('admin')} type="button">Quartermaster</button>}
|
||||
{loggedIn
|
||||
|
||||
Loading…
Reference in New Issue
Block a user