@heylemon/lemonade 0.6.0 → 0.6.1

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.0",
3
- "commit": "3e86276898dcac0ab6e58fb3fd5732fe712d069a",
4
- "builtAt": "2026-02-27T09:13:13.678Z"
2
+ "version": "0.6.1",
3
+ "commit": "4ac9cdf2202fe251d4f4c3cc2dd1dc0f7995ce5a",
4
+ "builtAt": "2026-02-27T09:54:23.299Z"
5
5
  }
@@ -1 +1 @@
1
- c7c3dae0b04843ef55701aa1424c692bd8fd4a90bdea1187fb53373a6da54271
1
+ fbb8e9270941a7d4b64212c26ad798019be3c219955c41a3ac299b8fb801a71a
@@ -0,0 +1,82 @@
1
+ import { authorizeGatewayConnect } from "./auth.js";
2
+ import { sendJson, sendMethodNotAllowed, sendUnauthorized, sendInvalidRequest, readJsonBodyOrError, } from "./http-common.js";
3
+ import { getBearerToken } from "./http-utils.js";
4
+ export async function handleCronHttpRequest(req, res, opts) {
5
+ const url = new URL(req.url ?? "/", `http://${req.headers.host || "localhost"}`);
6
+ if (!url.pathname.startsWith("/api/cron"))
7
+ return false;
8
+ const token = getBearerToken(req);
9
+ const authResult = await authorizeGatewayConnect({
10
+ auth: opts.auth,
11
+ connectAuth: { token, password: token },
12
+ req,
13
+ trustedProxies: opts.trustedProxies,
14
+ });
15
+ if (!authResult.ok) {
16
+ sendUnauthorized(res);
17
+ return true;
18
+ }
19
+ res.setHeader("Access-Control-Allow-Origin", "*");
20
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
21
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
22
+ if (req.method === "OPTIONS") {
23
+ res.statusCode = 204;
24
+ res.end();
25
+ return true;
26
+ }
27
+ const cron = opts.getCronService();
28
+ if (!cron) {
29
+ sendJson(res, 503, {
30
+ error: { message: "Cron service not available", type: "service_unavailable" },
31
+ });
32
+ return true;
33
+ }
34
+ const subPath = url.pathname.replace(/^\/api\/cron\/?/, "").replace(/\/+$/, "");
35
+ try {
36
+ switch (subPath) {
37
+ case "run":
38
+ return await handleRunJob(req, res, cron);
39
+ case "jobs":
40
+ return await handleListJobs(req, res, cron);
41
+ default:
42
+ sendJson(res, 404, {
43
+ error: { message: "Not found", type: "not_found" },
44
+ });
45
+ return true;
46
+ }
47
+ }
48
+ catch (err) {
49
+ console.error("[cron-http] Internal error:", err);
50
+ sendJson(res, 500, {
51
+ error: { message: "An internal error occurred", type: "internal_error" },
52
+ });
53
+ return true;
54
+ }
55
+ }
56
+ async function handleRunJob(req, res, cron) {
57
+ if (req.method !== "POST") {
58
+ sendMethodNotAllowed(res, "POST");
59
+ return true;
60
+ }
61
+ const body = (await readJsonBodyOrError(req, res, 10_000));
62
+ if (!body)
63
+ return true;
64
+ const jobId = body.id ?? body.jobId;
65
+ if (!jobId || typeof jobId !== "string") {
66
+ sendInvalidRequest(res, "id is required");
67
+ return true;
68
+ }
69
+ const mode = body.mode === "due" ? "due" : "force";
70
+ const result = await cron.run(jobId, mode);
71
+ sendJson(res, 200, result);
72
+ return true;
73
+ }
74
+ async function handleListJobs(req, res, cron) {
75
+ if (req.method !== "GET") {
76
+ sendMethodNotAllowed(res, "GET");
77
+ return true;
78
+ }
79
+ const jobs = await cron.list({ includeDisabled: true });
80
+ sendJson(res, 200, { jobs });
81
+ return true;
82
+ }
@@ -12,6 +12,7 @@ import { handleOpenResponsesHttpRequest } from "./openresponses-http.js";
12
12
  import { handleSkillsHttpRequest } from "./skills-http.js";
13
13
  import { handleTaskEventsHttpRequest } from "./task-events-http.js";
14
14
  import { handleToolsInvokeHttpRequest } from "./tools-invoke-http.js";
15
+ import { handleCronHttpRequest } from "./cron-http.js";
15
16
  function sendJson(res, status, body) {
16
17
  res.statusCode = status;
17
18
  res.setHeader("Content-Type", "application/json; charset=utf-8");
@@ -143,7 +144,7 @@ export function createHooksRequestHandler(opts) {
143
144
  };
144
145
  }
145
146
  export function createGatewayHttpServer(opts) {
146
- const { canvasHost, controlUiEnabled, controlUiBasePath, openAiChatCompletionsEnabled, openResponsesEnabled, openResponsesConfig, handleHooksRequest, handlePluginRequest, resolvedAuth, } = opts;
147
+ const { canvasHost, controlUiEnabled, controlUiBasePath, openAiChatCompletionsEnabled, openResponsesEnabled, openResponsesConfig, handleHooksRequest, handlePluginRequest, cronHttpOptions, resolvedAuth, } = opts;
147
148
  const httpServer = opts.tlsOptions
148
149
  ? createHttpsServer(opts.tlsOptions, (req, res) => {
149
150
  void handleRequest(req, res);
@@ -177,6 +178,12 @@ export function createGatewayHttpServer(opts) {
177
178
  trustedProxies,
178
179
  }))
179
180
  return;
181
+ if (cronHttpOptions &&
182
+ (await handleCronHttpRequest(req, res, {
183
+ ...cronHttpOptions,
184
+ trustedProxies,
185
+ })))
186
+ return;
180
187
  if (handlePluginRequest && (await handlePluginRequest(req, res)))
181
188
  return;
182
189
  if (openResponsesEnabled) {
@@ -53,6 +53,9 @@ export async function createGatewayRuntimeState(params) {
53
53
  openResponsesConfig: params.openResponsesConfig,
54
54
  handleHooksRequest,
55
55
  handlePluginRequest,
56
+ cronHttpOptions: params.getCronService
57
+ ? { auth: params.resolvedAuth, getCronService: params.getCronService }
58
+ : undefined,
56
59
  resolvedAuth: params.resolvedAuth,
57
60
  tlsOptions: params.gatewayTls?.enabled ? params.gatewayTls.tlsOptions : undefined,
58
61
  });
@@ -173,6 +173,7 @@ export async function startGatewayServer(port = 18789, opts = {}) {
173
173
  canvasRuntime,
174
174
  canvasHostEnabled,
175
175
  allowCanvasHostInTests: opts.allowCanvasHostInTests,
176
+ getCronService: () => cron ?? null,
176
177
  logCanvas,
177
178
  log,
178
179
  logHooks,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heylemon/lemonade",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "AI gateway CLI for Lemon - local AI assistant with integrations",
5
5
  "publishConfig": {
6
6
  "access": "restricted"