diff --git a/README.md b/README.md
index a09a461..4643621 100644
--- a/README.md
+++ b/README.md
@@ -5,11 +5,15 @@
[](LICENSE)
[](scripts/agent-comms.mjs)
+[](https://agent-comms.github.io/agent-comms-demo/)
+
Async communication infrastructure for coding and operations agents that share a
human operator.
Created by [Shay Palachy Affek](http://www.shaypalachy.com/).
+Live public demo:
+
The project is intentionally product-neutral. It provides the open-source core
for:
diff --git a/docs/assets/agent-comms-demo-dashboard.png b/docs/assets/agent-comms-demo-dashboard.png
new file mode 100644
index 0000000..2fe66d5
Binary files /dev/null and b/docs/assets/agent-comms-demo-dashboard.png differ
diff --git a/package.json b/package.json
index 1d8cc7e..4b7645b 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc -b && vite build",
+ "build:demo": "VITE_AGENT_COMMS_DEMO=1 VITE_BASE=/agent-comms-demo/ npm run build",
"preview": "vite preview --host 127.0.0.1",
"test": "vitest run",
"check": "tsc -b"
diff --git a/src/App.tsx b/src/App.tsx
index 88a9622..57ead47 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -52,7 +52,9 @@ const emptyState: AgentCommsState = {
todos: [],
};
-const useDemoData = import.meta.env.DEV && new URLSearchParams(window.location.search).get("demo") === "1";
+const useDemoData =
+ import.meta.env.VITE_AGENT_COMMS_DEMO === "1" ||
+ (import.meta.env.DEV && new URLSearchParams(window.location.search).get("demo") === "1");
const themePreferenceKey = "agent-comms-theme-mode";
const nightModeTheme: Record = {
@@ -1508,6 +1510,9 @@ export function App() {
const operatorRequest = useCallback(
async (path: string, options: RequestInit = {}) => {
+ if (useDemoData) {
+ throw new Error("Demo mode uses public sample data and does not write to an operator API.");
+ }
const controller = new AbortController();
const timeout = window.setTimeout(() => controller.abort(), 8000);
const headers: Record = {
@@ -1685,10 +1690,12 @@ export function App() {
}, [liveSessions, operatorRequest, operatorToken]);
useEffect(() => {
+ if (useDemoData) return;
void refreshOperatorData();
}, [refreshOperatorData]);
useEffect(() => {
+ if (useDemoData) return;
const timer = window.setInterval(() => {
void refreshOperatorData();
}, 1000);
@@ -1696,6 +1703,10 @@ export function App() {
}, [refreshOperatorData]);
useEffect(() => {
+ if (useDemoData) {
+ document.title = defaultBranding.appName;
+ return;
+ }
let cancelled = false;
void loadDeploymentBranding().then((nextBranding) => {
if (cancelled) return;
diff --git a/src/styles.css b/src/styles.css
index d7b4548..e7d74a2 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -47,6 +47,7 @@ button {
display: grid;
grid-template-columns: 280px minmax(0, 1fr);
min-height: 100vh;
+ color: var(--color-text);
background: var(--color-bg);
}
diff --git a/vite.config.ts b/vite.config.ts
index 081c8d9..c5780b8 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -2,5 +2,6 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
+ base: process.env.VITE_BASE ?? "/",
plugins: [react()],
});