workmatic 1.0.0 → 1.0.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.
package/dist/cli.js CHANGED
@@ -1,24 +1,14 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Workmatic CLI
4
- *
5
- * Commands:
6
- * stats <db> Show job statistics
7
- * export <db> [output] Export jobs to CSV
8
- * import <db> <input> Import jobs from CSV
9
- * purge <db> [--status=done] Delete jobs by status
10
- * retry <db> [--status=dead] Retry failed/dead jobs
11
- * pause <db> <queue> Pause a queue (workers stop claiming)
12
- * resume <db> <queue> Resume a paused queue
13
- */
14
- import Database from 'better-sqlite3';
15
- import { createReadStream, createWriteStream } from 'fs';
16
- import { createInterface } from 'readline';
17
- // Parse command line arguments
18
- const args = process.argv.slice(2);
19
- const command = args[0];
2
+
3
+ // src/cli.ts
4
+ import Database from "better-sqlite3";
5
+ import { createReadStream, createWriteStream } from "fs";
6
+ import { createInterface } from "readline";
7
+ import Table from "cli-table3";
8
+ var args = process.argv.slice(2);
9
+ var command = args[0];
20
10
  function printUsage() {
21
- console.log(`
11
+ console.log(`
22
12
  Workmatic CLI - Job Queue Management
23
13
 
24
14
  Usage:
@@ -52,200 +42,219 @@ Examples:
52
42
  workmatic resume ./jobs.db emails
53
43
  `);
54
44
  }
55
- function parseOptions(args) {
56
- const options = {};
57
- for (const arg of args) {
58
- if (arg.startsWith('--')) {
59
- const [key, value] = arg.slice(2).split('=');
60
- options[key] = value ?? 'true';
61
- }
45
+ function parseOptions(args2) {
46
+ const options = {};
47
+ for (const arg of args2) {
48
+ if (arg.startsWith("--")) {
49
+ const [key, value] = arg.slice(2).split("=");
50
+ options[key] = value ?? "true";
62
51
  }
63
- return options;
52
+ }
53
+ return options;
64
54
  }
65
- function getPositionalArgs(args) {
66
- return args.filter(arg => !arg.startsWith('--'));
55
+ function getPositionalArgs(args2) {
56
+ return args2.filter((arg) => !arg.startsWith("--"));
67
57
  }
68
- // CSV helpers
69
58
  function escapeCSV(value) {
70
- if (value === null)
71
- return '';
72
- const str = String(value);
73
- if (str.includes(',') || str.includes('"') || str.includes('\n')) {
74
- return `"${str.replace(/"/g, '""')}"`;
75
- }
76
- return str;
59
+ if (value === null) return "";
60
+ const str = String(value);
61
+ if (str.includes(",") || str.includes('"') || str.includes("\n")) {
62
+ return `"${str.replace(/"/g, '""')}"`;
63
+ }
64
+ return str;
77
65
  }
78
66
  function parseCSVLine(line) {
79
- const result = [];
80
- let current = '';
81
- let inQuotes = false;
82
- for (let i = 0; i < line.length; i++) {
83
- const char = line[i];
84
- if (inQuotes) {
85
- if (char === '"') {
86
- if (line[i + 1] === '"') {
87
- current += '"';
88
- i++;
89
- }
90
- else {
91
- inQuotes = false;
92
- }
93
- }
94
- else {
95
- current += char;
96
- }
97
- }
98
- else {
99
- if (char === '"') {
100
- inQuotes = true;
101
- }
102
- else if (char === ',') {
103
- result.push(current);
104
- current = '';
105
- }
106
- else {
107
- current += char;
108
- }
67
+ const result = [];
68
+ let current = "";
69
+ let inQuotes = false;
70
+ for (let i = 0; i < line.length; i++) {
71
+ const char = line[i];
72
+ if (inQuotes) {
73
+ if (char === '"') {
74
+ if (line[i + 1] === '"') {
75
+ current += '"';
76
+ i++;
77
+ } else {
78
+ inQuotes = false;
109
79
  }
80
+ } else {
81
+ current += char;
82
+ }
83
+ } else {
84
+ if (char === '"') {
85
+ inQuotes = true;
86
+ } else if (char === ",") {
87
+ result.push(current);
88
+ current = "";
89
+ } else {
90
+ current += char;
91
+ }
110
92
  }
111
- result.push(current);
112
- return result;
93
+ }
94
+ result.push(current);
95
+ return result;
113
96
  }
114
- // Commands
115
97
  async function cmdStats(dbPath) {
116
- const db = new Database(dbPath, { readonly: true });
117
- try {
118
- const stats = db.prepare(`
98
+ const db = new Database(dbPath, { readonly: true });
99
+ try {
100
+ const stats = db.prepare(`
119
101
  SELECT status, COUNT(*) as count
120
102
  FROM workmatic_jobs
121
103
  GROUP BY status
122
104
  `).all();
123
- const queues = db.prepare(`
105
+ const queues = db.prepare(`
124
106
  SELECT queue, COUNT(*) as count
125
107
  FROM workmatic_jobs
126
108
  GROUP BY queue
127
109
  `).all();
128
- const total = stats.reduce((sum, s) => sum + s.count, 0);
129
- console.log('\n📊 Job Statistics\n');
130
- console.log('By Status:');
131
- console.log('─'.repeat(30));
132
- const statusOrder = ['ready', 'running', 'done', 'failed', 'dead'];
133
- const statusEmoji = {
134
- ready: '⏳',
135
- running: '▶️ ',
136
- done: '✅',
137
- failed: '⚠️ ',
138
- dead: '💀',
139
- };
140
- for (const status of statusOrder) {
141
- const stat = stats.find(s => s.status === status);
142
- const count = stat?.count ?? 0;
143
- const emoji = statusEmoji[status] || ' ';
144
- console.log(` ${emoji} ${status.padEnd(10)} ${count.toLocaleString().padStart(8)}`);
145
- }
146
- console.log('─'.repeat(30));
147
- console.log(` Total: ${total.toLocaleString().padStart(8)}`);
148
- if (queues.length > 0) {
149
- console.log('\nBy Queue:');
150
- console.log('─'.repeat(30));
151
- for (const q of queues) {
152
- console.log(` ${q.queue.padEnd(15)} ${q.count.toLocaleString().padStart(8)}`);
153
- }
154
- }
155
- console.log();
110
+ const total = stats.reduce((sum, s) => sum + s.count, 0);
111
+ console.log("\n\u{1F4CA} Job Statistics\n");
112
+ const statusOrder = ["ready", "running", "done", "failed", "dead"];
113
+ const statusEmoji = {
114
+ ready: "\u23F3",
115
+ running: "\u25B6\uFE0F",
116
+ done: "\u2705",
117
+ failed: "\u26A0\uFE0F",
118
+ dead: "\u{1F480}"
119
+ };
120
+ const statusTable = new Table({
121
+ head: ["", "Status", "Count"],
122
+ style: { head: ["cyan"], border: ["gray"] },
123
+ colAligns: ["left", "left", "right"]
124
+ });
125
+ for (const status of statusOrder) {
126
+ const stat = stats.find((s) => s.status === status);
127
+ const count = stat?.count ?? 0;
128
+ const emoji = statusEmoji[status] || "";
129
+ statusTable.push([emoji, status, count.toLocaleString()]);
156
130
  }
157
- finally {
158
- db.close();
131
+ statusTable.push([{ colSpan: 2, content: "Total", hAlign: "right" }, total.toLocaleString()]);
132
+ console.log(statusTable.toString());
133
+ if (queues.length > 0) {
134
+ console.log("\n\u{1F4CB} By Queue\n");
135
+ const queueTable = new Table({
136
+ head: ["Queue", "Jobs"],
137
+ style: { head: ["cyan"], border: ["gray"] },
138
+ colAligns: ["left", "right"]
139
+ });
140
+ for (const q of queues) {
141
+ queueTable.push([q.queue, q.count.toLocaleString()]);
142
+ }
143
+ console.log(queueTable.toString());
159
144
  }
145
+ console.log();
146
+ } finally {
147
+ db.close();
148
+ }
160
149
  }
161
150
  async function cmdList(dbPath, options) {
162
- const db = new Database(dbPath, { readonly: true });
163
- try {
164
- let query = 'SELECT public_id, queue, status, priority, attempts, max_attempts, created_at, last_error FROM workmatic_jobs WHERE 1=1';
165
- const params = [];
166
- if (options.status) {
167
- query += ' AND status = ?';
168
- params.push(options.status);
169
- }
170
- if (options.queue) {
171
- query += ' AND queue = ?';
172
- params.push(options.queue);
173
- }
174
- query += ' ORDER BY created_at DESC LIMIT ?';
175
- params.push(parseInt(options.limit || '100', 10));
176
- const jobs = db.prepare(query).all(...params);
177
- if (jobs.length === 0) {
178
- console.log('No jobs found.');
179
- return;
180
- }
181
- console.log('\nID Queue Status Pri Attempts Created');
182
- console.log('─'.repeat(85));
183
- for (const job of jobs) {
184
- const id = job.public_id.slice(0, 21);
185
- const queue = job.queue.slice(0, 15).padEnd(15);
186
- const status = job.status.padEnd(9);
187
- const pri = String(job.priority).padStart(3);
188
- const attempts = `${job.attempts}/${job.max_attempts}`.padStart(8);
189
- const created = new Date(job.created_at).toISOString().slice(0, 19);
190
- console.log(`${id} ${queue} ${status} ${pri} ${attempts} ${created}`);
191
- }
192
- console.log(`\nShowing ${jobs.length} jobs\n`);
151
+ const db = new Database(dbPath, { readonly: true });
152
+ try {
153
+ let query = "SELECT public_id, queue, status, priority, attempts, max_attempts, created_at, last_error FROM workmatic_jobs WHERE 1=1";
154
+ const params = [];
155
+ if (options.status) {
156
+ query += " AND status = ?";
157
+ params.push(options.status);
158
+ }
159
+ if (options.queue) {
160
+ query += " AND queue = ?";
161
+ params.push(options.queue);
162
+ }
163
+ query += " ORDER BY created_at DESC LIMIT ?";
164
+ params.push(parseInt(options.limit || "100", 10));
165
+ const jobs = db.prepare(query).all(...params);
166
+ if (jobs.length === 0) {
167
+ console.log("No jobs found.");
168
+ return;
193
169
  }
194
- finally {
195
- db.close();
170
+ const statusEmoji = {
171
+ ready: "\u23F3",
172
+ running: "\u25B6\uFE0F",
173
+ done: "\u2705",
174
+ failed: "\u26A0\uFE0F",
175
+ dead: "\u{1F480}"
176
+ };
177
+ const table = new Table({
178
+ head: ["ID", "Queue", "Status", "Pri", "Attempts", "Created"],
179
+ style: { head: ["cyan"], border: ["gray"] },
180
+ colAligns: ["left", "left", "left", "right", "center", "left"],
181
+ colWidths: [24, 16, 12, 5, 10, 21],
182
+ wordWrap: true
183
+ });
184
+ for (const job of jobs) {
185
+ const emoji = statusEmoji[job.status] || "";
186
+ table.push([
187
+ job.public_id.slice(0, 21),
188
+ job.queue.slice(0, 14),
189
+ `${emoji} ${job.status}`,
190
+ job.priority,
191
+ `${job.attempts}/${job.max_attempts}`,
192
+ new Date(job.created_at).toISOString().slice(0, 19)
193
+ ]);
196
194
  }
195
+ console.log();
196
+ console.log(table.toString());
197
+ console.log(`
198
+ Showing ${jobs.length} jobs
199
+ `);
200
+ } finally {
201
+ db.close();
202
+ }
197
203
  }
198
204
  async function cmdExport(dbPath, outputPath, options) {
199
- const db = new Database(dbPath, { readonly: true });
200
- try {
201
- let query = 'SELECT * FROM workmatic_jobs WHERE 1=1';
202
- const params = [];
203
- if (options.status) {
204
- query += ' AND status = ?';
205
- params.push(options.status);
206
- }
207
- if (options.queue) {
208
- query += ' AND queue = ?';
209
- params.push(options.queue);
210
- }
211
- query += ' ORDER BY id';
212
- const jobs = db.prepare(query).all(...params);
213
- const output = outputPath ? createWriteStream(outputPath) : process.stdout;
214
- // Header
215
- const columns = [
216
- 'public_id', 'queue', 'payload', 'status', 'priority',
217
- 'run_at', 'attempts', 'max_attempts', 'lease_until',
218
- 'created_at', 'updated_at', 'last_error'
219
- ];
220
- if (outputPath) {
221
- output.write(columns.join(',') + '\n');
222
- }
223
- else {
224
- console.log(columns.join(','));
225
- }
226
- // Rows
227
- for (const job of jobs) {
228
- const row = columns.map(col => escapeCSV(job[col]));
229
- if (outputPath) {
230
- output.write(row.join(',') + '\n');
231
- }
232
- else {
233
- console.log(row.join(','));
234
- }
235
- }
236
- if (outputPath) {
237
- output.end();
238
- console.error(`✅ Exported ${jobs.length} jobs to ${outputPath}`);
239
- }
205
+ const db = new Database(dbPath, { readonly: true });
206
+ try {
207
+ let query = "SELECT * FROM workmatic_jobs WHERE 1=1";
208
+ const params = [];
209
+ if (options.status) {
210
+ query += " AND status = ?";
211
+ params.push(options.status);
212
+ }
213
+ if (options.queue) {
214
+ query += " AND queue = ?";
215
+ params.push(options.queue);
216
+ }
217
+ query += " ORDER BY id";
218
+ const jobs = db.prepare(query).all(...params);
219
+ const output = outputPath ? createWriteStream(outputPath) : process.stdout;
220
+ const columns = [
221
+ "public_id",
222
+ "queue",
223
+ "payload",
224
+ "status",
225
+ "priority",
226
+ "run_at",
227
+ "attempts",
228
+ "max_attempts",
229
+ "lease_until",
230
+ "created_at",
231
+ "updated_at",
232
+ "last_error"
233
+ ];
234
+ if (outputPath) {
235
+ output.write(columns.join(",") + "\n");
236
+ } else {
237
+ console.log(columns.join(","));
240
238
  }
241
- finally {
242
- db.close();
239
+ for (const job of jobs) {
240
+ const row = columns.map((col) => escapeCSV(job[col]));
241
+ if (outputPath) {
242
+ output.write(row.join(",") + "\n");
243
+ } else {
244
+ console.log(row.join(","));
245
+ }
243
246
  }
247
+ if (outputPath) {
248
+ output.end();
249
+ console.error(`\u2705 Exported ${jobs.length} jobs to ${outputPath}`);
250
+ }
251
+ } finally {
252
+ db.close();
253
+ }
244
254
  }
245
255
  async function cmdImport(dbPath, inputPath) {
246
- const db = new Database(dbPath);
247
- // Ensure schema exists
248
- db.exec(`
256
+ const db = new Database(dbPath);
257
+ db.exec(`
249
258
  CREATE TABLE IF NOT EXISTS workmatic_jobs (
250
259
  id INTEGER PRIMARY KEY AUTOINCREMENT,
251
260
  public_id TEXT UNIQUE NOT NULL,
@@ -262,79 +271,87 @@ async function cmdImport(dbPath, inputPath) {
262
271
  last_error TEXT
263
272
  )
264
273
  `);
265
- const insert = db.prepare(`
274
+ const insert = db.prepare(`
266
275
  INSERT OR REPLACE INTO workmatic_jobs
267
276
  (public_id, queue, payload, status, priority, run_at, attempts, max_attempts, lease_until, created_at, updated_at, last_error)
268
277
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
269
278
  `);
270
- const rl = createInterface({
271
- input: createReadStream(inputPath),
272
- crlfDelay: Infinity,
279
+ const rl = createInterface({
280
+ input: createReadStream(inputPath),
281
+ crlfDelay: Infinity
282
+ });
283
+ let lineNum = 0;
284
+ let imported = 0;
285
+ let columns = [];
286
+ try {
287
+ const insertMany = db.transaction((rows) => {
288
+ for (const row of rows) {
289
+ insert.run(
290
+ row.public_id,
291
+ row.queue,
292
+ row.payload,
293
+ row.status,
294
+ parseInt(row.priority, 10) || 0,
295
+ parseInt(row.run_at, 10) || Date.now(),
296
+ parseInt(row.attempts, 10) || 0,
297
+ parseInt(row.max_attempts, 10) || 3,
298
+ parseInt(row.lease_until, 10) || 0,
299
+ parseInt(row.created_at, 10) || Date.now(),
300
+ parseInt(row.updated_at, 10) || Date.now(),
301
+ row.last_error || null
302
+ );
303
+ imported++;
304
+ }
273
305
  });
274
- let lineNum = 0;
275
- let imported = 0;
276
- let columns = [];
277
- try {
278
- const insertMany = db.transaction((rows) => {
279
- for (const row of rows) {
280
- insert.run(row.public_id, row.queue, row.payload, row.status, parseInt(row.priority, 10) || 0, parseInt(row.run_at, 10) || Date.now(), parseInt(row.attempts, 10) || 0, parseInt(row.max_attempts, 10) || 3, parseInt(row.lease_until, 10) || 0, parseInt(row.created_at, 10) || Date.now(), parseInt(row.updated_at, 10) || Date.now(), row.last_error || null);
281
- imported++;
282
- }
283
- });
284
- const batch = [];
285
- for await (const line of rl) {
286
- lineNum++;
287
- if (lineNum === 1) {
288
- // Header row
289
- columns = parseCSVLine(line);
290
- continue;
291
- }
292
- if (!line.trim())
293
- continue;
294
- const values = parseCSVLine(line);
295
- const row = {};
296
- for (let i = 0; i < columns.length; i++) {
297
- row[columns[i]] = values[i] ?? '';
298
- }
299
- batch.push(row);
300
- // Batch insert every 1000 rows
301
- if (batch.length >= 1000) {
302
- insertMany(batch);
303
- batch.length = 0;
304
- process.stderr.write(`\rImported ${imported} jobs...`);
305
- }
306
- }
307
- // Insert remaining
308
- if (batch.length > 0) {
309
- insertMany(batch);
310
- }
311
- console.log(`\n✅ Imported ${imported} jobs from ${inputPath}`);
306
+ const batch = [];
307
+ for await (const line of rl) {
308
+ lineNum++;
309
+ if (lineNum === 1) {
310
+ columns = parseCSVLine(line);
311
+ continue;
312
+ }
313
+ if (!line.trim()) continue;
314
+ const values = parseCSVLine(line);
315
+ const row = {};
316
+ for (let i = 0; i < columns.length; i++) {
317
+ row[columns[i]] = values[i] ?? "";
318
+ }
319
+ batch.push(row);
320
+ if (batch.length >= 1e3) {
321
+ insertMany(batch);
322
+ batch.length = 0;
323
+ process.stderr.write(`\rImported ${imported} jobs...`);
324
+ }
312
325
  }
313
- finally {
314
- db.close();
326
+ if (batch.length > 0) {
327
+ insertMany(batch);
315
328
  }
329
+ console.log(`
330
+ \u2705 Imported ${imported} jobs from ${inputPath}`);
331
+ } finally {
332
+ db.close();
333
+ }
316
334
  }
317
335
  async function cmdPurge(dbPath, options) {
318
- if (!options.status) {
319
- console.error('Error: --status is required for purge command');
320
- console.error('Example: workmatic purge ./jobs.db --status=done');
321
- process.exit(1);
322
- }
323
- const db = new Database(dbPath);
324
- try {
325
- const result = db.prepare('DELETE FROM workmatic_jobs WHERE status = ?').run(options.status);
326
- console.log(`🗑️ Deleted ${result.changes} jobs with status '${options.status}'`);
327
- }
328
- finally {
329
- db.close();
330
- }
336
+ if (!options.status) {
337
+ console.error("Error: --status is required for purge command");
338
+ console.error("Example: workmatic purge ./jobs.db --status=done");
339
+ process.exit(1);
340
+ }
341
+ const db = new Database(dbPath);
342
+ try {
343
+ const result = db.prepare("DELETE FROM workmatic_jobs WHERE status = ?").run(options.status);
344
+ console.log(`\u{1F5D1}\uFE0F Deleted ${result.changes} jobs with status '${options.status}'`);
345
+ } finally {
346
+ db.close();
347
+ }
331
348
  }
332
349
  async function cmdRetry(dbPath, options) {
333
- const status = options.status || 'dead';
334
- const db = new Database(dbPath);
335
- try {
336
- const now = Date.now();
337
- const result = db.prepare(`
350
+ const status = options.status || "dead";
351
+ const db = new Database(dbPath);
352
+ try {
353
+ const now = Date.now();
354
+ const result = db.prepare(`
338
355
  UPDATE workmatic_jobs
339
356
  SET status = 'ready',
340
357
  attempts = 0,
@@ -343,163 +360,177 @@ async function cmdRetry(dbPath, options) {
343
360
  updated_at = ?
344
361
  WHERE status = ?
345
362
  `).run(now, now, status);
346
- console.log(`🔄 Reset ${result.changes} jobs from '${status}' to 'ready'`);
347
- }
348
- finally {
349
- db.close();
350
- }
363
+ console.log(`\u{1F504} Reset ${result.changes} jobs from '${status}' to 'ready'`);
364
+ } finally {
365
+ db.close();
366
+ }
351
367
  }
352
368
  async function cmdPause(dbPath, queueName) {
353
- const db = new Database(dbPath);
354
- try {
355
- // Ensure settings table exists
356
- db.exec(`
369
+ const db = new Database(dbPath);
370
+ try {
371
+ db.exec(`
357
372
  CREATE TABLE IF NOT EXISTS workmatic_settings (
358
373
  queue TEXT PRIMARY KEY,
359
374
  paused INTEGER NOT NULL DEFAULT 0,
360
375
  updated_at INTEGER NOT NULL
361
376
  )
362
377
  `);
363
- const now = Date.now();
364
- db.prepare(`
378
+ const now = Date.now();
379
+ db.prepare(`
365
380
  INSERT INTO workmatic_settings (queue, paused, updated_at)
366
381
  VALUES (?, 1, ?)
367
382
  ON CONFLICT(queue) DO UPDATE SET paused = 1, updated_at = ?
368
383
  `).run(queueName, now, now);
369
- console.log(`⏸️ Paused queue '${queueName}'`);
370
- console.log(` Running workers will stop claiming new jobs.`);
371
- }
372
- finally {
373
- db.close();
374
- }
384
+ console.log(`\u23F8\uFE0F Paused queue '${queueName}'`);
385
+ console.log(` Running workers will stop claiming new jobs.`);
386
+ } finally {
387
+ db.close();
388
+ }
375
389
  }
376
390
  async function cmdResume(dbPath, queueName) {
377
- const db = new Database(dbPath);
378
- try {
379
- // Ensure settings table exists
380
- db.exec(`
391
+ const db = new Database(dbPath);
392
+ try {
393
+ db.exec(`
381
394
  CREATE TABLE IF NOT EXISTS workmatic_settings (
382
395
  queue TEXT PRIMARY KEY,
383
396
  paused INTEGER NOT NULL DEFAULT 0,
384
397
  updated_at INTEGER NOT NULL
385
398
  )
386
399
  `);
387
- const now = Date.now();
388
- db.prepare(`
400
+ const now = Date.now();
401
+ db.prepare(`
389
402
  INSERT INTO workmatic_settings (queue, paused, updated_at)
390
403
  VALUES (?, 0, ?)
391
404
  ON CONFLICT(queue) DO UPDATE SET paused = 0, updated_at = ?
392
405
  `).run(queueName, now, now);
393
- console.log(`▶️ Resumed queue '${queueName}'`);
394
- console.log(` Workers will start claiming jobs again.`);
395
- }
396
- finally {
397
- db.close();
398
- }
406
+ console.log(`\u25B6\uFE0F Resumed queue '${queueName}'`);
407
+ console.log(` Workers will start claiming jobs again.`);
408
+ } finally {
409
+ db.close();
410
+ }
399
411
  }
400
412
  async function cmdQueues(dbPath) {
401
- const db = new Database(dbPath, { readonly: true });
402
- try {
403
- // Get all queues with their job counts and pause state
404
- const queues = db.prepare(`
405
- SELECT
406
- j.queue,
407
- COUNT(*) as total,
408
- SUM(CASE WHEN j.status = 'ready' THEN 1 ELSE 0 END) as ready,
409
- SUM(CASE WHEN j.status = 'running' THEN 1 ELSE 0 END) as running,
410
- COALESCE(s.paused, 0) as paused
411
- FROM workmatic_jobs j
412
- LEFT JOIN workmatic_settings s ON j.queue = s.queue
413
- GROUP BY j.queue
414
- `).all();
415
- if (queues.length === 0) {
416
- console.log('No queues found.');
417
- return;
418
- }
419
- console.log('\n📋 Queues\n');
420
- console.log('Queue Status Ready Running Total');
421
- console.log('─'.repeat(55));
422
- for (const q of queues) {
423
- const name = q.queue.padEnd(16);
424
- const status = q.paused ? '⏸️ PAUSED' : '▶️ ACTIVE';
425
- const ready = String(q.ready).padStart(5);
426
- const running = String(q.running).padStart(8);
427
- const total = String(q.total).padStart(6);
428
- console.log(`${name} ${status} ${ready} ${running} ${total}`);
429
- }
430
- console.log();
413
+ const db = new Database(dbPath, { readonly: true });
414
+ try {
415
+ const settingsExists = db.prepare(`
416
+ SELECT name FROM sqlite_master WHERE type='table' AND name='workmatic_settings'
417
+ `).get();
418
+ let queues;
419
+ if (settingsExists) {
420
+ queues = db.prepare(`
421
+ SELECT
422
+ j.queue,
423
+ COUNT(*) as total,
424
+ SUM(CASE WHEN j.status = 'ready' THEN 1 ELSE 0 END) as ready,
425
+ SUM(CASE WHEN j.status = 'running' THEN 1 ELSE 0 END) as running,
426
+ COALESCE(s.paused, 0) as paused
427
+ FROM workmatic_jobs j
428
+ LEFT JOIN workmatic_settings s ON j.queue = s.queue
429
+ GROUP BY j.queue
430
+ `).all();
431
+ } else {
432
+ queues = db.prepare(`
433
+ SELECT
434
+ queue,
435
+ COUNT(*) as total,
436
+ SUM(CASE WHEN status = 'ready' THEN 1 ELSE 0 END) as ready,
437
+ SUM(CASE WHEN status = 'running' THEN 1 ELSE 0 END) as running,
438
+ 0 as paused
439
+ FROM workmatic_jobs
440
+ GROUP BY queue
441
+ `).all();
431
442
  }
432
- finally {
433
- db.close();
443
+ if (queues.length === 0) {
444
+ console.log("No queues found.");
445
+ return;
434
446
  }
447
+ console.log("\n\u{1F4CB} Queues\n");
448
+ const table = new Table({
449
+ head: ["Queue", "Status", "Ready", "Running", "Total"],
450
+ style: { head: ["cyan"], border: ["gray"] },
451
+ colAligns: ["left", "left", "right", "right", "right"]
452
+ });
453
+ for (const q of queues) {
454
+ const status = q.paused ? "\u23F8\uFE0F PAUSED" : "\u25B6\uFE0F ACTIVE";
455
+ table.push([
456
+ q.queue,
457
+ status,
458
+ q.ready.toLocaleString(),
459
+ q.running.toLocaleString(),
460
+ q.total.toLocaleString()
461
+ ]);
462
+ }
463
+ console.log(table.toString());
464
+ console.log();
465
+ } finally {
466
+ db.close();
467
+ }
435
468
  }
436
- // Main
437
469
  async function main() {
438
- if (!command || command === '--help' || command === '-h') {
439
- printUsage();
440
- process.exit(0);
441
- }
442
- const positionalArgs = getPositionalArgs(args.slice(1));
443
- const options = parseOptions(args);
444
- const dbPath = positionalArgs[0];
445
- if (!dbPath) {
446
- console.error('Error: Database path is required');
447
- printUsage();
448
- process.exit(1);
449
- }
450
- try {
451
- switch (command) {
452
- case 'stats':
453
- await cmdStats(dbPath);
454
- break;
455
- case 'list':
456
- await cmdList(dbPath, options);
457
- break;
458
- case 'export':
459
- await cmdExport(dbPath, positionalArgs[1], options);
460
- break;
461
- case 'import':
462
- if (!positionalArgs[1]) {
463
- console.error('Error: Input CSV file is required');
464
- process.exit(1);
465
- }
466
- await cmdImport(dbPath, positionalArgs[1]);
467
- break;
468
- case 'purge':
469
- await cmdPurge(dbPath, options);
470
- break;
471
- case 'retry':
472
- await cmdRetry(dbPath, options);
473
- break;
474
- case 'pause':
475
- if (!positionalArgs[1]) {
476
- console.error('Error: Queue name is required');
477
- console.error('Example: workmatic pause ./jobs.db emails');
478
- process.exit(1);
479
- }
480
- await cmdPause(dbPath, positionalArgs[1]);
481
- break;
482
- case 'resume':
483
- if (!positionalArgs[1]) {
484
- console.error('Error: Queue name is required');
485
- console.error('Example: workmatic resume ./jobs.db emails');
486
- process.exit(1);
487
- }
488
- await cmdResume(dbPath, positionalArgs[1]);
489
- break;
490
- case 'queues':
491
- await cmdQueues(dbPath);
492
- break;
493
- default:
494
- console.error(`Unknown command: ${command}`);
495
- printUsage();
496
- process.exit(1);
470
+ if (!command || command === "--help" || command === "-h") {
471
+ printUsage();
472
+ process.exit(0);
473
+ }
474
+ const positionalArgs = getPositionalArgs(args.slice(1));
475
+ const options = parseOptions(args);
476
+ const dbPath = positionalArgs[0];
477
+ if (!dbPath) {
478
+ console.error("Error: Database path is required");
479
+ printUsage();
480
+ process.exit(1);
481
+ }
482
+ try {
483
+ switch (command) {
484
+ case "stats":
485
+ await cmdStats(dbPath);
486
+ break;
487
+ case "list":
488
+ await cmdList(dbPath, options);
489
+ break;
490
+ case "export":
491
+ await cmdExport(dbPath, positionalArgs[1], options);
492
+ break;
493
+ case "import":
494
+ if (!positionalArgs[1]) {
495
+ console.error("Error: Input CSV file is required");
496
+ process.exit(1);
497
497
  }
498
- }
499
- catch (error) {
500
- console.error('Error:', error instanceof Error ? error.message : error);
498
+ await cmdImport(dbPath, positionalArgs[1]);
499
+ break;
500
+ case "purge":
501
+ await cmdPurge(dbPath, options);
502
+ break;
503
+ case "retry":
504
+ await cmdRetry(dbPath, options);
505
+ break;
506
+ case "pause":
507
+ if (!positionalArgs[1]) {
508
+ console.error("Error: Queue name is required");
509
+ console.error("Example: workmatic pause ./jobs.db emails");
510
+ process.exit(1);
511
+ }
512
+ await cmdPause(dbPath, positionalArgs[1]);
513
+ break;
514
+ case "resume":
515
+ if (!positionalArgs[1]) {
516
+ console.error("Error: Queue name is required");
517
+ console.error("Example: workmatic resume ./jobs.db emails");
518
+ process.exit(1);
519
+ }
520
+ await cmdResume(dbPath, positionalArgs[1]);
521
+ break;
522
+ case "queues":
523
+ await cmdQueues(dbPath);
524
+ break;
525
+ default:
526
+ console.error(`Unknown command: ${command}`);
527
+ printUsage();
501
528
  process.exit(1);
502
529
  }
530
+ } catch (error) {
531
+ console.error("Error:", error instanceof Error ? error.message : error);
532
+ process.exit(1);
533
+ }
503
534
  }
504
535
  main();
505
536
  //# sourceMappingURL=cli.js.map