@lovelybunch/api 1.0.57 → 1.0.59

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.
Files changed (35) hide show
  1. package/dist/lib/git.d.ts +13 -0
  2. package/dist/lib/git.js +137 -5
  3. package/dist/lib/jobs/global-job-scheduler.d.ts +2 -0
  4. package/dist/lib/jobs/global-job-scheduler.js +11 -0
  5. package/dist/lib/jobs/job-runner.d.ts +17 -0
  6. package/dist/lib/jobs/job-runner.js +167 -0
  7. package/dist/lib/jobs/job-scheduler.d.ts +39 -0
  8. package/dist/lib/jobs/job-scheduler.js +309 -0
  9. package/dist/lib/jobs/job-store.d.ts +16 -0
  10. package/dist/lib/jobs/job-store.js +211 -0
  11. package/dist/lib/storage/file-storage.js +7 -5
  12. package/dist/lib/terminal/terminal-manager.d.ts +2 -0
  13. package/dist/lib/terminal/terminal-manager.js +65 -0
  14. package/dist/routes/api/v1/ai/route.d.ts +1 -7
  15. package/dist/routes/api/v1/ai/route.js +25 -12
  16. package/dist/routes/api/v1/git/index.js +63 -1
  17. package/dist/routes/api/v1/jobs/[id]/route.d.ts +133 -0
  18. package/dist/routes/api/v1/jobs/[id]/route.js +135 -0
  19. package/dist/routes/api/v1/jobs/[id]/run/route.d.ts +31 -0
  20. package/dist/routes/api/v1/jobs/[id]/run/route.js +37 -0
  21. package/dist/routes/api/v1/jobs/index.d.ts +3 -0
  22. package/dist/routes/api/v1/jobs/index.js +14 -0
  23. package/dist/routes/api/v1/jobs/route.d.ts +108 -0
  24. package/dist/routes/api/v1/jobs/route.js +144 -0
  25. package/dist/routes/api/v1/jobs/status/route.d.ts +23 -0
  26. package/dist/routes/api/v1/jobs/status/route.js +21 -0
  27. package/dist/routes/api/v1/resources/[id]/route.d.ts +2 -44
  28. package/dist/server-with-static.js +5 -0
  29. package/dist/server.js +5 -0
  30. package/package.json +4 -4
  31. package/static/assets/index-CHq6mL1J.css +33 -0
  32. package/static/assets/index-QHnHUcsV.js +820 -0
  33. package/static/index.html +2 -2
  34. package/static/assets/index-CRg4lVi6.js +0 -779
  35. package/static/assets/index-VqhUTak4.css +0 -33
@@ -117,6 +117,7 @@ export class TerminalManager {
117
117
  previewSockets: new Set(),
118
118
  previewBuffer: '',
119
119
  previewFlushTimer: null,
120
+ previewPingIntervals: new Map(),
120
121
  };
121
122
  this.sessions.set(sessionId, session);
122
123
  // Set up PTY event handlers
@@ -206,6 +207,21 @@ export class TerminalManager {
206
207
  }
207
208
  session.websocket = ws;
208
209
  session.lastActivity = new Date();
210
+ // Setup WebSocket keepalive ping (every 30 seconds)
211
+ const pingInterval = setInterval(() => {
212
+ if (ws.readyState === WebSocket.OPEN) {
213
+ try {
214
+ ws.ping();
215
+ }
216
+ catch (e) {
217
+ clearInterval(pingInterval);
218
+ }
219
+ }
220
+ else {
221
+ clearInterval(pingInterval);
222
+ }
223
+ }, 30000);
224
+ session.pingInterval = pingInterval;
209
225
  // On attach, send a snapshot of recent backlog so refreshed clients render prior output
210
226
  try {
211
227
  if (session.backlog && session.backlog.length > 0 && ws.readyState === WebSocket.OPEN) {
@@ -245,12 +261,20 @@ export class TerminalManager {
245
261
  }
246
262
  });
247
263
  ws.on('close', () => {
264
+ if (session.pingInterval) {
265
+ clearInterval(session.pingInterval);
266
+ session.pingInterval = undefined;
267
+ }
248
268
  if (session.websocket === ws) {
249
269
  session.websocket = undefined;
250
270
  }
251
271
  });
252
272
  ws.on('error', (error) => {
253
273
  console.error('WebSocket error:', error);
274
+ if (session.pingInterval) {
275
+ clearInterval(session.pingInterval);
276
+ session.pingInterval = undefined;
277
+ }
254
278
  if (session.websocket === ws) {
255
279
  session.websocket = undefined;
256
280
  }
@@ -262,10 +286,22 @@ export class TerminalManager {
262
286
  if (!session) {
263
287
  return false;
264
288
  }
289
+ // Clear main WebSocket ping interval
290
+ if (session.pingInterval) {
291
+ clearInterval(session.pingInterval);
292
+ session.pingInterval = undefined;
293
+ }
265
294
  // Close WebSocket if connected
266
295
  if (session.websocket && session.websocket.readyState === WebSocket.OPEN) {
267
296
  session.websocket.close();
268
297
  }
298
+ // Clear all preview ping intervals and close preview sockets
299
+ if (session.previewPingIntervals && session.previewPingIntervals.size > 0) {
300
+ for (const interval of session.previewPingIntervals.values()) {
301
+ clearInterval(interval);
302
+ }
303
+ session.previewPingIntervals.clear();
304
+ }
269
305
  // Close all preview sockets
270
306
  if (session.previewSockets && session.previewSockets.size > 0) {
271
307
  for (const ws of Array.from(session.previewSockets)) {
@@ -366,8 +402,27 @@ export class TerminalManager {
366
402
  return false;
367
403
  if (!session.previewSockets)
368
404
  session.previewSockets = new Set();
405
+ if (!session.previewPingIntervals)
406
+ session.previewPingIntervals = new Map();
369
407
  session.previewSockets.add(ws);
370
408
  session.lastActivity = new Date();
409
+ // Setup WebSocket keepalive ping for preview socket (every 30 seconds)
410
+ const pingInterval = setInterval(() => {
411
+ if (ws.readyState === WebSocket.OPEN) {
412
+ try {
413
+ ws.ping();
414
+ }
415
+ catch (e) {
416
+ clearInterval(pingInterval);
417
+ session.previewPingIntervals?.delete(ws);
418
+ }
419
+ }
420
+ else {
421
+ clearInterval(pingInterval);
422
+ session.previewPingIntervals?.delete(ws);
423
+ }
424
+ }, 30000);
425
+ session.previewPingIntervals.set(ws, pingInterval);
371
426
  // On attach, send a snapshot of recent backlog (last 4KB)
372
427
  try {
373
428
  const backlog = session.backlog || '';
@@ -382,9 +437,19 @@ export class TerminalManager {
382
437
  // Ignore messages: preview sockets are read-only
383
438
  });
384
439
  ws.on('close', () => {
440
+ const interval = session.previewPingIntervals?.get(ws);
441
+ if (interval) {
442
+ clearInterval(interval);
443
+ session.previewPingIntervals?.delete(ws);
444
+ }
385
445
  session.previewSockets?.delete(ws);
386
446
  });
387
447
  ws.on('error', () => {
448
+ const interval = session.previewPingIntervals?.get(ws);
449
+ if (interval) {
450
+ clearInterval(interval);
451
+ session.previewPingIntervals?.delete(ws);
452
+ }
388
453
  session.previewSockets?.delete(ws);
389
454
  });
390
455
  return true;
@@ -1,8 +1,2 @@
1
1
  import { Context } from 'hono';
2
- export declare function POST(c: Context): Promise<(Response & import("hono").TypedResponse<{
3
- error: string;
4
- }, 400, "json">) | (Response & import("hono").TypedResponse<{
5
- error: string;
6
- }, 500, "json">) | (Response & import("hono").TypedResponse<{
7
- response: any;
8
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
2
+ export declare function POST(c: Context): Promise<Response>;
@@ -34,7 +34,7 @@ function getGlobalApiKey(provider) {
34
34
  }
35
35
  export async function POST(c) {
36
36
  try {
37
- const { message: userMessage, history, model, context, contextContent, agentPersona, systemPrompt: systemOverride, maxTokens, enableTools, attachedContextFiles } = await c.req.json();
37
+ const { message: userMessage, history, model, context, contextContent, agentPersona, systemPrompt: systemOverride, maxTokens, enableTools, attachedContextFiles, stream } = await c.req.json();
38
38
  if (!userMessage) {
39
39
  return c.json({ error: "Message is required" }, 400);
40
40
  }
@@ -95,7 +95,8 @@ export async function POST(c) {
95
95
  model: model || "openai/gpt-4o",
96
96
  messages: messagesPayload,
97
97
  temperature: 0.7,
98
- max_tokens: typeof maxTokens === 'number' ? Math.max(256, Math.min(4000, Math.floor(maxTokens))) : 2000,
98
+ max_tokens: typeof maxTokens === 'number' ? Math.max(256, Math.min(8000, Math.floor(maxTokens))) : 8000,
99
+ stream: stream === true,
99
100
  };
100
101
  // Add tools if enabled
101
102
  if (tools) {
@@ -124,6 +125,17 @@ export async function POST(c) {
124
125
  console.error("OpenRouter API error:", error);
125
126
  return c.json({ error: "Failed to get AI response" }, 500);
126
127
  }
128
+ // Handle streaming response
129
+ if (stream === true && response.body) {
130
+ return c.newResponse(response.body, {
131
+ status: 200,
132
+ headers: {
133
+ 'Content-Type': 'text/event-stream',
134
+ 'Cache-Control': 'no-cache',
135
+ 'Connection': 'keep-alive',
136
+ },
137
+ });
138
+ }
127
139
  const data = await response.json();
128
140
  const aiMessage = data.choices?.[0]?.message;
129
141
  if (!aiMessage) {
@@ -143,6 +155,7 @@ export async function POST(c) {
143
155
  }))
144
156
  ];
145
157
  // Get final response from AI with tool results
158
+ // Use lower max_tokens for tool result synthesis (typically just needs to summarize)
146
159
  const finalResponse = await fetch("https://openrouter.ai/api/v1/chat/completions", {
147
160
  method: "POST",
148
161
  headers: {
@@ -154,8 +167,8 @@ export async function POST(c) {
154
167
  body: JSON.stringify({
155
168
  model: model || "openai/gpt-4o",
156
169
  messages: messagesWithTools,
157
- temperature: 0.7,
158
- max_tokens: typeof maxTokens === 'number' ? Math.max(256, Math.min(4000, Math.floor(maxTokens))) : 2000,
170
+ temperature: 0.5, // Lower temperature for more consistent tool result summaries
171
+ max_tokens: 2000, // Reduced from 8000 - tool summaries don't need to be as long
159
172
  }),
160
173
  });
161
174
  if (!finalResponse.ok) {
@@ -179,9 +192,9 @@ export async function POST(c) {
179
192
  }
180
193
  }
181
194
  async function executeToolCalls(toolCalls) {
182
- const results = [];
183
195
  const storage = new FileStorageAdapter();
184
- for (const toolCall of toolCalls) {
196
+ // Execute all tool calls in parallel for better performance
197
+ const resultPromises = toolCalls.map(async (toolCall) => {
185
198
  try {
186
199
  // Handle both function calling formats
187
200
  const functionName = toolCall.function?.name || toolCall.name;
@@ -199,7 +212,7 @@ async function executeToolCalls(toolCalls) {
199
212
  else if (functionName === 'knowledge_documents') {
200
213
  result = await executeKnowledgeToolDirect(functionArgs);
201
214
  }
202
- results.push({
215
+ return {
203
216
  tool_call_id: toolCall.id,
204
217
  content: JSON.stringify({
205
218
  success: result.success,
@@ -207,19 +220,19 @@ async function executeToolCalls(toolCalls) {
207
220
  message: result.message,
208
221
  error: result.error
209
222
  })
210
- });
223
+ };
211
224
  }
212
225
  catch (error) {
213
- results.push({
226
+ return {
214
227
  tool_call_id: toolCall.id,
215
228
  content: JSON.stringify({
216
229
  success: false,
217
230
  error: error instanceof Error ? error.message : 'Unknown error'
218
231
  })
219
- });
232
+ };
220
233
  }
221
- }
222
- return results;
234
+ });
235
+ return Promise.all(resultPromises);
223
236
  }
224
237
  async function executeProposalsToolDirect(args, storage) {
225
238
  const { operation, id, filters, proposal } = args;
@@ -1,5 +1,5 @@
1
1
  import { Hono } from 'hono';
2
- import { getRepoStatus, listBranches, createBranch, deleteBranch, pushCurrent, pullCurrent, listWorktrees, addWorktree, removeWorktree, commitInWorktree, pushWorktree, pullWorktree, } from '../../../../lib/git.js';
2
+ import { getRepoStatus, listBranches, createBranch, deleteBranch, switchBranch, mergeBranch, pushCurrent, pullCurrent, listWorktrees, addWorktree, removeWorktree, commitInWorktree, pushWorktree, pullWorktree, checkRemoteAuth, getCredentialConfig, storeCredentials, } from '../../../../lib/git.js';
3
3
  import { loadGitSettings, setDefaultWorktreePath } from '../../../../lib/git-settings.js';
4
4
  const app = new Hono();
5
5
  // Settings
@@ -38,6 +38,42 @@ app.get('/status', async (c) => {
38
38
  return c.json({ success: false, error: { message: e.message } }, 500);
39
39
  }
40
40
  });
41
+ // Auth Status
42
+ app.get('/auth-status', async (c) => {
43
+ try {
44
+ const authStatus = await checkRemoteAuth();
45
+ return c.json({ success: true, data: authStatus });
46
+ }
47
+ catch (e) {
48
+ return c.json({ success: false, error: { message: e.message } }, 500);
49
+ }
50
+ });
51
+ // Credential Config
52
+ app.get('/credential-config', async (c) => {
53
+ try {
54
+ const config = await getCredentialConfig();
55
+ return c.json({ success: true, data: config });
56
+ }
57
+ catch (e) {
58
+ return c.json({ success: false, error: { message: e.message } }, 500);
59
+ }
60
+ });
61
+ // Store Credentials
62
+ app.post('/credentials', async (c) => {
63
+ try {
64
+ const body = await c.req.json();
65
+ const username = String(body?.username || '');
66
+ const password = String(body?.password || '');
67
+ if (!username || !password) {
68
+ return c.json({ success: false, error: { message: 'Username and password are required' } }, 400);
69
+ }
70
+ await storeCredentials(username, password);
71
+ return c.json({ success: true, data: { message: 'Credentials stored successfully' } });
72
+ }
73
+ catch (e) {
74
+ return c.json({ success: false, error: { message: e.message } }, 500);
75
+ }
76
+ });
41
77
  // Branches
42
78
  app.get('/branches', async (c) => {
43
79
  try {
@@ -72,6 +108,32 @@ app.delete('/branches/:branch', async (c) => {
72
108
  return c.json({ success: false, error: { message: e.message } }, 500);
73
109
  }
74
110
  });
111
+ app.post('/branches/:branch/switch', async (c) => {
112
+ try {
113
+ const name = c.req.param('branch');
114
+ await switchBranch(name);
115
+ return c.json({ success: true, data: { switched: name } });
116
+ }
117
+ catch (e) {
118
+ return c.json({ success: false, error: { message: e.message } }, 500);
119
+ }
120
+ });
121
+ app.post('/branches/:branch/merge', async (c) => {
122
+ try {
123
+ const name = c.req.param('branch');
124
+ let strategy;
125
+ try {
126
+ strategy = await c.req.json();
127
+ }
128
+ catch { }
129
+ const mergeStrategy = strategy?.strategy === 'squash' || strategy?.strategy === 'rebase' ? strategy.strategy : 'merge';
130
+ const result = await mergeBranch(name, mergeStrategy);
131
+ return c.json({ success: true, data: { branch: name, strategy: mergeStrategy, result } });
132
+ }
133
+ catch (e) {
134
+ return c.json({ success: false, error: { message: e.message } }, 500);
135
+ }
136
+ });
75
137
  // Commits (in current repo root)
76
138
  app.post('/commits', async (c) => {
77
139
  try {
@@ -0,0 +1,133 @@
1
+ import { Context } from 'hono';
2
+ import { ScheduledJobStatus } from '@lovelybunch/types';
3
+ export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
4
+ success: false;
5
+ error: {
6
+ code: string;
7
+ message: string;
8
+ };
9
+ }, 404, "json">) | (Response & import("hono").TypedResponse<{
10
+ success: true;
11
+ data: {
12
+ id: string;
13
+ name: string;
14
+ description?: string;
15
+ prompt: string;
16
+ model: string;
17
+ status: ScheduledJobStatus;
18
+ schedule: {
19
+ type: "cron";
20
+ expression: string;
21
+ timezone?: string;
22
+ description?: string;
23
+ } | {
24
+ type: "interval";
25
+ hours: number;
26
+ daysOfWeek: import("@lovelybunch/types").DayOfWeek[];
27
+ timezone?: string;
28
+ anchorHour?: number;
29
+ };
30
+ metadata: {
31
+ createdAt: string;
32
+ updatedAt: string;
33
+ lastRunAt?: string;
34
+ nextRunAt?: string;
35
+ };
36
+ runs: {
37
+ id: string;
38
+ jobId: string;
39
+ trigger: import("@lovelybunch/types").ScheduledJobTrigger;
40
+ status: import("@lovelybunch/types").ScheduledJobRunStatus;
41
+ startedAt: string;
42
+ finishedAt?: string;
43
+ outputPath?: string;
44
+ summary?: string;
45
+ error?: string;
46
+ cliCommand?: string;
47
+ }[];
48
+ tags?: string[];
49
+ contextPaths?: string[];
50
+ };
51
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
52
+ success: false;
53
+ error: {
54
+ code: string;
55
+ message: any;
56
+ };
57
+ }, 500, "json">)>;
58
+ export declare function PATCH(c: Context): Promise<(Response & import("hono").TypedResponse<{
59
+ success: false;
60
+ error: {
61
+ code: string;
62
+ message: string;
63
+ };
64
+ }, 404, "json">) | (Response & import("hono").TypedResponse<{
65
+ success: false;
66
+ error: {
67
+ code: string;
68
+ message: any;
69
+ };
70
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
71
+ success: true;
72
+ data: {
73
+ id: string;
74
+ name: string;
75
+ description?: string;
76
+ prompt: string;
77
+ model: string;
78
+ status: ScheduledJobStatus;
79
+ schedule: {
80
+ type: "cron";
81
+ expression: string;
82
+ timezone?: string;
83
+ description?: string;
84
+ } | {
85
+ type: "interval";
86
+ hours: number;
87
+ daysOfWeek: import("@lovelybunch/types").DayOfWeek[];
88
+ timezone?: string;
89
+ anchorHour?: number;
90
+ };
91
+ metadata: {
92
+ createdAt: string;
93
+ updatedAt: string;
94
+ lastRunAt?: string;
95
+ nextRunAt?: string;
96
+ };
97
+ runs: {
98
+ id: string;
99
+ jobId: string;
100
+ trigger: import("@lovelybunch/types").ScheduledJobTrigger;
101
+ status: import("@lovelybunch/types").ScheduledJobRunStatus;
102
+ startedAt: string;
103
+ finishedAt?: string;
104
+ outputPath?: string;
105
+ summary?: string;
106
+ error?: string;
107
+ cliCommand?: string;
108
+ }[];
109
+ tags?: string[];
110
+ contextPaths?: string[];
111
+ };
112
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
113
+ success: false;
114
+ error: {
115
+ code: string;
116
+ message: any;
117
+ };
118
+ }, 500, "json">)>;
119
+ export declare function DELETE(c: Context): Promise<(Response & import("hono").TypedResponse<{
120
+ success: false;
121
+ error: {
122
+ code: string;
123
+ message: string;
124
+ };
125
+ }, 404, "json">) | (Response & import("hono").TypedResponse<{
126
+ success: true;
127
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
128
+ success: false;
129
+ error: {
130
+ code: string;
131
+ message: any;
132
+ };
133
+ }, 500, "json">)>;
@@ -0,0 +1,135 @@
1
+ import { JobStore } from '../../../../../lib/jobs/job-store.js';
2
+ import { getGlobalJobScheduler } from '../../../../../lib/jobs/global-job-scheduler.js';
3
+ import { normalizeSchedule, normalizeStatus, } from '../route.js';
4
+ const store = new JobStore();
5
+ const scheduler = getGlobalJobScheduler();
6
+ function normalizeScheduleUpdate(current, incoming) {
7
+ if (!incoming)
8
+ return current;
9
+ const scheduleInput = {
10
+ ...(current.type === 'interval' ? current : {}),
11
+ ...(current.type === 'cron' ? current : {}),
12
+ ...incoming,
13
+ type: incoming?.type ?? current.type
14
+ };
15
+ return normalizeSchedule(scheduleInput);
16
+ }
17
+ function normalizeStatusUpdate(current, status) {
18
+ if (status === undefined)
19
+ return current;
20
+ return normalizeStatus(status);
21
+ }
22
+ export async function GET(c) {
23
+ try {
24
+ const id = c.req.param('id');
25
+ const job = await store.getJob(id);
26
+ if (!job) {
27
+ return c.json({
28
+ success: false,
29
+ error: {
30
+ code: 'JOB_NOT_FOUND',
31
+ message: `Job ${id} not found`
32
+ }
33
+ }, 404);
34
+ }
35
+ return c.json({ success: true, data: job });
36
+ }
37
+ catch (error) {
38
+ console.error('Failed to fetch job:', error);
39
+ return c.json({
40
+ success: false,
41
+ error: {
42
+ code: 'GET_JOB_ERROR',
43
+ message: error?.message ?? 'Unknown error retrieving job'
44
+ }
45
+ }, 500);
46
+ }
47
+ }
48
+ export async function PATCH(c) {
49
+ try {
50
+ const id = c.req.param('id');
51
+ const existing = await store.getJob(id);
52
+ if (!existing) {
53
+ return c.json({
54
+ success: false,
55
+ error: {
56
+ code: 'JOB_NOT_FOUND',
57
+ message: `Job ${id} not found`
58
+ }
59
+ }, 404);
60
+ }
61
+ const body = await c.req.json();
62
+ let schedule;
63
+ try {
64
+ schedule = normalizeScheduleUpdate(existing.schedule, body.schedule);
65
+ }
66
+ catch (validationError) {
67
+ return c.json({
68
+ success: false,
69
+ error: {
70
+ code: 'INVALID_SCHEDULE',
71
+ message: validationError?.message || 'Invalid schedule definition.'
72
+ }
73
+ }, 400);
74
+ }
75
+ const updated = {
76
+ ...existing,
77
+ name: typeof body.name === 'string' ? body.name : existing.name,
78
+ description: typeof body.description === 'string' ? body.description : existing.description,
79
+ prompt: typeof body.prompt === 'string' ? body.prompt : existing.prompt,
80
+ model: typeof body.model === 'string' ? body.model : existing.model,
81
+ status: normalizeStatusUpdate(existing.status, body.status),
82
+ schedule,
83
+ tags: Array.isArray(body.tags) ? body.tags : existing.tags,
84
+ contextPaths: Array.isArray(body.contextPaths) ? body.contextPaths : existing.contextPaths,
85
+ metadata: {
86
+ ...existing.metadata,
87
+ updatedAt: new Date()
88
+ }
89
+ };
90
+ await store.saveJob(updated);
91
+ await scheduler.refresh(updated.id);
92
+ const reloaded = await store.getJob(updated.id);
93
+ return c.json({
94
+ success: true,
95
+ data: reloaded ?? updated
96
+ });
97
+ }
98
+ catch (error) {
99
+ console.error('Failed to update job:', error);
100
+ return c.json({
101
+ success: false,
102
+ error: {
103
+ code: 'UPDATE_JOB_ERROR',
104
+ message: error?.message ?? 'Unknown error updating job'
105
+ }
106
+ }, 500);
107
+ }
108
+ }
109
+ export async function DELETE(c) {
110
+ try {
111
+ const id = c.req.param('id');
112
+ const deleted = await store.deleteJob(id);
113
+ if (!deleted) {
114
+ return c.json({
115
+ success: false,
116
+ error: {
117
+ code: 'JOB_NOT_FOUND',
118
+ message: `Job ${id} not found`
119
+ }
120
+ }, 404);
121
+ }
122
+ await scheduler.unregister(id);
123
+ return c.json({ success: true });
124
+ }
125
+ catch (error) {
126
+ console.error('Failed to delete job:', error);
127
+ return c.json({
128
+ success: false,
129
+ error: {
130
+ code: 'DELETE_JOB_ERROR',
131
+ message: error?.message ?? 'Unknown error deleting job'
132
+ }
133
+ }, 500);
134
+ }
135
+ }
@@ -0,0 +1,31 @@
1
+ import { Context } from 'hono';
2
+ export declare function POST(c: Context): Promise<(Response & import("hono").TypedResponse<{
3
+ success: false;
4
+ error: {
5
+ code: string;
6
+ message: string;
7
+ };
8
+ }, 404, "json">) | (Response & import("hono").TypedResponse<{
9
+ success: true;
10
+ data: {
11
+ jobId: string;
12
+ run: {
13
+ id: string;
14
+ jobId: string;
15
+ trigger: import("@lovelybunch/types").ScheduledJobTrigger;
16
+ status: import("@lovelybunch/types").ScheduledJobRunStatus;
17
+ startedAt: string;
18
+ finishedAt?: string;
19
+ outputPath?: string;
20
+ summary?: string;
21
+ error?: string;
22
+ cliCommand?: string;
23
+ };
24
+ };
25
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
26
+ success: false;
27
+ error: {
28
+ code: string;
29
+ message: any;
30
+ };
31
+ }, 500, "json">)>;
@@ -0,0 +1,37 @@
1
+ import { JobStore } from '../../../../../../lib/jobs/job-store.js';
2
+ import { getGlobalJobScheduler } from '../../../../../../lib/jobs/global-job-scheduler.js';
3
+ const store = new JobStore();
4
+ const scheduler = getGlobalJobScheduler();
5
+ export async function POST(c) {
6
+ try {
7
+ const id = c.req.param('id');
8
+ const job = await store.getJob(id);
9
+ if (!job) {
10
+ return c.json({
11
+ success: false,
12
+ error: {
13
+ code: 'JOB_NOT_FOUND',
14
+ message: `Job ${id} not found`
15
+ }
16
+ }, 404);
17
+ }
18
+ const run = await scheduler.runNow(id, 'manual');
19
+ return c.json({
20
+ success: true,
21
+ data: {
22
+ jobId: id,
23
+ run
24
+ }
25
+ });
26
+ }
27
+ catch (error) {
28
+ console.error('Failed to trigger job run:', error);
29
+ return c.json({
30
+ success: false,
31
+ error: {
32
+ code: 'RUN_JOB_ERROR',
33
+ message: error?.message ?? 'Unknown error running job'
34
+ }
35
+ }, 500);
36
+ }
37
+ }
@@ -0,0 +1,3 @@
1
+ import { Hono } from 'hono';
2
+ declare const jobs: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
3
+ export default jobs;
@@ -0,0 +1,14 @@
1
+ import { Hono } from 'hono';
2
+ import { GET, POST } from './route.js';
3
+ import { GET as getJob, PATCH as patchJob, DELETE as deleteJob } from './[id]/route.js';
4
+ import { POST as runJob } from './[id]/run/route.js';
5
+ import { GET as getStatus } from './status/route.js';
6
+ const jobs = new Hono();
7
+ jobs.get('/', GET);
8
+ jobs.post('/', POST);
9
+ jobs.get('/status', getStatus);
10
+ jobs.get('/:id', getJob);
11
+ jobs.patch('/:id', patchJob);
12
+ jobs.delete('/:id', deleteJob);
13
+ jobs.post('/:id/run', runJob);
14
+ export default jobs;