foreman-ai 1.1.0 → 2.0.0
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/index.js +33 -14
- package/dist/index.js.map +1 -1
- package/dist/web/middleware.d.ts +8 -0
- package/dist/web/middleware.js +20 -0
- package/dist/web/middleware.js.map +1 -0
- package/dist/web/routes/items.d.ts +1 -0
- package/dist/web/routes/items.js +123 -0
- package/dist/web/routes/items.js.map +1 -0
- package/dist/web/routes/projects.d.ts +1 -0
- package/dist/web/routes/projects.js +33 -0
- package/dist/web/routes/projects.js.map +1 -0
- package/dist/web/routes/sessions.d.ts +1 -0
- package/dist/web/routes/sessions.js +34 -0
- package/dist/web/routes/sessions.js.map +1 -0
- package/dist/web/server.d.ts +1 -0
- package/dist/web/server.js +33 -0
- package/dist/web/server.js.map +1 -0
- package/package.json +9 -2
- package/web/README.md +73 -0
package/dist/index.js
CHANGED
|
@@ -6,13 +6,23 @@ import { registerProjectTools } from "./tools/projects.js";
|
|
|
6
6
|
import { registerItemTools } from "./tools/items.js";
|
|
7
7
|
import { registerSmartTools } from "./tools/smart.js";
|
|
8
8
|
import { registerSessionTools } from "./tools/sessions.js";
|
|
9
|
+
import { startWebServer } from "./web/server.js";
|
|
9
10
|
import path from "path";
|
|
10
11
|
import os from "os";
|
|
11
|
-
function
|
|
12
|
+
function getArg(flag) {
|
|
12
13
|
const args = process.argv.slice(2);
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
15
|
-
|
|
14
|
+
const idx = args.indexOf(flag);
|
|
15
|
+
if (idx !== -1 && args[idx + 1]) {
|
|
16
|
+
return args[idx + 1];
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
function hasFlag(flag) {
|
|
21
|
+
return process.argv.slice(2).includes(flag);
|
|
22
|
+
}
|
|
23
|
+
function getDbPath() {
|
|
24
|
+
const raw = getArg("--db");
|
|
25
|
+
if (raw) {
|
|
16
26
|
if (raw.startsWith("~")) {
|
|
17
27
|
return path.join(os.homedir(), raw.slice(1));
|
|
18
28
|
}
|
|
@@ -23,16 +33,25 @@ function getDbPath() {
|
|
|
23
33
|
async function main() {
|
|
24
34
|
const dbPath = getDbPath();
|
|
25
35
|
initDb(dbPath);
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
const webMode = hasFlag("--web");
|
|
37
|
+
const port = parseInt(getArg("--port") ?? "4040");
|
|
38
|
+
if (webMode) {
|
|
39
|
+
// Web-only mode: start HTTP server, no MCP stdio
|
|
40
|
+
startWebServer(port);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// Default: MCP server over stdio
|
|
44
|
+
const server = new McpServer({
|
|
45
|
+
name: "foreman-ai",
|
|
46
|
+
version: "2.0.0",
|
|
47
|
+
});
|
|
48
|
+
registerProjectTools(server);
|
|
49
|
+
registerItemTools(server);
|
|
50
|
+
registerSmartTools(server);
|
|
51
|
+
registerSessionTools(server);
|
|
52
|
+
const transport = new StdioServerTransport();
|
|
53
|
+
await server.connect(transport);
|
|
54
|
+
}
|
|
36
55
|
}
|
|
37
56
|
main().catch((err) => {
|
|
38
57
|
console.error("Foreman failed to start:", err);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,SAAS,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from "express";
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
export declare const corsMiddleware: (req: cors.CorsRequest, res: {
|
|
4
|
+
statusCode?: number | undefined;
|
|
5
|
+
setHeader(key: string, value: string): any;
|
|
6
|
+
end(): any;
|
|
7
|
+
}, next: (err?: any) => any) => void;
|
|
8
|
+
export declare function apiKeyAuth(req: Request, res: Response, next: NextFunction): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import cors from "cors";
|
|
2
|
+
export const corsMiddleware = cors({
|
|
3
|
+
origin: true,
|
|
4
|
+
credentials: true,
|
|
5
|
+
});
|
|
6
|
+
export function apiKeyAuth(req, res, next) {
|
|
7
|
+
const apiKey = process.env.FOREMAN_API_KEY;
|
|
8
|
+
// If no API key is configured, allow all requests (local use)
|
|
9
|
+
if (!apiKey) {
|
|
10
|
+
next();
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const provided = req.headers["x-api-key"] || req.query["api_key"];
|
|
14
|
+
if (provided !== apiKey) {
|
|
15
|
+
res.status(401).json({ error: "Invalid API key" });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
next();
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/web/middleware.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,IAAI;IACZ,WAAW,EAAE,IAAI;CAClB,CAAC,CAAC;AAEH,MAAM,UAAU,UAAU,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IACxE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAE3C,8DAA8D;IAC9D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,IAAI,EAAE,CAAC;QACP,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAClE,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;QACnD,OAAO;IACT,CAAC;IAED,IAAI,EAAE,CAAC;AACT,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const itemsRouter: import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import { getDb } from "../../db.js";
|
|
4
|
+
function rowToItem(row) {
|
|
5
|
+
return {
|
|
6
|
+
...row,
|
|
7
|
+
blocked_by: JSON.parse(row.blocked_by),
|
|
8
|
+
tags: JSON.parse(row.tags),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export const itemsRouter = Router();
|
|
12
|
+
// List items with filters
|
|
13
|
+
itemsRouter.get("/", (req, res) => {
|
|
14
|
+
const db = getDb();
|
|
15
|
+
const { project_id, status, category, tag, execution_mode } = req.query;
|
|
16
|
+
const conditions = [];
|
|
17
|
+
const params = [];
|
|
18
|
+
if (project_id) {
|
|
19
|
+
conditions.push("project_id = ?");
|
|
20
|
+
params.push(project_id);
|
|
21
|
+
}
|
|
22
|
+
if (status) {
|
|
23
|
+
conditions.push("status = ?");
|
|
24
|
+
params.push(status);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
conditions.push("status != 'archived'");
|
|
28
|
+
}
|
|
29
|
+
if (category) {
|
|
30
|
+
conditions.push("category = ?");
|
|
31
|
+
params.push(category);
|
|
32
|
+
}
|
|
33
|
+
if (tag) {
|
|
34
|
+
conditions.push("tags LIKE ?");
|
|
35
|
+
params.push(`%"${tag}"%`);
|
|
36
|
+
}
|
|
37
|
+
if (execution_mode) {
|
|
38
|
+
conditions.push("execution_mode = ?");
|
|
39
|
+
params.push(execution_mode);
|
|
40
|
+
}
|
|
41
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
42
|
+
const rows = db
|
|
43
|
+
.prepare(`SELECT * FROM items ${where} ORDER BY priority ASC, roi_score DESC`)
|
|
44
|
+
.all(...params);
|
|
45
|
+
res.json(rows.map(rowToItem));
|
|
46
|
+
});
|
|
47
|
+
// Get single item
|
|
48
|
+
itemsRouter.get("/:id", (req, res) => {
|
|
49
|
+
const db = getDb();
|
|
50
|
+
const row = db
|
|
51
|
+
.prepare("SELECT * FROM items WHERE id = ? OR id LIKE ?")
|
|
52
|
+
.get(req.params.id, `${req.params.id}%`);
|
|
53
|
+
if (!row) {
|
|
54
|
+
res.status(404).json({ error: "Item not found" });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
res.json(rowToItem(row));
|
|
58
|
+
});
|
|
59
|
+
// Create item
|
|
60
|
+
itemsRouter.post("/", (req, res) => {
|
|
61
|
+
const db = getDb();
|
|
62
|
+
const id = uuidv4();
|
|
63
|
+
const now = new Date().toISOString();
|
|
64
|
+
const b = req.body;
|
|
65
|
+
db.prepare(`
|
|
66
|
+
INSERT INTO items (id, project_id, title, description, status, priority, category,
|
|
67
|
+
roi_score, roi_reason, effort, blocked_by, tags, source, execution_mode,
|
|
68
|
+
assigned_to, created_at, updated_at)
|
|
69
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
70
|
+
`).run(id, b.project_id, b.title, b.description ?? "", b.status ?? "backlog", b.priority ?? 100, b.category ?? "feature", b.roi_score ?? null, b.roi_reason ?? "", b.effort ?? null, JSON.stringify(b.blocked_by ?? []), JSON.stringify(b.tags ?? []), b.source ?? "user", b.execution_mode ?? "manual", b.assigned_to ?? "", now, now);
|
|
71
|
+
const row = db.prepare("SELECT * FROM items WHERE id = ?").get(id);
|
|
72
|
+
res.status(201).json(rowToItem(row));
|
|
73
|
+
});
|
|
74
|
+
// Update item
|
|
75
|
+
itemsRouter.patch("/:id", (req, res) => {
|
|
76
|
+
const db = getDb();
|
|
77
|
+
const { id } = req.params;
|
|
78
|
+
const updates = req.body;
|
|
79
|
+
const now = new Date().toISOString();
|
|
80
|
+
const sets = [];
|
|
81
|
+
const values = [];
|
|
82
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
83
|
+
if (value === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
if (key === "blocked_by" || key === "tags") {
|
|
86
|
+
sets.push(`${key} = ?`);
|
|
87
|
+
values.push(JSON.stringify(value));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
sets.push(`${key} = ?`);
|
|
91
|
+
values.push(value);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (sets.length === 0) {
|
|
95
|
+
res.status(400).json({ error: "No fields to update" });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (updates.status === "done") {
|
|
99
|
+
sets.push("completed_at = ?");
|
|
100
|
+
values.push(now);
|
|
101
|
+
}
|
|
102
|
+
sets.push("updated_at = ?");
|
|
103
|
+
values.push(now);
|
|
104
|
+
values.push(id);
|
|
105
|
+
const result = db.prepare(`UPDATE items SET ${sets.join(", ")} WHERE id = ?`).run(...values);
|
|
106
|
+
if (result.changes === 0) {
|
|
107
|
+
res.status(404).json({ error: "Item not found" });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const row = db.prepare("SELECT * FROM items WHERE id = ?").get(id);
|
|
111
|
+
res.json(rowToItem(row));
|
|
112
|
+
});
|
|
113
|
+
// Delete item
|
|
114
|
+
itemsRouter.delete("/:id", (req, res) => {
|
|
115
|
+
const db = getDb();
|
|
116
|
+
const result = db.prepare("DELETE FROM items WHERE id = ?").run(req.params.id);
|
|
117
|
+
if (result.changes === 0) {
|
|
118
|
+
res.status(404).json({ error: "Item not found" });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
res.json({ deleted: true });
|
|
122
|
+
});
|
|
123
|
+
//# sourceMappingURL=items.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"items.js","sourceRoot":"","sources":["../../../src/web/routes/items.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const projectsRouter: import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { getDb } from "../../db.js";
|
|
3
|
+
export const projectsRouter = Router();
|
|
4
|
+
projectsRouter.get("/", (_req, res) => {
|
|
5
|
+
const db = getDb();
|
|
6
|
+
const projects = db.prepare("SELECT * FROM projects ORDER BY name").all();
|
|
7
|
+
res.json(projects);
|
|
8
|
+
});
|
|
9
|
+
projectsRouter.get("/:id", (req, res) => {
|
|
10
|
+
const db = getDb();
|
|
11
|
+
const project = db.prepare("SELECT * FROM projects WHERE id = ?").get(req.params.id);
|
|
12
|
+
if (!project) {
|
|
13
|
+
res.status(404).json({ error: "Project not found" });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
res.json(project);
|
|
17
|
+
});
|
|
18
|
+
projectsRouter.put("/:id", (req, res) => {
|
|
19
|
+
const db = getDb();
|
|
20
|
+
const { name, description, repo_path } = req.body;
|
|
21
|
+
const now = new Date().toISOString();
|
|
22
|
+
const id = req.params.id;
|
|
23
|
+
const existing = db.prepare("SELECT id FROM projects WHERE id = ?").get(id);
|
|
24
|
+
if (existing) {
|
|
25
|
+
db.prepare("UPDATE projects SET name = ?, description = ?, repo_path = ?, updated_at = ? WHERE id = ?").run(name, description ?? "", repo_path ?? "", now, id);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
db.prepare("INSERT INTO projects (id, name, description, repo_path, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(id, name, description ?? "", repo_path ?? "", now, now);
|
|
29
|
+
}
|
|
30
|
+
const project = db.prepare("SELECT * FROM projects WHERE id = ?").get(id);
|
|
31
|
+
res.json(project);
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../../src/web/routes/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGpC,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC;AAEvC,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IACpC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,EAAe,CAAC;IACvF,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACrB,CAAC,CAAC,CAAC;AAEH,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACtC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAwB,CAAC;IAE5G,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;QACrD,OAAO;IACT,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC;AAEH,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACtC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;IAClD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;IAEzB,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAE5E,IAAI,QAAQ,EAAE,CAAC;QACb,EAAE,CAAC,OAAO,CACR,2FAA2F,CAC5F,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE,EAAE,SAAS,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC3D,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,OAAO,CACR,2GAA2G,CAC5G,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,IAAI,EAAE,EAAE,SAAS,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC1E,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sessionsRouter: import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { getDb } from "../../db.js";
|
|
3
|
+
function rowToSession(row) {
|
|
4
|
+
return {
|
|
5
|
+
...row,
|
|
6
|
+
items_touched: JSON.parse(row.items_touched),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export const sessionsRouter = Router();
|
|
10
|
+
sessionsRouter.get("/", (req, res) => {
|
|
11
|
+
const db = getDb();
|
|
12
|
+
const { project_id, limit } = req.query;
|
|
13
|
+
const max = Math.min(parseInt(limit) || 20, 50);
|
|
14
|
+
let query = "SELECT * FROM sessions";
|
|
15
|
+
const params = [];
|
|
16
|
+
if (project_id) {
|
|
17
|
+
query += " WHERE project_id = ?";
|
|
18
|
+
params.push(project_id);
|
|
19
|
+
}
|
|
20
|
+
query += " ORDER BY started_at DESC LIMIT ?";
|
|
21
|
+
params.push(max);
|
|
22
|
+
const rows = db.prepare(query).all(...params);
|
|
23
|
+
res.json(rows.map(rowToSession));
|
|
24
|
+
});
|
|
25
|
+
sessionsRouter.get("/:id", (req, res) => {
|
|
26
|
+
const db = getDb();
|
|
27
|
+
const row = db.prepare("SELECT * FROM sessions WHERE id = ?").get(req.params.id);
|
|
28
|
+
if (!row) {
|
|
29
|
+
res.status(404).json({ error: "Session not found" });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
res.json(rowToSession(row));
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../../src/web/routes/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGpC,SAAS,YAAY,CAAC,GAA4B;IAChD,OAAO;QACL,GAAG,GAAG;QACN,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAuB,CAAC;KAC5C,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC;AAEvC,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACnC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAe,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAE1D,IAAI,KAAK,GAAG,wBAAwB,CAAC;IACrC,MAAM,MAAM,GAAc,EAAE,CAAC;IAE7B,IAAI,UAAU,EAAE,CAAC;QACf,KAAK,IAAI,uBAAuB,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK,IAAI,mCAAmC,CAAC;IAC7C,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEjB,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAA8B,CAAC;IAC3E,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;AACnC,CAAC,CAAC,CAAC;AAEH,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACtC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAwC,CAAC;IAExH,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;QACrD,OAAO;IACT,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startWebServer(port: number): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { corsMiddleware, apiKeyAuth } from "./middleware.js";
|
|
5
|
+
import { projectsRouter } from "./routes/projects.js";
|
|
6
|
+
import { itemsRouter } from "./routes/items.js";
|
|
7
|
+
import { sessionsRouter } from "./routes/sessions.js";
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
export function startWebServer(port) {
|
|
10
|
+
const app = express();
|
|
11
|
+
app.use(corsMiddleware);
|
|
12
|
+
app.use(express.json());
|
|
13
|
+
app.use("/api/*path", apiKeyAuth);
|
|
14
|
+
// REST API routes
|
|
15
|
+
app.use("/api/projects", projectsRouter);
|
|
16
|
+
app.use("/api/items", itemsRouter);
|
|
17
|
+
app.use("/api/sessions", sessionsRouter);
|
|
18
|
+
// Health check
|
|
19
|
+
app.get("/api/health", (_req, res) => {
|
|
20
|
+
res.json({ status: "ok", version: "2.0.0" });
|
|
21
|
+
});
|
|
22
|
+
// Serve frontend static files
|
|
23
|
+
const webDist = path.join(__dirname, "..", "..", "web", "dist");
|
|
24
|
+
app.use(express.static(webDist));
|
|
25
|
+
// SPA fallback — serve index.html for all non-API routes
|
|
26
|
+
app.get("*path", (_req, res) => {
|
|
27
|
+
res.sendFile(path.join(webDist, "index.html"));
|
|
28
|
+
});
|
|
29
|
+
app.listen(port, () => {
|
|
30
|
+
console.error(`Foreman dashboard running at http://localhost:${port}`);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IAEtB,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACxB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACxB,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAElC,kBAAkB;IAClB,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IACnC,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;IAEzC,eAAe;IACf,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACnC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,8BAA8B;IAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IAChE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAEjC,yDAAyD;IACzD,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QAC7B,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACpB,OAAO,CAAC,KAAK,CAAC,iDAAiD,IAAI,EAAE,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foreman-ai",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI Project Manager MCP server — persistent, cross-project backlog management for Claude Code",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc",
|
|
13
|
+
"build:web": "cd web && npm run build",
|
|
14
|
+
"build:all": "tsc && cd web && npm run build",
|
|
13
15
|
"dev": "tsc --watch",
|
|
14
|
-
"
|
|
16
|
+
"dev:web": "cd web && npm run dev",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"dashboard": "node dist/index.js --web"
|
|
15
19
|
},
|
|
16
20
|
"keywords": [
|
|
17
21
|
"mcp",
|
|
@@ -24,6 +28,7 @@
|
|
|
24
28
|
"license": "MIT",
|
|
25
29
|
"files": [
|
|
26
30
|
"dist",
|
|
31
|
+
"web/dist",
|
|
27
32
|
"README.md"
|
|
28
33
|
],
|
|
29
34
|
"dependencies": {
|
|
@@ -33,6 +38,8 @@
|
|
|
33
38
|
},
|
|
34
39
|
"devDependencies": {
|
|
35
40
|
"@types/better-sqlite3": "^7.6.13",
|
|
41
|
+
"@types/cors": "^2.8.19",
|
|
42
|
+
"@types/express": "^5.0.6",
|
|
36
43
|
"@types/node": "^25.5.0",
|
|
37
44
|
"@types/uuid": "^10.0.0",
|
|
38
45
|
"typescript": "^6.0.2"
|
package/web/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
|
9
|
+
|
|
10
|
+
## React Compiler
|
|
11
|
+
|
|
12
|
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
13
|
+
|
|
14
|
+
## Expanding the ESLint configuration
|
|
15
|
+
|
|
16
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores(['dist']),
|
|
21
|
+
{
|
|
22
|
+
files: ['**/*.{ts,tsx}'],
|
|
23
|
+
extends: [
|
|
24
|
+
// Other configs...
|
|
25
|
+
|
|
26
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
27
|
+
tseslint.configs.recommendedTypeChecked,
|
|
28
|
+
// Alternatively, use this for stricter rules
|
|
29
|
+
tseslint.configs.strictTypeChecked,
|
|
30
|
+
// Optionally, add this for stylistic rules
|
|
31
|
+
tseslint.configs.stylisticTypeChecked,
|
|
32
|
+
|
|
33
|
+
// Other configs...
|
|
34
|
+
],
|
|
35
|
+
languageOptions: {
|
|
36
|
+
parserOptions: {
|
|
37
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
38
|
+
tsconfigRootDir: import.meta.dirname,
|
|
39
|
+
},
|
|
40
|
+
// other options...
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// eslint.config.js
|
|
50
|
+
import reactX from 'eslint-plugin-react-x'
|
|
51
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
52
|
+
|
|
53
|
+
export default defineConfig([
|
|
54
|
+
globalIgnores(['dist']),
|
|
55
|
+
{
|
|
56
|
+
files: ['**/*.{ts,tsx}'],
|
|
57
|
+
extends: [
|
|
58
|
+
// Other configs...
|
|
59
|
+
// Enable lint rules for React
|
|
60
|
+
reactX.configs['recommended-typescript'],
|
|
61
|
+
// Enable lint rules for React DOM
|
|
62
|
+
reactDom.configs.recommended,
|
|
63
|
+
],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parserOptions: {
|
|
66
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
+
tsconfigRootDir: import.meta.dirname,
|
|
68
|
+
},
|
|
69
|
+
// other options...
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
```
|