@rubytech/taskmaster 1.26.0 → 1.26.2

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.
@@ -6,7 +6,7 @@
6
6
  <title>Taskmaster Control</title>
7
7
  <meta name="color-scheme" content="dark light" />
8
8
  <link rel="icon" type="image/png" href="./favicon.png" />
9
- <script type="module" crossorigin src="./assets/index-C1r0QazL.js"></script>
9
+ <script type="module" crossorigin src="./assets/index-1WwUK7EM.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="./assets/index-C7ieCeTV.css">
11
11
  </head>
12
12
  <body>
@@ -0,0 +1,67 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { getResolvedLoggerSettings } from "../logging.js";
4
+ import { loadConfig } from "../config/config.js";
5
+ import { isMasterSession } from "./server-methods/access.js";
6
+ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
7
+ function todayLocal() {
8
+ const d = new Date();
9
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
10
+ }
11
+ /**
12
+ * GET /logs/download?date=YYYY-MM-DD&token=...
13
+ *
14
+ * Serves the raw log file as a download. Requires admin (master PIN) session
15
+ * when a master PIN is configured.
16
+ */
17
+ export function handleLogDownloadRequest(req, res) {
18
+ const url = new URL(req.url ?? "/", "http://localhost");
19
+ if (url.pathname !== "/logs/download" || req.method !== "GET")
20
+ return false;
21
+ // Auth: require master session when a master PIN is set.
22
+ const config = loadConfig();
23
+ const hasMasterPin = Boolean(config.access?.masterPin);
24
+ if (hasMasterPin) {
25
+ const token = url.searchParams.get("token");
26
+ if (!token || !isMasterSession(token)) {
27
+ res.statusCode = 403;
28
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
29
+ res.end("Forbidden: admin login required");
30
+ return true;
31
+ }
32
+ }
33
+ const dateParam = url.searchParams.get("date");
34
+ const date = dateParam && DATE_RE.test(dateParam) ? dateParam : todayLocal();
35
+ const logDir = path.dirname(getResolvedLoggerSettings().file);
36
+ const file = path.join(logDir, `taskmaster-${date}.log`);
37
+ try {
38
+ const stat = fs.statSync(file);
39
+ if (!stat.isFile()) {
40
+ res.statusCode = 404;
41
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
42
+ res.end("Log file not found");
43
+ return true;
44
+ }
45
+ res.statusCode = 200;
46
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
47
+ res.setHeader("Content-Disposition", `attachment; filename="taskmaster-${date}.log"`);
48
+ res.setHeader("Content-Length", stat.size);
49
+ const stream = fs.createReadStream(file);
50
+ stream.pipe(res);
51
+ stream.on("error", () => {
52
+ if (!res.headersSent) {
53
+ res.statusCode = 500;
54
+ res.end("Read error");
55
+ }
56
+ else {
57
+ res.end();
58
+ }
59
+ });
60
+ }
61
+ catch {
62
+ res.statusCode = 404;
63
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
64
+ res.end("Log file not found");
65
+ }
66
+ return true;
67
+ }
@@ -2,8 +2,8 @@ import { Type } from "@sinclair/typebox";
2
2
  import { NonEmptyString } from "./primitives.js";
3
3
  export const LogsTailParamsSchema = Type.Object({
4
4
  cursor: Type.Optional(Type.Integer({ minimum: 0 })),
5
- limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 5000 })),
6
- maxBytes: Type.Optional(Type.Integer({ minimum: 1, maximum: 1_000_000 })),
5
+ limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 50_000 })),
6
+ maxBytes: Type.Optional(Type.Integer({ minimum: 1, maximum: 10_000_000 })),
7
7
  minLevel: Type.Optional(Type.Union([
8
8
  Type.Literal("fatal"),
9
9
  Type.Literal("error"),
@@ -15,6 +15,7 @@ import { extractHookToken, getHookChannelError, normalizeAgentPayload, normalize
15
15
  import { applyHookMappings } from "./hooks-mapping.js";
16
16
  import { handleOpenAiHttpRequest } from "./openai-http.js";
17
17
  import { handleOpenResponsesHttpRequest } from "./openresponses-http.js";
18
+ import { handleLogDownloadRequest } from "./logs-download-http.js";
18
19
  import { handleMediaRequest } from "./media-http.js";
19
20
  import { handleToolsInvokeHttpRequest } from "./tools-invoke-http.js";
20
21
  function sendJson(res, status, body) {
@@ -248,6 +249,8 @@ export function createGatewayHttpServer(opts) {
248
249
  }
249
250
  }
250
251
  }
252
+ if (handleLogDownloadRequest(req, res))
253
+ return;
251
254
  if (await handleHooksRequest(req, res))
252
255
  return;
253
256
  if (await handleToolsInvokeHttpRequest(req, res, {
@@ -8,7 +8,7 @@ import { ErrorCodes, errorShape, formatValidationErrors, validateLogsTailParams,
8
8
  import { isMasterSession } from "./access.js";
9
9
  const CONTROL_UI_CLIENT_ID = "taskmaster-control-ui";
10
10
  const DEFAULT_LIMIT = 5000;
11
- const DEFAULT_MAX_BYTES = 2_000_000;
11
+ const DEFAULT_MAX_BYTES = 5_000_000;
12
12
  const MAX_LIMIT = 50_000;
13
13
  const MAX_BYTES = 10_000_000;
14
14
  const ROLLING_LOG_RE = /^taskmaster-\d{4}-\d{2}-\d{2}\.log$/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.26.0",
3
+ "version": "1.26.2",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"