first
This commit is contained in:
38
agent/agent-ui/README.md
Normal file
38
agent/agent-ui/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
<!--
|
||||
Created by: Codex
|
||||
Date: 2026-01-05
|
||||
Purpose: Desktop UI for Mesh Agent (Tauri)
|
||||
Refs: CLAUDE.md
|
||||
-->
|
||||
|
||||
# Mesh Agent UI (Tauri)
|
||||
|
||||
This is a lightweight desktop UI for the Mesh agent.
|
||||
|
||||
## Features (MVP)
|
||||
- Show agent status (running/stopped)
|
||||
- Edit and save agent configuration
|
||||
- Start/stop the agent from the UI
|
||||
|
||||
## Dev setup
|
||||
|
||||
```bash
|
||||
cd agent/agent-ui
|
||||
npm install
|
||||
```
|
||||
|
||||
```bash
|
||||
cd agent/agent-ui
|
||||
npm run dev
|
||||
```
|
||||
|
||||
In another terminal, run the Tauri backend:
|
||||
|
||||
```bash
|
||||
cd agent/agent-ui
|
||||
cargo tauri dev
|
||||
```
|
||||
|
||||
## Notes
|
||||
- The UI uses the same config file as the CLI agent.
|
||||
- The agent core runs inside the UI process for now.
|
||||
91
agent/agent-ui/index.html
Normal file
91
agent/agent-ui/index.html
Normal file
@@ -0,0 +1,91 @@
|
||||
<!--
|
||||
Created by: Codex
|
||||
Date: 2026-01-05
|
||||
Purpose: Mesh Agent UI shell
|
||||
Refs: CLAUDE.md
|
||||
-->
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Mesh Agent</title>
|
||||
<link rel="stylesheet" href="/src/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow">Mesh</p>
|
||||
<h1>Agent Control</h1>
|
||||
<p class="subtitle">Desktop UI for the P2P data plane</p>
|
||||
</div>
|
||||
<div class="status" id="status-badge">Stopped</div>
|
||||
</header>
|
||||
|
||||
<section class="grid">
|
||||
<div class="card">
|
||||
<h2>Status</h2>
|
||||
<p class="label">State</p>
|
||||
<p class="value" id="status-text">Stopped</p>
|
||||
<p class="label">Last error</p>
|
||||
<p class="value muted" id="error-text">None</p>
|
||||
|
||||
<div class="actions">
|
||||
<button id="start-btn">Start Agent</button>
|
||||
<button class="ghost" id="stop-btn">Stop Agent</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Config</h2>
|
||||
<form id="config-form">
|
||||
<label>
|
||||
Device ID
|
||||
<input name="device_id" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
Server URL
|
||||
<input name="server_url" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
WS URL
|
||||
<input name="ws_url" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
Auth Token
|
||||
<input name="auth_token" type="password" />
|
||||
</label>
|
||||
<label>
|
||||
QUIC Port
|
||||
<input name="quic_port" type="number" min="0" />
|
||||
</label>
|
||||
<label>
|
||||
Log Level
|
||||
<input name="log_level" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
Gotify URL
|
||||
<input name="gotify_url" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
Gotify Token
|
||||
<input name="gotify_token" type="password" />
|
||||
</label>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="ghost" id="reload-btn">Reload</button>
|
||||
<button type="submit" id="save-btn">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<p>Agent runs inside this UI process. Close the app to stop it.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
1020
agent/agent-ui/package-lock.json
generated
Normal file
1020
agent/agent-ui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
agent/agent-ui/package.json
Normal file
22
agent/agent-ui/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"_created_by": "Codex",
|
||||
"_created_date": "2026-01-05",
|
||||
"_purpose": "Desktop UI for Mesh Agent (Tauri)",
|
||||
"_refs": "CLAUDE.md",
|
||||
"name": "mesh-agent-ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.4.2"
|
||||
}
|
||||
}
|
||||
21
agent/agent-ui/src-tauri/Cargo.toml
Normal file
21
agent/agent-ui/src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
# Created by: Codex
|
||||
# Date: 2026-01-05
|
||||
# Purpose: Tauri backend for Mesh Agent UI
|
||||
# Refs: CLAUDE.md
|
||||
|
||||
[package]
|
||||
name = "mesh-agent-ui"
|
||||
version = "0.1.0"
|
||||
description = "Desktop UI for Mesh Agent"
|
||||
authors = ["Mesh Team"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
mesh_agent = { path = "../..", package = "mesh-agent" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tauri = { version = "2.0", features = [] }
|
||||
tokio = { version = "1.35", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0", features = [] }
|
||||
8
agent/agent-ui/src-tauri/build.rs
Normal file
8
agent/agent-ui/src-tauri/build.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
// Created by: Codex
|
||||
// Date: 2026-01-05
|
||||
// Purpose: Build script for Tauri
|
||||
// Refs: CLAUDE.md
|
||||
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
94
agent/agent-ui/src-tauri/src/commands.rs
Normal file
94
agent/agent-ui/src-tauri/src/commands.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
// Created by: Codex
|
||||
// Date: 2026-01-05
|
||||
// Purpose: Tauri commands for Mesh Agent UI
|
||||
// Refs: CLAUDE.md
|
||||
|
||||
use serde::Serialize;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use mesh_agent::config::Config;
|
||||
use mesh_agent::runner::AgentHandle;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AgentState {
|
||||
pub running: bool,
|
||||
pub last_error: Option<String>,
|
||||
pub handle: Option<AgentHandle>,
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
pub inner: Mutex<AgentState>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AgentStatus {
|
||||
pub running: bool,
|
||||
pub last_error: Option<String>,
|
||||
}
|
||||
|
||||
impl AgentStatus {
|
||||
fn from_state(state: &AgentState) -> Self {
|
||||
Self {
|
||||
running: state.running,
|
||||
last_error: state.last_error.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_status(state: tauri::State<'_, AppState>) -> Result<AgentStatus, String> {
|
||||
let guard = state.inner.lock().await;
|
||||
Ok(AgentStatus::from_state(&guard))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_config() -> Result<Config, String> {
|
||||
Config::load().map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_config(config: Config) -> Result<(), String> {
|
||||
config.save_default_path().map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_agent(state: tauri::State<'_, AppState>) -> Result<AgentStatus, String> {
|
||||
{
|
||||
let guard = state.inner.lock().await;
|
||||
if guard.running {
|
||||
return Ok(AgentStatus::from_state(&guard));
|
||||
}
|
||||
}
|
||||
|
||||
let config = Config::load().map_err(|err| err.to_string())?;
|
||||
let handle = mesh_agent::runner::start_agent(config)
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
let mut guard = state.inner.lock().await;
|
||||
guard.handle = Some(handle);
|
||||
guard.running = true;
|
||||
guard.last_error = None;
|
||||
|
||||
Ok(AgentStatus::from_state(&guard))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn stop_agent(state: tauri::State<'_, AppState>) -> Result<AgentStatus, String> {
|
||||
let handle = {
|
||||
let mut guard = state.inner.lock().await;
|
||||
guard.running = false;
|
||||
guard.last_error = None;
|
||||
guard.handle.take()
|
||||
};
|
||||
|
||||
if let Some(handle) = handle {
|
||||
if let Err(err) = handle.stop().await {
|
||||
let mut guard = state.inner.lock().await;
|
||||
guard.last_error = Some(err.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let guard = state.inner.lock().await;
|
||||
Ok(AgentStatus::from_state(&guard))
|
||||
}
|
||||
30
agent/agent-ui/src-tauri/src/main.rs
Normal file
30
agent/agent-ui/src-tauri/src/main.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Created by: Codex
|
||||
// Date: 2026-01-05
|
||||
// Purpose: Tauri entrypoint for Mesh Agent UI
|
||||
// Refs: CLAUDE.md
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
mod commands;
|
||||
|
||||
use commands::{AppState, AgentState};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
fn main() {
|
||||
let result = tauri::Builder::default()
|
||||
.manage(AppState {
|
||||
inner: Mutex::new(AgentState::default()),
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::get_status,
|
||||
commands::get_config,
|
||||
commands::save_config,
|
||||
commands::start_agent,
|
||||
commands::stop_agent,
|
||||
])
|
||||
.run(tauri::generate_context!());
|
||||
|
||||
if let Err(err) = result {
|
||||
eprintln!("Mesh Agent UI failed to start: {}", err);
|
||||
}
|
||||
}
|
||||
25
agent/agent-ui/src-tauri/tauri.conf.json
Normal file
25
agent/agent-ui/src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Mesh Agent",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.mesh.agent",
|
||||
"build": {
|
||||
"beforeBuildCommand": "npm run build",
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"devUrl": "http://localhost:5173",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Mesh Agent",
|
||||
"width": 1080,
|
||||
"height": 720,
|
||||
"resizable": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
}
|
||||
}
|
||||
117
agent/agent-ui/src/main.ts
Normal file
117
agent/agent-ui/src/main.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
// Created by: Codex
|
||||
// Date: 2026-01-05
|
||||
// Purpose: UI logic for Mesh Agent desktop app
|
||||
// Refs: CLAUDE.md
|
||||
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
|
||||
type Config = {
|
||||
device_id: string;
|
||||
server_url: string;
|
||||
ws_url: string;
|
||||
auth_token: string | null;
|
||||
gotify_url: string | null;
|
||||
gotify_token: string | null;
|
||||
quic_port: number;
|
||||
log_level: string;
|
||||
};
|
||||
|
||||
type AgentStatus = {
|
||||
running: boolean;
|
||||
last_error: string | null;
|
||||
};
|
||||
|
||||
const statusBadge = document.querySelector<HTMLDivElement>("#status-badge");
|
||||
const statusText = document.querySelector<HTMLParagraphElement>("#status-text");
|
||||
const errorText = document.querySelector<HTMLParagraphElement>("#error-text");
|
||||
const form = document.querySelector<HTMLFormElement>("#config-form");
|
||||
const startBtn = document.querySelector<HTMLButtonElement>("#start-btn");
|
||||
const stopBtn = document.querySelector<HTMLButtonElement>("#stop-btn");
|
||||
const reloadBtn = document.querySelector<HTMLButtonElement>("#reload-btn");
|
||||
|
||||
if (!statusBadge || !statusText || !errorText || !form || !startBtn || !stopBtn || !reloadBtn) {
|
||||
throw new Error("UI elements missing");
|
||||
}
|
||||
|
||||
const toOptional = (value: FormDataEntryValue | null): string | null => {
|
||||
if (!value) return null;
|
||||
const trimmed = value.toString().trim();
|
||||
return trimmed.length ? trimmed : null;
|
||||
};
|
||||
|
||||
const getFormData = (): Config => {
|
||||
const data = new FormData(form);
|
||||
return {
|
||||
device_id: String(data.get("device_id") || ""),
|
||||
server_url: String(data.get("server_url") || ""),
|
||||
ws_url: String(data.get("ws_url") || ""),
|
||||
auth_token: toOptional(data.get("auth_token")),
|
||||
gotify_url: toOptional(data.get("gotify_url")),
|
||||
gotify_token: toOptional(data.get("gotify_token")),
|
||||
quic_port: Number(data.get("quic_port") || 0),
|
||||
log_level: String(data.get("log_level") || "info")
|
||||
};
|
||||
};
|
||||
|
||||
const setFormData = (config: Config) => {
|
||||
(form.elements.namedItem("device_id") as HTMLInputElement).value = config.device_id;
|
||||
(form.elements.namedItem("server_url") as HTMLInputElement).value = config.server_url;
|
||||
(form.elements.namedItem("ws_url") as HTMLInputElement).value = config.ws_url;
|
||||
(form.elements.namedItem("auth_token") as HTMLInputElement).value = config.auth_token || "";
|
||||
(form.elements.namedItem("gotify_url") as HTMLInputElement).value = config.gotify_url || "";
|
||||
(form.elements.namedItem("gotify_token") as HTMLInputElement).value = config.gotify_token || "";
|
||||
(form.elements.namedItem("quic_port") as HTMLInputElement).value = String(config.quic_port);
|
||||
(form.elements.namedItem("log_level") as HTMLInputElement).value = config.log_level;
|
||||
};
|
||||
|
||||
const setStatus = (status: AgentStatus) => {
|
||||
const text = status.running ? "Running" : "Stopped";
|
||||
statusText.textContent = text;
|
||||
statusBadge.textContent = text;
|
||||
statusBadge.classList.toggle("stopped", !status.running);
|
||||
errorText.textContent = status.last_error || "None";
|
||||
};
|
||||
|
||||
const loadConfig = async () => {
|
||||
const config = await invoke<Config>("get_config");
|
||||
setFormData(config);
|
||||
};
|
||||
|
||||
const saveConfig = async () => {
|
||||
const config = getFormData();
|
||||
await invoke("save_config", { config });
|
||||
};
|
||||
|
||||
const startAgent = async () => {
|
||||
const status = await invoke<AgentStatus>("start_agent");
|
||||
setStatus(status);
|
||||
};
|
||||
|
||||
const stopAgent = async () => {
|
||||
const status = await invoke<AgentStatus>("stop_agent");
|
||||
setStatus(status);
|
||||
};
|
||||
|
||||
form.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
await saveConfig();
|
||||
});
|
||||
|
||||
reloadBtn.addEventListener("click", async () => {
|
||||
await loadConfig();
|
||||
});
|
||||
|
||||
startBtn.addEventListener("click", async () => {
|
||||
await startAgent();
|
||||
});
|
||||
|
||||
stopBtn.addEventListener("click", async () => {
|
||||
await stopAgent();
|
||||
});
|
||||
|
||||
loadConfig()
|
||||
.then(() => invoke<AgentStatus>("get_status"))
|
||||
.then(setStatus)
|
||||
.catch((err) => {
|
||||
errorText.textContent = String(err);
|
||||
});
|
||||
195
agent/agent-ui/src/styles.css
Normal file
195
agent/agent-ui/src/styles.css
Normal file
@@ -0,0 +1,195 @@
|
||||
/* Created by: Codex */
|
||||
/* Date: 2026-01-05 */
|
||||
/* Purpose: UI styling for Mesh Agent desktop app */
|
||||
/* Refs: CLAUDE.md */
|
||||
|
||||
:root {
|
||||
--bg: #0f1113;
|
||||
--panel: #181c1f;
|
||||
--panel-alt: #101315;
|
||||
--ink: #f2f1ec;
|
||||
--muted: #b6b1a7;
|
||||
--accent: #ff9e3d;
|
||||
--accent-2: #53d0b3;
|
||||
--danger: #f05365;
|
||||
--border: rgba(242, 241, 236, 0.1);
|
||||
--shadow: 0 20px 50px rgba(0, 0, 0, 0.35);
|
||||
--radius: 18px;
|
||||
--font-sans: "Space Grotesk", "IBM Plex Sans", "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--font-sans);
|
||||
color: var(--ink);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(255, 158, 61, 0.18), transparent 45%),
|
||||
radial-gradient(circle at 20% 40%, rgba(83, 208, 179, 0.15), transparent 50%),
|
||||
linear-gradient(160deg, #0e0f10, #15191c 50%, #0f1214);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 48px 28px 40px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3em;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 36px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 10px 18px;
|
||||
border-radius: 999px;
|
||||
background: rgba(83, 208, 179, 0.2);
|
||||
color: var(--accent-2);
|
||||
border: 1px solid rgba(83, 208, 179, 0.4);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status.stopped {
|
||||
background: rgba(240, 83, 101, 0.15);
|
||||
color: var(--danger);
|
||||
border-color: rgba(240, 83, 101, 0.35);
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 22px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: linear-gradient(160deg, rgba(24, 28, 31, 0.9), rgba(16, 19, 21, 0.92));
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 24px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 18px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin: 12px 0 4px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.value {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.value.muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
form {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
input {
|
||||
background: var(--panel-alt);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--ink);
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: 1px solid var(--accent);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
padding: 10px 16px;
|
||||
font-weight: 600;
|
||||
color: #1b1b1b;
|
||||
background: var(--accent);
|
||||
cursor: pointer;
|
||||
transition: transform 120ms ease, box-shadow 120ms ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 12px 24px rgba(255, 158, 61, 0.22);
|
||||
}
|
||||
|
||||
button.ghost {
|
||||
background: transparent;
|
||||
color: var(--ink);
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
button.ghost:hover {
|
||||
box-shadow: none;
|
||||
border-color: rgba(242, 241, 236, 0.25);
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 26px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
#app {
|
||||
padding: 32px 18px 28px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
18
agent/agent-ui/tsconfig.json
Normal file
18
agent/agent-ui/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"_created_by": "Codex",
|
||||
"_created_date": "2026-01-05",
|
||||
"_purpose": "TypeScript config for Mesh Agent UI",
|
||||
"_refs": "CLAUDE.md",
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
14
agent/agent-ui/vite.config.ts
Normal file
14
agent/agent-ui/vite.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// Created by: Codex
|
||||
// Date: 2026-01-05
|
||||
// Purpose: Vite config for Mesh Agent UI
|
||||
// Refs: CLAUDE.md
|
||||
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
base: "./",
|
||||
server: {
|
||||
port: 5173,
|
||||
strictPort: true
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user