feat(tireless): scaffold workspace, dashboard and staged design plan
Autonomous issue-to-PR driver for Claude Code and OpenCode, structured per lair/architecture generic.md. Workspace: entities/core/data/agent library crates plus api, worker and cli binaries. Two pieces of real logic land with tests — lane routing (cc for judgement, oc for specification) and the limit governor. Constraints encoded as code rather than comments: - agents are spawned as vendor binaries; tireless never calls a provider API - ANTHROPIC_API_KEY is never set by tireless, only passed through - assert_not_anthropic refuses to start an OpenCode lane pointed at Anthropic - every run passes the governor; provider rate-limit signals win over our own accounting Deployment assets target bob.hanzalova.internal:23296 (registered in port-allocations.md), fronted by hanzalova at tireless.internal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DHhHtohxcdk1PL3tfnYJdH
This commit is contained in:
12
dashboard/index.html
Normal file
12
dashboard/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>tireless</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
3079
dashboard/package-lock.json
generated
Normal file
3079
dashboard/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
dashboard/package.json
Normal file
32
dashboard/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "tireless-dashboard",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.62.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": "^7.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.2",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "~5.7.2",
|
||||
"typescript-eslint": "^8.18.0",
|
||||
"vite": "^6.0.5"
|
||||
}
|
||||
}
|
||||
28
dashboard/src/App.tsx
Normal file
28
dashboard/src/App.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { NavLink, Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import Lanes from './routes/Lanes';
|
||||
import Jobs from './routes/Jobs';
|
||||
import Repos from './routes/Repos';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="app">
|
||||
<header>
|
||||
<h1>tireless</h1>
|
||||
<nav>
|
||||
<NavLink to="/lanes">lanes</NavLink>
|
||||
<NavLink to="/jobs">jobs</NavLink>
|
||||
<NavLink to="/repos">repos</NavLink>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/lanes" replace />} />
|
||||
<Route path="/lanes" element={<Lanes />} />
|
||||
<Route path="/jobs" element={<Jobs />} />
|
||||
<Route path="/repos" element={<Repos />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
37
dashboard/src/api/client.ts
Normal file
37
dashboard/src/api/client.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Client for tireless-api.
|
||||
*
|
||||
* The base URL is stamped at build time by the deploy workflow. In dev, Vite
|
||||
* proxies /v1 to a local API (see vite.config.ts), so the default empty base
|
||||
* resolves to a same-origin request in both cases.
|
||||
*/
|
||||
const BASE = import.meta.env.VITE_API_BASE_URL ?? '';
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
readonly status: number,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(`${BASE}/v1${path}`, {
|
||||
...init,
|
||||
headers: { 'content-type': 'application/json', ...init?.headers },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new ApiError(response.status, await response.text());
|
||||
}
|
||||
return response.status === 204 ? (undefined as T) : await response.json();
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(path: string) => request<T>(path),
|
||||
post: <T>(path: string, body: unknown) =>
|
||||
request<T>(path, { method: 'POST', body: JSON.stringify(body) }),
|
||||
patch: <T>(path: string, body: unknown) =>
|
||||
request<T>(path, { method: 'PATCH', body: JSON.stringify(body) }),
|
||||
delete: <T>(path: string) => request<T>(path, { method: 'DELETE' }),
|
||||
};
|
||||
6
dashboard/src/api/generated/AgentKind.ts
Normal file
6
dashboard/src/api/generated/AgentKind.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Which coding agent executed a run.
|
||||
*/
|
||||
export type AgentKind = "claude_code" | "opencode";
|
||||
27
dashboard/src/api/generated/AgentRun.ts
Normal file
27
dashboard/src/api/generated/AgentRun.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AgentKind } from "./AgentKind";
|
||||
import type { BillingMode } from "./BillingMode";
|
||||
import type { PullRequestRef } from "./PullRequestRef";
|
||||
import type { RunOutcome } from "./RunOutcome";
|
||||
|
||||
/**
|
||||
* One invocation of one agent against one job.
|
||||
*
|
||||
* A job may have several runs: a retry after a rate limit, or a follow-up turn
|
||||
* resuming the same agent session.
|
||||
*/
|
||||
export type AgentRun = { id: string, job_id: string, agent: AgentKind,
|
||||
/**
|
||||
* Model actually used, as reported by the agent.
|
||||
*/
|
||||
model: string | null, billing: BillingMode,
|
||||
/**
|
||||
* The agent's own session identifier, so a follow-up turn can resume rather
|
||||
* than restart. Claude Code reports this in its stream; tireless passes it
|
||||
* back via `--resume`.
|
||||
*/
|
||||
session_id: string | null, outcome: RunOutcome | null,
|
||||
/**
|
||||
* Pull request opened by this run, if any.
|
||||
*/
|
||||
pull_request: PullRequestRef | null, started_at: string, finished_at: string | null, };
|
||||
11
dashboard/src/api/generated/BillingMode.ts
Normal file
11
dashboard/src/api/generated/BillingMode.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Where a Claude Code run's tokens were billed.
|
||||
*
|
||||
* Read from the `apiKeySource` field of Claude Code's `system`/`init` stream
|
||||
* event — the same signal vibe-kanban surfaces in
|
||||
* `crates/executors/src/executors/claude.rs:911`. tireless records it per run
|
||||
* so the dashboard can show, honestly, which runs drew on the subscription.
|
||||
*/
|
||||
export type BillingMode = "subscription" | "api_key" | "unknown";
|
||||
6
dashboard/src/api/generated/Forge.ts
Normal file
6
dashboard/src/api/generated/Forge.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* A source forge tireless can poll and push to.
|
||||
*/
|
||||
export type Forge = "gitea" | "git_hub";
|
||||
7
dashboard/src/api/generated/IssueRef.ts
Normal file
7
dashboard/src/api/generated/IssueRef.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Forge } from "./Forge";
|
||||
|
||||
/**
|
||||
* Stable coordinates for an issue on some forge.
|
||||
*/
|
||||
export type IssueRef = { forge: Forge, owner: string, repo: string, number: bigint, };
|
||||
23
dashboard/src/api/generated/Job.ts
Normal file
23
dashboard/src/api/generated/Job.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { IssueRef } from "./IssueRef";
|
||||
import type { JobKind } from "./JobKind";
|
||||
import type { JobState } from "./JobState";
|
||||
|
||||
/**
|
||||
* One unit of work against one issue.
|
||||
*/
|
||||
export type Job = { id: string, issue: IssueRef, kind: JobKind, state: JobState,
|
||||
/**
|
||||
* Worker identity holding the claim, if any.
|
||||
*/
|
||||
claimed_by: string | null,
|
||||
/**
|
||||
* Claims expire so a crashed worker's jobs return to the pool.
|
||||
*/
|
||||
claim_expires_at: string | null,
|
||||
/**
|
||||
* Set when this job's issue was itself produced by a Plan job. Presence of
|
||||
* a parent is the primary signal to route implementation to OpenCode —
|
||||
* the plan is the spec (see `doc/plan/design.md` §3).
|
||||
*/
|
||||
parent_job_id: string | null, attempts: number, last_error: string | null, created_at: string, updated_at: string, };
|
||||
6
dashboard/src/api/generated/JobKind.ts
Normal file
6
dashboard/src/api/generated/JobKind.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* What tireless was asked to do with an issue.
|
||||
*/
|
||||
export type JobKind = "plan" | "implement";
|
||||
10
dashboard/src/api/generated/JobState.ts
Normal file
10
dashboard/src/api/generated/JobState.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Job lifecycle.
|
||||
*
|
||||
* A job is the unit of work-claiming. Claiming is a Postgres row transition
|
||||
* under `FOR UPDATE SKIP LOCKED` (`architecture/generic.md` §3), not a forge
|
||||
* label — labels cannot be claimed atomically.
|
||||
*/
|
||||
export type JobState = "pending" | "claimed" | "running" | "delivered" | "blocked" | "failed" | "abandoned";
|
||||
44
dashboard/src/api/generated/LabelProtocol.ts
Normal file
44
dashboard/src/api/generated/LabelProtocol.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* The label vocabulary tireless reads from and writes to a forge.
|
||||
*
|
||||
* Labels are the *human-facing* interface: they are how an operator opts an
|
||||
* issue in, and how tireless reports back. They are **not** the authority on
|
||||
* state — Postgres is (see `doc/plan/design.md` §4). Labels are a best-effort
|
||||
* mirror, reconciled on every poll.
|
||||
*/
|
||||
export type LabelProtocol = {
|
||||
/**
|
||||
* Opt-in marker. Without it tireless never touches an issue, regardless of
|
||||
* any other label present. Default: `tireless`.
|
||||
*/
|
||||
opt_in: string,
|
||||
/**
|
||||
* Requests decomposition into an epic plus child issues. Default: `tireless/plan`.
|
||||
*/
|
||||
mode_plan: string,
|
||||
/**
|
||||
* Requests an implementation and a pull request. Default: `tireless/implement`.
|
||||
*/
|
||||
mode_implement: string,
|
||||
/**
|
||||
* Forces the Claude Code lane. Default: `tireless/agent:cc`.
|
||||
*/
|
||||
force_cc: string,
|
||||
/**
|
||||
* Forces the OpenCode lane. Default: `tireless/agent:oc`.
|
||||
*/
|
||||
force_oc: string,
|
||||
/**
|
||||
* Written by tireless while a job holds the issue. Default: `tireless/claimed`.
|
||||
*/
|
||||
state_claimed: string,
|
||||
/**
|
||||
* Written by tireless when it needs a human. Default: `tireless/blocked`.
|
||||
*/
|
||||
state_blocked: string,
|
||||
/**
|
||||
* Written by tireless when it has delivered. Default: `tireless/done`.
|
||||
*/
|
||||
state_done: string, };
|
||||
23
dashboard/src/api/generated/PollSchedule.ts
Normal file
23
dashboard/src/api/generated/PollSchedule.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* How often a repo's issues are polled.
|
||||
*
|
||||
* The floor exists to keep tireless a well-behaved API client: a repo that is
|
||||
* quiet for days does not need to be asked every thirty seconds. See
|
||||
* `doc/plan/design.md` §5.
|
||||
*/
|
||||
export type PollSchedule = {
|
||||
/**
|
||||
* Seconds between polls. Clamped to `>= min_interval_seconds` by core.
|
||||
*/
|
||||
interval_seconds: number,
|
||||
/**
|
||||
* Optional quiet window (local time, `HH:MM`), during which no poll runs
|
||||
* and no job is claimed. Both ends required if either is set.
|
||||
*/
|
||||
quiet_from: string | null, quiet_until: string | null,
|
||||
/**
|
||||
* When false the repo stays configured but is skipped entirely.
|
||||
*/
|
||||
enabled: boolean, };
|
||||
7
dashboard/src/api/generated/PullRequestRef.ts
Normal file
7
dashboard/src/api/generated/PullRequestRef.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Forge } from "./Forge";
|
||||
|
||||
/**
|
||||
* Coordinates for a pull request tireless opened.
|
||||
*/
|
||||
export type PullRequestRef = { forge: Forge, owner: string, repo: string, number: bigint, head_branch: string, url: string, };
|
||||
6
dashboard/src/api/generated/RunOutcome.ts
Normal file
6
dashboard/src/api/generated/RunOutcome.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* How a run ended.
|
||||
*/
|
||||
export type RunOutcome = "succeeded" | "failed" | "rate_limited" | "budget_exhausted" | "timed_out" | "cancelled";
|
||||
20
dashboard/src/api/generated/TrackedRepo.ts
Normal file
20
dashboard/src/api/generated/TrackedRepo.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Forge } from "./Forge";
|
||||
import type { PollSchedule } from "./PollSchedule";
|
||||
|
||||
/**
|
||||
* A repo tireless watches for labelled issues.
|
||||
*/
|
||||
export type TrackedRepo = { id: string, forge: Forge, owner: string, repo: string,
|
||||
/**
|
||||
* Clone URL used for the mirror cache. SSH for Gitea, HTTPS for GitHub.
|
||||
*/
|
||||
clone_url: string,
|
||||
/**
|
||||
* Branch PRs target. Usually `main`; tireless never pushes to it directly.
|
||||
*/
|
||||
default_branch: string, schedule: PollSchedule,
|
||||
/**
|
||||
* Set from the forge's `ETag` so repeat polls are conditional requests.
|
||||
*/
|
||||
last_etag: string | null, last_polled_at: string | null, created_at: string, };
|
||||
51
dashboard/src/index.css
Normal file
51
dashboard/src/index.css
Normal file
@@ -0,0 +1,51 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
font-family: ui-sans-serif, system-ui, sans-serif;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.app {
|
||||
max-width: 72rem;
|
||||
margin: 0 auto;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.app > header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 1.5rem;
|
||||
border-bottom: 1px solid color-mix(in srgb, currentColor 20%, transparent);
|
||||
padding-bottom: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.app > header h1 {
|
||||
font-size: 1.25rem;
|
||||
margin: 0;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
nav a.active {
|
||||
opacity: 1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
opacity: 0.6;
|
||||
font-style: italic;
|
||||
}
|
||||
28
dashboard/src/main.tsx
Normal file
28
dashboard/src/main.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
|
||||
// Server state lives in React Query; the dashboard is a rendering and
|
||||
// interaction layer only (architecture/generic.md §4).
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 10_000,
|
||||
refetchOnWindowFocus: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
12
dashboard/src/routes/Jobs.tsx
Normal file
12
dashboard/src/routes/Jobs.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Job list and detail: what tireless has claimed, what it is running, what it
|
||||
* delivered, and what is blocked waiting on a human.
|
||||
*/
|
||||
export default function Jobs() {
|
||||
return (
|
||||
<section>
|
||||
<h2>jobs</h2>
|
||||
<p className="placeholder">Job listing arrives with stage 2.</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
18
dashboard/src/routes/Lanes.tsx
Normal file
18
dashboard/src/routes/Lanes.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Lane status: the page that answers "is tireless about to spend my
|
||||
* subscription, and how much is left?".
|
||||
*
|
||||
* Shows, per lane: in-flight runs, window budget consumed, the most recent
|
||||
* provider limit signal, and whether the circuit breaker has tripped. Stage 6
|
||||
* adds the pause/resume controls; until then this is read-only.
|
||||
*/
|
||||
export default function Lanes() {
|
||||
return (
|
||||
<section>
|
||||
<h2>lanes</h2>
|
||||
<p className="placeholder">
|
||||
Lane telemetry arrives with stage 3 (Claude Code) and stage 5 (OpenCode).
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
15
dashboard/src/routes/Repos.tsx
Normal file
15
dashboard/src/routes/Repos.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Polled repo management: add and remove Gitea/GitHub repos, and edit each
|
||||
* one's poll schedule and quiet window.
|
||||
*
|
||||
* This is the primary reason the dashboard exists — the poller's schedule is
|
||||
* meant to be changed without a redeploy.
|
||||
*/
|
||||
export default function Repos() {
|
||||
return (
|
||||
<section>
|
||||
<h2>repos</h2>
|
||||
<p className="placeholder">Repo CRUD arrives with stage 1.</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
10
dashboard/src/vite-env.d.ts
vendored
Normal file
10
dashboard/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
/** Stamped at build time by the deploy workflow. */
|
||||
readonly VITE_API_BASE_URL?: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
23
dashboard/tsconfig.json
Normal file
23
dashboard/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
1
dashboard/tsconfig.tsbuildinfo
Normal file
1
dashboard/tsconfig.tsbuildinfo
Normal file
@@ -0,0 +1 @@
|
||||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/api/generated/AgentKind.ts","./src/api/generated/AgentRun.ts","./src/api/generated/BillingMode.ts","./src/api/generated/Forge.ts","./src/api/generated/IssueRef.ts","./src/api/generated/Job.ts","./src/api/generated/JobKind.ts","./src/api/generated/JobState.ts","./src/api/generated/LabelProtocol.ts","./src/api/generated/PollSchedule.ts","./src/api/generated/PullRequestRef.ts","./src/api/generated/RunOutcome.ts","./src/api/generated/TrackedRepo.ts","./src/routes/Jobs.tsx","./src/routes/Lanes.tsx","./src/routes/Repos.tsx"],"version":"5.7.3"}
|
||||
23
dashboard/vite.config.ts
Normal file
23
dashboard/vite.config.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react-swc';
|
||||
|
||||
// Build output is static and served by nginx from /var/www/tireless — no Node
|
||||
// in production (architecture/generic.md §4). The API base URL is stamped at
|
||||
// build time from VITE_API_BASE_URL by the deploy workflow.
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
sourcemap: true,
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
// Dev-only: point at a local tireless-api on its registered port.
|
||||
'/v1': {
|
||||
target: 'http://127.0.0.1:23296',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user