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 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 getDbPath() {
12
+ function getArg(flag) {
12
13
  const args = process.argv.slice(2);
13
- const dbIndex = args.indexOf("--db");
14
- if (dbIndex !== -1 && args[dbIndex + 1]) {
15
- const raw = args[dbIndex + 1];
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 server = new McpServer({
27
- name: "foreman-ai",
28
- version: "1.1.0",
29
- });
30
- registerProjectTools(server);
31
- registerItemTools(server);
32
- registerSmartTools(server);
33
- registerSessionTools(server);
34
- const transport = new StdioServerTransport();
35
- await server.connect(transport);
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,SAAS;IAChB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,OAAO,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QAC9B,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;AAC3D,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,CAAC,MAAM,CAAC,CAAC;IAEf,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAE7B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;IAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
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": "1.1.0",
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
- "start": "node dist/index.js"
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
+ ```