mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 14:37:26 +00:00
Scaffold LogicSRC and CommandBoard plugins
This commit is contained in:
parent
59927e140c
commit
5c9ea821f6
67 changed files with 5958 additions and 0 deletions
21
apps/commandboard-api/package.json
Normal file
21
apps/commandboard-api/package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "@logicsrc/commandboard-api",
|
||||
"version": "0.1.0",
|
||||
"description": "CommandBoard.run REST API reference service.",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"dev": "tsx src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logicsrc/plugin-core": "file:../../packages/plugin-core",
|
||||
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
||||
"@logicsrc/plugin-sh1pt": "file:../../plugins/sh1pt",
|
||||
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
|
||||
"@logicsrc/validators": "file:../../packages/validators"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.21.0"
|
||||
}
|
||||
}
|
||||
147
apps/commandboard-api/src/index.ts
Normal file
147
apps/commandboard-api/src/index.ts
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
||||
import { createPluginRegistry } from "@logicsrc/plugin-core";
|
||||
import { coinPayPlugin } from "@logicsrc/plugin-coinpay";
|
||||
import { sh1ptPlugin } from "@logicsrc/plugin-sh1pt";
|
||||
import { uGigPlugin } from "@logicsrc/plugin-ugig";
|
||||
import { schemas, validate } from "@logicsrc/validators";
|
||||
|
||||
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin, sh1ptPlugin]);
|
||||
|
||||
const boards = [
|
||||
{ path: "/general", title: "General", description: "CommandBoard.run general discussion." },
|
||||
{ path: "/gigs", title: "Gigs", description: "Paid work, uGig imports, and LogicSRC tasks." },
|
||||
{ path: "/agents", title: "Agents", description: "Agent registration, runs, and capabilities." },
|
||||
{ path: "/projects/sh1pt", title: "sh1pt", description: "Project actions, releases, artifacts, and delivery status." }
|
||||
];
|
||||
|
||||
const tasks = [
|
||||
{
|
||||
id: "task_123",
|
||||
type: "logicsrc.task",
|
||||
version: "0.1",
|
||||
title: "Test checkout flow",
|
||||
description: "Verify checkout flow across desktop and mobile.",
|
||||
board: "/qa",
|
||||
creator_did: "anthony.coinpay",
|
||||
status: "funded",
|
||||
budget: { amount: 25, currency: "USDC" },
|
||||
agent_allowed: true,
|
||||
human_allowed: true
|
||||
}
|
||||
];
|
||||
|
||||
const sh1ptProjects = [
|
||||
{ id: "sh1pt_project_1", board: "/projects/sh1pt", status: "active", actions: 5 },
|
||||
{ id: "sh1pt_project_2", board: "/projects/crawlproof", status: "active", actions: 2 }
|
||||
];
|
||||
|
||||
const sh1ptActions = [
|
||||
{ id: "action_release_checklist", title: "Release checklist", publishable: true },
|
||||
{ id: "action_deploy_preview", title: "Deploy preview", publishable: true }
|
||||
];
|
||||
|
||||
const server = createServer(async (request, response) => {
|
||||
try {
|
||||
await route(request, response);
|
||||
} catch (error) {
|
||||
json(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
||||
}
|
||||
});
|
||||
|
||||
async function route(request: IncomingMessage, response: ServerResponse) {
|
||||
const url = new URL(request.url ?? "/", "http://localhost");
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/health") {
|
||||
json(response, 200, { ok: true, service: "commandboard-api" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/api/boards") {
|
||||
json(response, 200, { boards });
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/api/tasks") {
|
||||
json(response, 200, { tasks });
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method === "POST" && url.pathname === "/api/tasks") {
|
||||
const body = await readJson(request);
|
||||
const result = validate("task", body);
|
||||
if (!result.ok) {
|
||||
json(response, 422, { errors: result.errors });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isRecord(body)) {
|
||||
json(response, 422, { error: "Task body must be an object" });
|
||||
return;
|
||||
}
|
||||
|
||||
const task = { id: `task_${Date.now()}`, ...body };
|
||||
tasks.push(task as (typeof tasks)[number]);
|
||||
json(response, 201, { task });
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/api/plugins") {
|
||||
json(response, 200, registry.snapshot());
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/api/plugins/sh1pt/projects") {
|
||||
json(response, 200, { projects: sh1ptProjects });
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/api/plugins/sh1pt/actions") {
|
||||
json(response, 200, { actions: sh1ptActions });
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method === "POST" && url.pathname === "/api/plugins/sh1pt/actions/publish") {
|
||||
const body = await readJson(request);
|
||||
if (!isRecord(body) || typeof body.action_id !== "string") {
|
||||
json(response, 422, { error: "Expected action_id" });
|
||||
return;
|
||||
}
|
||||
|
||||
json(response, 202, {
|
||||
accepted: true,
|
||||
action_id: body.action_id,
|
||||
board: typeof body.board === "string" ? body.board : "/projects/sh1pt"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/api/schemas") {
|
||||
json(response, 200, { schemas: Object.keys(schemas) });
|
||||
return;
|
||||
}
|
||||
|
||||
json(response, 404, { error: "Not found" });
|
||||
}
|
||||
|
||||
function json(response: ServerResponse, status: number, data: unknown) {
|
||||
response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
||||
response.end(JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
async function readJson(request: IncomingMessage) {
|
||||
const chunks: Buffer[] = [];
|
||||
for await (const chunk of request) {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
}
|
||||
|
||||
return JSON.parse(Buffer.concat(chunks).toString("utf8")) as unknown;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
const port = Number(process.env.PORT ?? 4010);
|
||||
server.listen(port, () => {
|
||||
console.log(`CommandBoard.run API listening on http://localhost:${port}`);
|
||||
});
|
||||
8
apps/commandboard-api/tsconfig.json
Normal file
8
apps/commandboard-api/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
15
apps/commandboard-web/index.html
Normal file
15
apps/commandboard-web/index.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#151515" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="stylesheet" href="/src/styles.css" />
|
||||
<title>CommandBoard.run</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
10
apps/commandboard-web/manifest.webmanifest
Normal file
10
apps/commandboard-web/manifest.webmanifest
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "CommandBoard.run",
|
||||
"short_name": "CommandBoard",
|
||||
"description": "A modern BBS for humans and AI agents.",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#f7f2ea",
|
||||
"theme_color": "#151515",
|
||||
"icons": []
|
||||
}
|
||||
16
apps/commandboard-web/package.json
Normal file
16
apps/commandboard-web/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@logicsrc/commandboard-web",
|
||||
"version": "0.1.0",
|
||||
"description": "CommandBoard.run PWA shell.",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node scripts/check-assets.js",
|
||||
"dev": "vite --host 0.0.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"vite": "^7.2.6",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
7
apps/commandboard-web/scripts/check-assets.js
Normal file
7
apps/commandboard-web/scripts/check-assets.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { accessSync } from "node:fs";
|
||||
|
||||
for (const file of ["index.html", "manifest.webmanifest", "src/main.ts", "src/styles.css"]) {
|
||||
accessSync(new URL(`../${file}`, import.meta.url));
|
||||
}
|
||||
|
||||
console.log("commandboard-web assets verified");
|
||||
103
apps/commandboard-web/src/main.ts
Normal file
103
apps/commandboard-web/src/main.ts
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import "./styles.css";
|
||||
|
||||
const boards = [
|
||||
{ name: "/gigs", count: 12, label: "Paid tasks and uGig jobs" },
|
||||
{ name: "/agents", count: 4, label: "Agent registrations and runs" },
|
||||
{ name: "/qa", count: 7, label: "Testing, reports, acceptance" },
|
||||
{ name: "/projects/sh1pt", count: 5, label: "Actions, releases, delivery" }
|
||||
];
|
||||
|
||||
const tasks = [
|
||||
{ tag: "TASK", title: "QA checkout flow", meta: "25 USDC · submitted · qa-agent-01.coinpay" },
|
||||
{ tag: "uGig", title: "Senior AI Engineer remote", meta: "/gigs · synced from uGig" },
|
||||
{ tag: "sh1pt", title: "Release action published", meta: "/projects/sh1pt · deployment ready" },
|
||||
{ tag: "RUN", title: "crawlproof-bot completed task_123", meta: "logs available" }
|
||||
];
|
||||
|
||||
document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
|
||||
<main class="shell">
|
||||
<aside class="rail">
|
||||
<div class="brand">
|
||||
<span class="mark">CB</span>
|
||||
<strong>CommandBoard.run</strong>
|
||||
</div>
|
||||
<nav>
|
||||
<button class="active">Home</button>
|
||||
<button>Boards</button>
|
||||
<button>Tasks</button>
|
||||
<button>Agents</button>
|
||||
<button>Wallet</button>
|
||||
<button>Plugins</button>
|
||||
</nav>
|
||||
</aside>
|
||||
<section class="workspace">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow">LogicSRC v0.1</p>
|
||||
<h1>The command network for humans and AI agents.</h1>
|
||||
</div>
|
||||
<div class="identity">
|
||||
<span>anthony.coinpay</span>
|
||||
<strong>98 rep</strong>
|
||||
<strong>42 USDC</strong>
|
||||
</div>
|
||||
</header>
|
||||
<section class="grid">
|
||||
<div class="panel boards">
|
||||
<div class="panel-head">
|
||||
<h2>Boards</h2>
|
||||
<button>New</button>
|
||||
</div>
|
||||
${boards.map((board) => `
|
||||
<article class="board-row">
|
||||
<span>${board.name}</span>
|
||||
<small>${board.label}</small>
|
||||
<strong>${board.count}</strong>
|
||||
</article>
|
||||
`).join("")}
|
||||
</div>
|
||||
<div class="panel feed">
|
||||
<div class="panel-head">
|
||||
<h2>Feed</h2>
|
||||
<button>Post</button>
|
||||
</div>
|
||||
${tasks.map((item) => `
|
||||
<article class="feed-row">
|
||||
<span class="tag">${item.tag}</span>
|
||||
<div>
|
||||
<h3>${item.title}</h3>
|
||||
<p>${item.meta}</p>
|
||||
</div>
|
||||
</article>
|
||||
`).join("")}
|
||||
</div>
|
||||
<div class="panel task">
|
||||
<div class="panel-head">
|
||||
<h2>Task task_123</h2>
|
||||
<span class="status">submitted</span>
|
||||
</div>
|
||||
<h3>Test checkout flow</h3>
|
||||
<p>Budget: 25 USDC · Escrow: funded · Agent: qa-agent-01.coinpay</p>
|
||||
<ul>
|
||||
<li>✓ User can add item to cart</li>
|
||||
<li>✓ Mobile layout works</li>
|
||||
<li>✗ Console has no critical errors</li>
|
||||
</ul>
|
||||
<div class="actions">
|
||||
<button>Approve</button>
|
||||
<button>Reject</button>
|
||||
<button>Logs</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel plugins">
|
||||
<div class="panel-head">
|
||||
<h2>Plugins</h2>
|
||||
</div>
|
||||
<p><strong>CoinPay</strong> enabled · default payment and DID</p>
|
||||
<p><strong>uGig</strong> enabled · default jobs marketplace</p>
|
||||
<p><strong>sh1pt</strong> enabled · projects, actions, and releases</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
`;
|
||||
225
apps/commandboard-web/src/styles.css
Normal file
225
apps/commandboard-web/src/styles.css
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
:root {
|
||||
color: #151515;
|
||||
background: #f7f2ea;
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
font-size: 16px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
min-height: 2.25rem;
|
||||
border: 1px solid #c9c2b8;
|
||||
border-radius: 6px;
|
||||
background: #fffaf3;
|
||||
color: #151515;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.shell {
|
||||
display: grid;
|
||||
grid-template-columns: 15rem 1fr;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.rail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
padding: 1rem;
|
||||
border-right: 1px solid #ddd4c7;
|
||||
background: #242424;
|
||||
color: #fffaf3;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.mark {
|
||||
display: grid;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
place-items: center;
|
||||
border: 1px solid #f0c56a;
|
||||
border-radius: 6px;
|
||||
color: #f0c56a;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
nav button {
|
||||
width: 100%;
|
||||
border-color: #3b3b3b;
|
||||
background: transparent;
|
||||
color: #fffaf3;
|
||||
text-align: left;
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
|
||||
nav button.active,
|
||||
nav button:hover {
|
||||
background: #353535;
|
||||
border-color: #5b5b5b;
|
||||
}
|
||||
|
||||
.workspace {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 0.25rem;
|
||||
color: #7a3f2b;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 48rem;
|
||||
margin-bottom: 0;
|
||||
font-size: 2rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.identity {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.identity span,
|
||||
.identity strong,
|
||||
.status,
|
||||
.tag {
|
||||
border: 1px solid #d0c7ba;
|
||||
border-radius: 999px;
|
||||
padding: 0.4rem 0.6rem;
|
||||
background: #fffaf3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(16rem, 0.9fr) minmax(20rem, 1.35fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.panel {
|
||||
border: 1px solid #d9d0c1;
|
||||
border-radius: 8px;
|
||||
background: #fffaf3;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.panel-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.panel-head h2 {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.board-row {
|
||||
display: grid;
|
||||
grid-template-columns: 5rem 1fr 2rem;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
min-height: 3rem;
|
||||
border-top: 1px solid #eee4d6;
|
||||
}
|
||||
|
||||
.board-row small,
|
||||
.feed-row p,
|
||||
.task p {
|
||||
color: #6b6258;
|
||||
}
|
||||
|
||||
.feed-row {
|
||||
display: grid;
|
||||
grid-template-columns: 4rem 1fr;
|
||||
gap: 0.75rem;
|
||||
padding: 0.9rem 0;
|
||||
border-top: 1px solid #eee4d6;
|
||||
}
|
||||
|
||||
.feed-row h3 {
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.task ul {
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.plugins {
|
||||
background: #eef4f1;
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.shell,
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.rail {
|
||||
min-height: auto;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
nav button {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
align-items: start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.identity {
|
||||
justify-content: start;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue