@castlemilk/omega 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +24 -12
- package/dist/server.js +418 -0
- package/dist/web/App.d.ts +3 -0
- package/dist/web/App.d.ts.map +1 -0
- package/dist/web/App.js +39 -0
- package/dist/web/App.js.map +1 -0
- package/dist/web/assets/index-BD8kY1aj.css +1 -0
- package/dist/web/assets/index-Dkm6k3Ip.js +40 -0
- package/dist/web/components/ProjectSidebar.d.ts +18 -0
- package/dist/web/components/ProjectSidebar.d.ts.map +1 -0
- package/dist/web/components/ProjectSidebar.js +16 -0
- package/dist/web/components/ProjectSidebar.js.map +1 -0
- package/dist/web/components/ProviderSettings.d.ts +16 -0
- package/dist/web/components/ProviderSettings.d.ts.map +1 -0
- package/dist/web/components/ProviderSettings.js +41 -0
- package/dist/web/components/ProviderSettings.js.map +1 -0
- package/dist/web/components/RouterPanel.d.ts +2 -0
- package/dist/web/components/RouterPanel.d.ts.map +1 -0
- package/dist/web/components/RouterPanel.js +31 -0
- package/dist/web/components/RouterPanel.js.map +1 -0
- package/dist/web/components/TaskBoard.d.ts +20 -0
- package/dist/web/components/TaskBoard.d.ts.map +1 -0
- package/dist/web/components/TaskBoard.js +60 -0
- package/dist/web/components/TaskBoard.js.map +1 -0
- package/dist/web/dist/assets/index-BD8kY1aj.css +1 -0
- package/dist/web/dist/assets/index-Dkm6k3Ip.js +40 -0
- package/dist/web/dist/index.html +13 -0
- package/dist/web/index.html +13 -0
- package/dist/web/lib/api.d.ts +30 -0
- package/dist/web/lib/api.d.ts.map +1 -0
- package/dist/web/lib/api.js +27 -0
- package/dist/web/lib/api.js.map +1 -0
- package/dist/web/main.d.ts +2 -0
- package/dist/web/main.d.ts.map +1 -0
- package/dist/web/main.js +7 -0
- package/dist/web/main.js.map +1 -0
- package/package.json +10 -4
package/dist/cli.js
CHANGED
|
@@ -12,8 +12,8 @@ function getApiUrl() {
|
|
|
12
12
|
const opts = program.opts();
|
|
13
13
|
return opts.api || "http://localhost:4000";
|
|
14
14
|
}
|
|
15
|
-
async function apiFetch(
|
|
16
|
-
const url = `${getApiUrl()}${
|
|
15
|
+
async function apiFetch(path3, init) {
|
|
16
|
+
const url = `${getApiUrl()}${path3}`;
|
|
17
17
|
const res = await fetch(url, init);
|
|
18
18
|
const data = await res.json().catch(() => ({}));
|
|
19
19
|
if (!res.ok) {
|
|
@@ -76,9 +76,21 @@ taskCmd.command("run").description("Run a task through the router").argument("<i
|
|
|
76
76
|
// ../../apps/cli/src/commands/ui.ts
|
|
77
77
|
import { Command as Command3 } from "commander";
|
|
78
78
|
import { spawn } from "child_process";
|
|
79
|
+
import { existsSync } from "fs";
|
|
80
|
+
import path from "path";
|
|
81
|
+
import { fileURLToPath } from "url";
|
|
79
82
|
import open from "open";
|
|
83
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
84
|
+
var __dirname = path.dirname(__filename);
|
|
85
|
+
function startBundledServer() {
|
|
86
|
+
const serverPath = path.resolve(__dirname, "server.js");
|
|
87
|
+
if (existsSync(serverPath)) {
|
|
88
|
+
return spawn(process.execPath, [serverPath], { stdio: "inherit" });
|
|
89
|
+
}
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
80
92
|
var uiCmd = new Command3("ui").description("Open the harness web UI").action(async () => {
|
|
81
|
-
const server = spawn("pnpm", ["--filter", "@omega/server", "dev"], {
|
|
93
|
+
const server = startBundledServer() ?? spawn("pnpm", ["--filter", "@omega/server", "dev"], {
|
|
82
94
|
stdio: "inherit",
|
|
83
95
|
shell: true
|
|
84
96
|
});
|
|
@@ -92,8 +104,8 @@ var uiCmd = new Command3("ui").description("Open the harness web UI").action(asy
|
|
|
92
104
|
|
|
93
105
|
// ../../apps/cli/src/commands/skill.ts
|
|
94
106
|
import { Command as Command4 } from "commander";
|
|
95
|
-
import
|
|
96
|
-
import { fileURLToPath } from "url";
|
|
107
|
+
import path2 from "path";
|
|
108
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
97
109
|
|
|
98
110
|
// ../skills/src/parse.ts
|
|
99
111
|
import matter from "gray-matter";
|
|
@@ -194,22 +206,22 @@ async function registerSkill(sourcePath, outputDir) {
|
|
|
194
206
|
// ../../apps/cli/src/commands/skill.ts
|
|
195
207
|
function getOutputDir() {
|
|
196
208
|
if (process.env.OMEGA_HARNESS_ROOT) {
|
|
197
|
-
return
|
|
209
|
+
return path2.join(path2.resolve(process.env.OMEGA_HARNESS_ROOT), "packages/skills/src");
|
|
198
210
|
}
|
|
199
211
|
try {
|
|
200
|
-
const
|
|
201
|
-
const
|
|
202
|
-
return
|
|
212
|
+
const __filename2 = fileURLToPath2(import.meta.url);
|
|
213
|
+
const __dirname2 = path2.dirname(__filename2);
|
|
214
|
+
return path2.join(path2.resolve(__dirname2, "../../../.."), "packages/skills/src");
|
|
203
215
|
} catch {
|
|
204
|
-
return
|
|
216
|
+
return path2.join(process.cwd(), "harness-skills");
|
|
205
217
|
}
|
|
206
218
|
}
|
|
207
219
|
var skillCmd = new Command4("skill").description("Manage skill artifacts");
|
|
208
220
|
skillCmd.command("generate").description("Generate a harness adapter from a SKILL.md file").argument("<source>", "path to SKILL.md").action(async (source) => {
|
|
209
221
|
const outputDir = getOutputDir();
|
|
210
|
-
const manifest = await registerSkill(
|
|
222
|
+
const manifest = await registerSkill(path2.resolve(source), outputDir);
|
|
211
223
|
console.log(`Generated adapter for skill: ${manifest.name}`);
|
|
212
|
-
console.log(`Output directory: ${
|
|
224
|
+
console.log(`Output directory: ${path2.join(outputDir, "generated")}`);
|
|
213
225
|
});
|
|
214
226
|
|
|
215
227
|
// ../../apps/cli/src/index.ts
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/server.ts
|
|
4
|
+
import express from "express";
|
|
5
|
+
import cors from "cors";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
// ../providers/src/openai.ts
|
|
10
|
+
var DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
11
|
+
var OpenAIProvider = class {
|
|
12
|
+
config;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
get baseUrl() {
|
|
17
|
+
return (this.config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
18
|
+
}
|
|
19
|
+
async listModels() {
|
|
20
|
+
const res = await fetch(`${this.baseUrl}/models`, {
|
|
21
|
+
headers: this.authHeaders()
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok) return [this.config.defaultModel];
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
return data.data?.map((m) => m.id) ?? [this.config.defaultModel];
|
|
26
|
+
}
|
|
27
|
+
async send(prompt, opts) {
|
|
28
|
+
const res = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
...this.authHeaders()
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
model: opts?.model ?? this.config.defaultModel,
|
|
36
|
+
messages: [
|
|
37
|
+
...opts?.system ? [{ role: "system", content: opts.system }] : [],
|
|
38
|
+
{ role: "user", content: prompt }
|
|
39
|
+
],
|
|
40
|
+
temperature: opts?.temperature
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
throw new Error(`OpenAI request failed: ${res.status} ${res.statusText}`);
|
|
45
|
+
}
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
return data.choices?.[0]?.message?.content ?? "";
|
|
48
|
+
}
|
|
49
|
+
authHeaders() {
|
|
50
|
+
return this.config.apiKey ? { Authorization: `Bearer ${this.config.apiKey}` } : {};
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ../providers/src/anthropic.ts
|
|
55
|
+
var DEFAULT_BASE_URL2 = "https://api.anthropic.com/v1";
|
|
56
|
+
var AnthropicProvider = class {
|
|
57
|
+
config;
|
|
58
|
+
constructor(config) {
|
|
59
|
+
this.config = config;
|
|
60
|
+
}
|
|
61
|
+
get baseUrl() {
|
|
62
|
+
return (this.config.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/$/, "");
|
|
63
|
+
}
|
|
64
|
+
async listModels() {
|
|
65
|
+
const res = await fetch(`${this.baseUrl}/models`, {
|
|
66
|
+
headers: this.headers()
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok) return [this.config.defaultModel];
|
|
69
|
+
const data = await res.json();
|
|
70
|
+
return data.data?.map((m) => m.id) ?? [this.config.defaultModel];
|
|
71
|
+
}
|
|
72
|
+
async send(prompt, opts) {
|
|
73
|
+
const body = {
|
|
74
|
+
model: opts?.model ?? this.config.defaultModel,
|
|
75
|
+
messages: [{ role: "user", content: prompt }],
|
|
76
|
+
max_tokens: 1024
|
|
77
|
+
};
|
|
78
|
+
if (opts?.system) body.system = opts.system;
|
|
79
|
+
if (opts?.temperature !== void 0) body.temperature = opts.temperature;
|
|
80
|
+
const res = await fetch(`${this.baseUrl}/messages`, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
...this.headers()
|
|
85
|
+
},
|
|
86
|
+
body: JSON.stringify(body)
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
throw new Error(`Anthropic request failed: ${res.status} ${res.statusText}`);
|
|
90
|
+
}
|
|
91
|
+
const data = await res.json();
|
|
92
|
+
return data.content?.find((c) => c.type === "text")?.text ?? "";
|
|
93
|
+
}
|
|
94
|
+
headers() {
|
|
95
|
+
return {
|
|
96
|
+
"x-api-key": this.config.apiKey ?? "",
|
|
97
|
+
"anthropic-version": "2023-06-01"
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// ../providers/src/ollama.ts
|
|
103
|
+
var DEFAULT_BASE_URL3 = "http://localhost:11434";
|
|
104
|
+
var OllamaProvider = class {
|
|
105
|
+
config;
|
|
106
|
+
constructor(config) {
|
|
107
|
+
this.config = config;
|
|
108
|
+
}
|
|
109
|
+
get baseUrl() {
|
|
110
|
+
return (this.config.baseUrl ?? DEFAULT_BASE_URL3).replace(/\/$/, "");
|
|
111
|
+
}
|
|
112
|
+
async listModels() {
|
|
113
|
+
const res = await fetch(`${this.baseUrl}/api/tags`);
|
|
114
|
+
if (!res.ok) return [this.config.defaultModel];
|
|
115
|
+
const data = await res.json();
|
|
116
|
+
return data.models?.map((m) => m.name) ?? [this.config.defaultModel];
|
|
117
|
+
}
|
|
118
|
+
async send(prompt, opts) {
|
|
119
|
+
const res = await fetch(`${this.baseUrl}/api/chat`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: { "Content-Type": "application/json" },
|
|
122
|
+
body: JSON.stringify({
|
|
123
|
+
model: opts?.model ?? this.config.defaultModel,
|
|
124
|
+
messages: [
|
|
125
|
+
...opts?.system ? [{ role: "system", content: opts.system }] : [],
|
|
126
|
+
{ role: "user", content: prompt }
|
|
127
|
+
],
|
|
128
|
+
stream: false,
|
|
129
|
+
options: opts?.temperature !== void 0 ? { temperature: opts.temperature } : void 0
|
|
130
|
+
})
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok) {
|
|
133
|
+
throw new Error(`Ollama request failed: ${res.status} ${res.statusText}`);
|
|
134
|
+
}
|
|
135
|
+
const data = await res.json();
|
|
136
|
+
return data.message?.content ?? "";
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// ../providers/src/gemini.ts
|
|
141
|
+
var DEFAULT_BASE_URL4 = "https://generativelanguage.googleapis.com/v1beta";
|
|
142
|
+
var GeminiProvider = class {
|
|
143
|
+
config;
|
|
144
|
+
constructor(config) {
|
|
145
|
+
this.config = config;
|
|
146
|
+
}
|
|
147
|
+
get baseUrl() {
|
|
148
|
+
return (this.config.baseUrl ?? DEFAULT_BASE_URL4).replace(/\/$/, "");
|
|
149
|
+
}
|
|
150
|
+
async listModels() {
|
|
151
|
+
const url = new URL(`${this.baseUrl}/models`);
|
|
152
|
+
if (this.config.apiKey) url.searchParams.set("key", this.config.apiKey);
|
|
153
|
+
const res = await fetch(url.toString());
|
|
154
|
+
if (!res.ok) return [this.config.defaultModel];
|
|
155
|
+
const data = await res.json();
|
|
156
|
+
return data.models?.map((m) => m.name.replace(/^models\//, "")) ?? [this.config.defaultModel];
|
|
157
|
+
}
|
|
158
|
+
async send(prompt, opts) {
|
|
159
|
+
const model = opts?.model ?? this.config.defaultModel;
|
|
160
|
+
const url = new URL(`${this.baseUrl}/models/${model}:generateContent`);
|
|
161
|
+
if (this.config.apiKey) url.searchParams.set("key", this.config.apiKey);
|
|
162
|
+
const body = {
|
|
163
|
+
contents: [{ role: "user", parts: [{ text: prompt }] }]
|
|
164
|
+
};
|
|
165
|
+
if (opts?.system) {
|
|
166
|
+
body.systemInstruction = { role: "user", parts: [{ text: opts.system }] };
|
|
167
|
+
}
|
|
168
|
+
if (opts?.temperature !== void 0) {
|
|
169
|
+
body.generationConfig = { temperature: opts.temperature };
|
|
170
|
+
}
|
|
171
|
+
const res = await fetch(url.toString(), {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers: { "Content-Type": "application/json" },
|
|
174
|
+
body: JSON.stringify(body)
|
|
175
|
+
});
|
|
176
|
+
if (!res.ok) {
|
|
177
|
+
throw new Error(`Gemini request failed: ${res.status} ${res.statusText}`);
|
|
178
|
+
}
|
|
179
|
+
const data = await res.json();
|
|
180
|
+
return data.candidates?.[0]?.content?.parts?.map((p) => p.text).join("") ?? "";
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// ../providers/src/generic.ts
|
|
185
|
+
var GenericProvider = class extends OpenAIProvider {
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// ../providers/src/index.ts
|
|
189
|
+
function createProvider(config) {
|
|
190
|
+
switch (config.kind) {
|
|
191
|
+
case "openai":
|
|
192
|
+
return new OpenAIProvider(config);
|
|
193
|
+
case "anthropic":
|
|
194
|
+
return new AnthropicProvider(config);
|
|
195
|
+
case "ollama":
|
|
196
|
+
return new OllamaProvider(config);
|
|
197
|
+
case "gemini":
|
|
198
|
+
return new GeminiProvider(config);
|
|
199
|
+
case "generic":
|
|
200
|
+
return new GenericProvider(config);
|
|
201
|
+
default:
|
|
202
|
+
throw new Error(`Unknown provider kind: ${config.kind}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ../router/src/rules.ts
|
|
207
|
+
var levelRank = {
|
|
208
|
+
simple: 1,
|
|
209
|
+
medium: 2,
|
|
210
|
+
complex: 3
|
|
211
|
+
};
|
|
212
|
+
var capRank = {
|
|
213
|
+
fast: 1,
|
|
214
|
+
capable: 2,
|
|
215
|
+
advanced: 3
|
|
216
|
+
};
|
|
217
|
+
function rankCapabilityForTask(cap, task) {
|
|
218
|
+
let score = capRank[cap.level];
|
|
219
|
+
if (capRank[cap.level] < levelRank[task.complexity]) {
|
|
220
|
+
score -= 10;
|
|
221
|
+
}
|
|
222
|
+
if (task.tags.includes("vision") && !cap.supportsVision) {
|
|
223
|
+
score -= 10;
|
|
224
|
+
}
|
|
225
|
+
if (task.tags.includes("tools") && !cap.supportsTools) {
|
|
226
|
+
score -= 10;
|
|
227
|
+
}
|
|
228
|
+
return score;
|
|
229
|
+
}
|
|
230
|
+
function selectProvider(configs, rules2, task) {
|
|
231
|
+
const enabled = configs.filter((cfg) => cfg.enabled);
|
|
232
|
+
if (enabled.length === 0) {
|
|
233
|
+
return void 0;
|
|
234
|
+
}
|
|
235
|
+
const matchingRules = rules2.filter((rule) => {
|
|
236
|
+
if (rule.when.complexity && rule.when.complexity !== task.complexity) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
if (rule.when.tags && !rule.when.tags.every((tag) => task.tags.includes(tag))) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
}).sort((a, b) => b.priority - a.priority);
|
|
244
|
+
for (const rule of matchingRules) {
|
|
245
|
+
const provider = rule.then.provider ? enabled.find((cfg) => cfg.id === rule.then.provider || cfg.name === rule.then.provider) : enabled[0];
|
|
246
|
+
if (provider) {
|
|
247
|
+
return { provider, model: rule.then.model || provider.defaultModel };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
let best;
|
|
251
|
+
for (const provider of enabled) {
|
|
252
|
+
for (const cap of provider.capabilities) {
|
|
253
|
+
const score = rankCapabilityForTask(cap, task);
|
|
254
|
+
if (!best || score > best.score) {
|
|
255
|
+
best = { provider, model: provider.defaultModel, score };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return best ? { provider: best.provider, model: best.model } : void 0;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/server.ts
|
|
263
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
264
|
+
var __dirname = path.dirname(__filename);
|
|
265
|
+
var app = express();
|
|
266
|
+
app.use(cors());
|
|
267
|
+
app.use(express.json());
|
|
268
|
+
var projects = [];
|
|
269
|
+
var tasks = [];
|
|
270
|
+
var providerConfigs = [
|
|
271
|
+
{
|
|
272
|
+
id: "default-ollama",
|
|
273
|
+
name: "ollama-local",
|
|
274
|
+
kind: "ollama",
|
|
275
|
+
baseUrl: "http://localhost:11434",
|
|
276
|
+
defaultModel: "llama3",
|
|
277
|
+
capabilities: [{ name: "llama3", level: "capable" }],
|
|
278
|
+
enabled: true
|
|
279
|
+
}
|
|
280
|
+
];
|
|
281
|
+
var rules = [];
|
|
282
|
+
function newId() {
|
|
283
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
284
|
+
}
|
|
285
|
+
function now() {
|
|
286
|
+
return /* @__PURE__ */ new Date();
|
|
287
|
+
}
|
|
288
|
+
app.get("/projects", (_req, res) => {
|
|
289
|
+
res.json(
|
|
290
|
+
projects.map((p) => ({
|
|
291
|
+
...p,
|
|
292
|
+
_count: { tasks: tasks.filter((t) => t.projectId === p.id).length }
|
|
293
|
+
}))
|
|
294
|
+
);
|
|
295
|
+
});
|
|
296
|
+
app.post("/projects", (req, res) => {
|
|
297
|
+
const body = req.body;
|
|
298
|
+
const project = {
|
|
299
|
+
id: newId(),
|
|
300
|
+
name: body.name,
|
|
301
|
+
path: body.path,
|
|
302
|
+
repoUrl: body.repoUrl,
|
|
303
|
+
description: body.description,
|
|
304
|
+
env: body.env,
|
|
305
|
+
createdAt: now()
|
|
306
|
+
};
|
|
307
|
+
projects.push(project);
|
|
308
|
+
res.status(201).json(project);
|
|
309
|
+
});
|
|
310
|
+
app.delete("/projects/:id", (req, res) => {
|
|
311
|
+
const idx = projects.findIndex((p) => p.id === req.params.id);
|
|
312
|
+
if (idx >= 0) projects.splice(idx, 1);
|
|
313
|
+
res.status(204).send();
|
|
314
|
+
});
|
|
315
|
+
app.get("/tasks", (req, res) => {
|
|
316
|
+
const projectId = req.query.projectId;
|
|
317
|
+
res.json(tasks.filter((t) => projectId ? t.projectId === projectId : true));
|
|
318
|
+
});
|
|
319
|
+
app.post("/tasks", (req, res) => {
|
|
320
|
+
const body = req.body;
|
|
321
|
+
const task = {
|
|
322
|
+
id: newId(),
|
|
323
|
+
projectId: body.projectId,
|
|
324
|
+
title: body.title,
|
|
325
|
+
description: body.description,
|
|
326
|
+
status: "todo",
|
|
327
|
+
complexity: body.complexity || "simple",
|
|
328
|
+
tags: body.tags || [],
|
|
329
|
+
createdAt: now(),
|
|
330
|
+
updatedAt: now()
|
|
331
|
+
};
|
|
332
|
+
tasks.push(task);
|
|
333
|
+
res.status(201).json(task);
|
|
334
|
+
});
|
|
335
|
+
app.patch("/tasks/:id", (req, res) => {
|
|
336
|
+
const task = tasks.find((t) => t.id === req.params.id);
|
|
337
|
+
if (!task) return res.status(404).json({ error: "Task not found" });
|
|
338
|
+
Object.assign(task, req.body, { updatedAt: now() });
|
|
339
|
+
res.json(task);
|
|
340
|
+
});
|
|
341
|
+
app.post("/tasks/:id/run", async (req, res) => {
|
|
342
|
+
const task = tasks.find((t) => t.id === req.params.id);
|
|
343
|
+
if (!task) return res.status(404).json({ error: "Task not found" });
|
|
344
|
+
task.status = "in_progress";
|
|
345
|
+
task.error = void 0;
|
|
346
|
+
task.result = void 0;
|
|
347
|
+
const selection = selectProvider(providerConfigs, rules, task);
|
|
348
|
+
if (!selection) {
|
|
349
|
+
task.status = "failed";
|
|
350
|
+
task.error = "No provider available for this task";
|
|
351
|
+
return res.json(task);
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
const provider = createProvider(selection.provider);
|
|
355
|
+
const prompt = [task.title, task.description].filter(Boolean).join("\n\n");
|
|
356
|
+
const result = await provider.send(prompt, { model: selection.model });
|
|
357
|
+
task.status = "done";
|
|
358
|
+
task.result = result;
|
|
359
|
+
task.assignedModel = { provider: selection.provider.name, model: selection.model };
|
|
360
|
+
} catch (err) {
|
|
361
|
+
task.status = "failed";
|
|
362
|
+
task.error = err instanceof Error ? err.message : String(err);
|
|
363
|
+
}
|
|
364
|
+
res.json(task);
|
|
365
|
+
});
|
|
366
|
+
app.get("/providers", (_req, res) => {
|
|
367
|
+
res.json(providerConfigs);
|
|
368
|
+
});
|
|
369
|
+
app.post("/providers", (req, res) => {
|
|
370
|
+
const body = req.body;
|
|
371
|
+
const cfg = {
|
|
372
|
+
id: newId(),
|
|
373
|
+
name: body.name,
|
|
374
|
+
kind: body.kind,
|
|
375
|
+
baseUrl: body.baseUrl,
|
|
376
|
+
apiKey: body.apiKey,
|
|
377
|
+
defaultModel: body.defaultModel,
|
|
378
|
+
capabilities: Array.isArray(body.capabilities) ? body.capabilities : JSON.parse(body.capabilities || "[]"),
|
|
379
|
+
enabled: body.enabled ?? true
|
|
380
|
+
};
|
|
381
|
+
providerConfigs.push(cfg);
|
|
382
|
+
res.status(201).json(cfg);
|
|
383
|
+
});
|
|
384
|
+
app.patch("/providers/:id/toggle", (req, res) => {
|
|
385
|
+
const cfg = providerConfigs.find((c) => c.id === req.params.id);
|
|
386
|
+
if (!cfg) return res.status(404).json({ error: "Provider not found" });
|
|
387
|
+
cfg.enabled = !cfg.enabled;
|
|
388
|
+
res.json(cfg);
|
|
389
|
+
});
|
|
390
|
+
app.delete("/providers/:id", (req, res) => {
|
|
391
|
+
providerConfigs = providerConfigs.filter((c) => c.id !== req.params.id);
|
|
392
|
+
res.status(204).send();
|
|
393
|
+
});
|
|
394
|
+
app.post("/router/select", (req, res) => {
|
|
395
|
+
const body = req.body;
|
|
396
|
+
const task = {
|
|
397
|
+
id: "preview",
|
|
398
|
+
projectId: "preview",
|
|
399
|
+
title: body.title,
|
|
400
|
+
status: "todo",
|
|
401
|
+
complexity: body.complexity || "simple",
|
|
402
|
+
tags: body.tags || [],
|
|
403
|
+
createdAt: now(),
|
|
404
|
+
updatedAt: now()
|
|
405
|
+
};
|
|
406
|
+
const selection = selectProvider(providerConfigs, rules, task);
|
|
407
|
+
if (!selection) return res.status(404).json({ error: "No provider available" });
|
|
408
|
+
res.json({ provider: selection.provider.name, model: selection.model });
|
|
409
|
+
});
|
|
410
|
+
var webDir = path.join(__dirname, "web");
|
|
411
|
+
app.use(express.static(webDir));
|
|
412
|
+
app.get("*", (_req, res) => {
|
|
413
|
+
res.sendFile(path.join(webDir, "index.html"));
|
|
414
|
+
});
|
|
415
|
+
var PORT = process.env.PORT || 4e3;
|
|
416
|
+
app.listen(PORT, () => {
|
|
417
|
+
console.log(`Omega harness server on http://localhost:${PORT}`);
|
|
418
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"AAOA,iBAAS,GAAG,gCAoDX;AAED,eAAe,GAAG,CAAC"}
|
package/dist/web/App.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { api } from './lib/api.js';
|
|
4
|
+
import { ProjectSidebar } from './components/ProjectSidebar.js';
|
|
5
|
+
import { TaskBoard } from './components/TaskBoard.js';
|
|
6
|
+
import { ProviderSettings } from './components/ProviderSettings.js';
|
|
7
|
+
import { RouterPanel } from './components/RouterPanel.js';
|
|
8
|
+
function App() {
|
|
9
|
+
const [projects, setProjects] = useState([]);
|
|
10
|
+
const [providers, setProviders] = useState([]);
|
|
11
|
+
const [selectedProjectId, setSelectedProjectId] = useState();
|
|
12
|
+
const [error, setError] = useState('');
|
|
13
|
+
async function loadProjects() {
|
|
14
|
+
try {
|
|
15
|
+
const data = await api.getProjects();
|
|
16
|
+
setProjects(data);
|
|
17
|
+
setError('');
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function loadProviders() {
|
|
24
|
+
try {
|
|
25
|
+
const data = await api.getProviders();
|
|
26
|
+
setProviders(data);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
loadProjects();
|
|
34
|
+
loadProviders();
|
|
35
|
+
}, []);
|
|
36
|
+
return (_jsxs("div", { className: "flex h-screen w-screen overflow-hidden", children: [_jsx(ProjectSidebar, { projects: projects, selectedId: selectedProjectId, onSelect: setSelectedProjectId, onChange: loadProjects }), _jsx(TaskBoard, { projectId: selectedProjectId }), _jsxs("aside", { className: "w-80 bg-white border-l border-gray-200 h-screen overflow-y-auto", children: [error && (_jsx("div", { className: "p-3 bg-red-50 text-red-700 text-xs border-b border-red-100", children: error })), _jsx(ProviderSettings, { providers: providers, onChange: loadProviders }), _jsx(RouterPanel, {})] })] }));
|
|
37
|
+
}
|
|
38
|
+
export default App;
|
|
39
|
+
//# sourceMappingURL=App.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.js","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,cAAc,EAAgB,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE1D,SAAS,GAAG;IACV,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAY,EAAE,CAAC,CAAC;IACxD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAa,EAAE,CAAC,CAAC;IAC3D,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,QAAQ,EAAU,CAAC;IACrE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEvC,KAAK,UAAU,YAAY;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;YACrC,WAAW,CAAC,IAAI,CAAC,CAAC;YAClB,QAAQ,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,KAAK,UAAU,aAAa;QAC1B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC;YACtC,YAAY,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,SAAS,CAAC,GAAG,EAAE;QACb,YAAY,EAAE,CAAC;QACf,aAAa,EAAE,CAAC;IAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CACL,eAAK,SAAS,EAAC,wCAAwC,aACrD,KAAC,cAAc,IACb,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,iBAAiB,EAC7B,QAAQ,EAAE,oBAAoB,EAC9B,QAAQ,EAAE,YAAY,GACtB,EAEF,KAAC,SAAS,IAAC,SAAS,EAAE,iBAAiB,GAAI,EAE3C,iBAAO,SAAS,EAAC,iEAAiE,aAC/E,KAAK,IAAI,CACR,cAAK,SAAS,EAAC,4DAA4D,YACxE,KAAK,GACF,CACP,EACD,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,GAAI,EACnE,KAAC,WAAW,KAAG,IACT,IACJ,CACP,CAAC;AACJ,CAAC;AAED,eAAe,GAAG,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mt-3{margin-top:.75rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.flex{display:flex}.grid{display:grid}.h-screen{height:100vh}.max-h-32{max-height:8rem}.w-32{width:8rem}.w-72{width:18rem}.w-80{width:20rem}.w-full{width:100%}.w-screen{width:100vw}.min-w-\[200px\]{min-width:200px}.min-w-\[800px\]{min-width:800px}.flex-1{flex:1 1 0%}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.border-red-100{--tw-border-opacity: 1;border-color:rgb(254 226 226 / var(--tw-border-opacity, 1))}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-purple-50{--tw-bg-opacity: 1;background-color:rgb(250 245 255 / var(--tw-bg-opacity, 1))}.bg-purple-600{--tw-bg-opacity: 1;background-color:rgb(147 51 234 / var(--tw-bg-opacity, 1))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.text-left{text-align:left}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}body{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.hover\:bg-blue-700:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-300:hover{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.hover\:bg-green-700:hover{--tw-bg-opacity: 1;background-color:rgb(21 128 61 / var(--tw-bg-opacity, 1))}.hover\:bg-purple-700:hover{--tw-bg-opacity: 1;background-color:rgb(126 34 206 / var(--tw-bg-opacity, 1))}.hover\:text-red-700:hover{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.disabled\:opacity-50:disabled{opacity:.5}
|