@nicnocquee/dataqueue 1.33.0 → 1.35.0-beta.20260224075710

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 (54) hide show
  1. package/ai/build-docs-content.ts +96 -0
  2. package/ai/build-llms-full.ts +42 -0
  3. package/ai/docs-content.json +290 -0
  4. package/ai/rules/advanced.md +170 -0
  5. package/ai/rules/basic.md +159 -0
  6. package/ai/rules/react-dashboard.md +87 -0
  7. package/ai/skills/dataqueue-advanced/SKILL.md +370 -0
  8. package/ai/skills/dataqueue-core/SKILL.md +235 -0
  9. package/ai/skills/dataqueue-react/SKILL.md +201 -0
  10. package/dist/cli.cjs +577 -32
  11. package/dist/cli.cjs.map +1 -1
  12. package/dist/cli.d.cts +52 -2
  13. package/dist/cli.d.ts +52 -2
  14. package/dist/cli.js +575 -32
  15. package/dist/cli.js.map +1 -1
  16. package/dist/index.cjs +937 -108
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +358 -11
  19. package/dist/index.d.ts +358 -11
  20. package/dist/index.js +937 -108
  21. package/dist/index.js.map +1 -1
  22. package/dist/mcp-server.cjs +186 -0
  23. package/dist/mcp-server.cjs.map +1 -0
  24. package/dist/mcp-server.d.cts +32 -0
  25. package/dist/mcp-server.d.ts +32 -0
  26. package/dist/mcp-server.js +175 -0
  27. package/dist/mcp-server.js.map +1 -0
  28. package/migrations/1781200000005_add_retry_config_to_job_queue.sql +17 -0
  29. package/migrations/1781200000006_add_output_to_job_queue.sql +3 -0
  30. package/package.json +10 -4
  31. package/src/backend.ts +36 -3
  32. package/src/backends/postgres.ts +344 -42
  33. package/src/backends/redis-scripts.ts +173 -8
  34. package/src/backends/redis.test.ts +668 -0
  35. package/src/backends/redis.ts +244 -15
  36. package/src/cli.test.ts +65 -0
  37. package/src/cli.ts +56 -19
  38. package/src/db-util.ts +1 -1
  39. package/src/index.test.ts +811 -12
  40. package/src/index.ts +106 -14
  41. package/src/install-mcp-command.test.ts +216 -0
  42. package/src/install-mcp-command.ts +185 -0
  43. package/src/install-rules-command.test.ts +218 -0
  44. package/src/install-rules-command.ts +233 -0
  45. package/src/install-skills-command.test.ts +176 -0
  46. package/src/install-skills-command.ts +124 -0
  47. package/src/mcp-server.test.ts +162 -0
  48. package/src/mcp-server.ts +231 -0
  49. package/src/processor.ts +133 -49
  50. package/src/queue.test.ts +477 -0
  51. package/src/queue.ts +20 -3
  52. package/src/supervisor.test.ts +340 -0
  53. package/src/supervisor.ts +177 -0
  54. package/src/types.ts +318 -3
@@ -14,6 +14,7 @@ import {
14
14
  EditCronScheduleOptions,
15
15
  WaitpointRecord,
16
16
  CreateTokenOptions,
17
+ AddJobOptions,
17
18
  } from '../types.js';
18
19
  import {
19
20
  QueueBackend,
@@ -61,6 +62,7 @@ function parseTimeoutString(timeout: string): number {
61
62
  }
62
63
  import {
63
64
  ADD_JOB_SCRIPT,
65
+ ADD_JOBS_SCRIPT,
64
66
  GET_NEXT_BATCH_SCRIPT,
65
67
  COMPLETE_JOB_SCRIPT,
66
68
  FAIL_JOB_SCRIPT,
@@ -162,9 +164,28 @@ function deserializeJob<PayloadMap, T extends JobType<PayloadMap>>(
162
164
  waitUntil: dateOrNull(h.waitUntil),
163
165
  waitTokenId: nullish(h.waitTokenId) as string | null | undefined,
164
166
  stepData: parseStepData(h.stepData),
167
+ retryDelay: numOrNull(h.retryDelay),
168
+ retryBackoff:
169
+ h.retryBackoff === 'true'
170
+ ? true
171
+ : h.retryBackoff === 'false'
172
+ ? false
173
+ : null,
174
+ retryDelayMax: numOrNull(h.retryDelayMax),
175
+ output: parseJsonField(h.output),
165
176
  };
166
177
  }
167
178
 
179
+ /** Parse a JSON field from a Redis hash, returning null for missing/null values. */
180
+ function parseJsonField(raw: string | undefined): unknown {
181
+ if (!raw || raw === 'null') return null;
182
+ try {
183
+ return JSON.parse(raw);
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
188
+
168
189
  /** Parse step data from a Redis hash field. */
169
190
  function parseStepData(
170
191
  raw: string | undefined,
@@ -181,8 +202,30 @@ export class RedisBackend implements QueueBackend {
181
202
  private client: RedisType;
182
203
  private prefix: string;
183
204
 
184
- constructor(redisConfig: RedisJobQueueConfig['redisConfig']) {
185
- // Dynamically require ioredis to avoid hard dep
205
+ /**
206
+ * Create a RedisBackend.
207
+ *
208
+ * @param configOrClient - Either `redisConfig` from the config file (the
209
+ * library creates a new ioredis client) or an existing ioredis client
210
+ * instance (bring your own).
211
+ * @param keyPrefix - Key prefix, only used when `configOrClient` is an
212
+ * external client. Ignored when `redisConfig` is passed (uses
213
+ * `redisConfig.keyPrefix` instead). Default: `'dq:'`.
214
+ */
215
+ constructor(
216
+ configOrClient: RedisJobQueueConfig['redisConfig'] | RedisType,
217
+ keyPrefix?: string,
218
+ ) {
219
+ if (configOrClient && typeof (configOrClient as any).eval === 'function') {
220
+ this.client = configOrClient as RedisType;
221
+ this.prefix = keyPrefix ?? 'dq:';
222
+ return;
223
+ }
224
+
225
+ const redisConfig = configOrClient as NonNullable<
226
+ RedisJobQueueConfig['redisConfig']
227
+ >;
228
+
186
229
  let IORedis: any;
187
230
  try {
188
231
  const _require = createRequire(import.meta.url);
@@ -260,17 +303,29 @@ export class RedisBackend implements QueueBackend {
260
303
 
261
304
  // ── Job CRUD ──────────────────────────────────────────────────────────
262
305
 
263
- async addJob<PayloadMap, T extends JobType<PayloadMap>>({
264
- jobType,
265
- payload,
266
- maxAttempts = 3,
267
- priority = 0,
268
- runAt = null,
269
- timeoutMs = undefined,
270
- forceKillOnTimeout = false,
271
- tags = undefined,
272
- idempotencyKey = undefined,
273
- }: JobOptions<PayloadMap, T>): Promise<number> {
306
+ async addJob<PayloadMap, T extends JobType<PayloadMap>>(
307
+ {
308
+ jobType,
309
+ payload,
310
+ maxAttempts = 3,
311
+ priority = 0,
312
+ runAt = null,
313
+ timeoutMs = undefined,
314
+ forceKillOnTimeout = false,
315
+ tags = undefined,
316
+ idempotencyKey = undefined,
317
+ retryDelay = undefined,
318
+ retryBackoff = undefined,
319
+ retryDelayMax = undefined,
320
+ }: JobOptions<PayloadMap, T>,
321
+ options?: AddJobOptions,
322
+ ): Promise<number> {
323
+ if (options?.db) {
324
+ throw new Error(
325
+ 'The db option is not supported with the Redis backend. ' +
326
+ 'Transactional job creation is only available with PostgreSQL.',
327
+ );
328
+ }
274
329
  const now = this.nowMs();
275
330
  const runAtMs = runAt ? runAt.getTime() : 0;
276
331
 
@@ -288,6 +343,9 @@ export class RedisBackend implements QueueBackend {
288
343
  tags ? JSON.stringify(tags) : 'null',
289
344
  idempotencyKey ?? 'null',
290
345
  now,
346
+ retryDelay !== undefined ? retryDelay.toString() : 'null',
347
+ retryBackoff !== undefined ? retryBackoff.toString() : 'null',
348
+ retryDelayMax !== undefined ? retryDelayMax.toString() : 'null',
291
349
  )) as number;
292
350
 
293
351
  const jobId = Number(result);
@@ -303,6 +361,83 @@ export class RedisBackend implements QueueBackend {
303
361
  return jobId;
304
362
  }
305
363
 
364
+ /**
365
+ * Insert multiple jobs atomically via a single Lua script.
366
+ * Returns IDs in the same order as the input array.
367
+ */
368
+ async addJobs<PayloadMap, T extends JobType<PayloadMap>>(
369
+ jobs: JobOptions<PayloadMap, T>[],
370
+ options?: AddJobOptions,
371
+ ): Promise<number[]> {
372
+ if (jobs.length === 0) return [];
373
+
374
+ if (options?.db) {
375
+ throw new Error(
376
+ 'The db option is not supported with the Redis backend. ' +
377
+ 'Transactional job creation is only available with PostgreSQL.',
378
+ );
379
+ }
380
+
381
+ const now = this.nowMs();
382
+
383
+ const jobsPayload = jobs.map((job) => ({
384
+ jobType: job.jobType,
385
+ payload: JSON.stringify(job.payload),
386
+ maxAttempts: job.maxAttempts ?? 3,
387
+ priority: job.priority ?? 0,
388
+ runAtMs: job.runAt ? job.runAt.getTime() : 0,
389
+ timeoutMs:
390
+ job.timeoutMs !== undefined ? job.timeoutMs.toString() : 'null',
391
+ forceKillOnTimeout: job.forceKillOnTimeout ? 'true' : 'false',
392
+ tags: job.tags ? JSON.stringify(job.tags) : 'null',
393
+ idempotencyKey: job.idempotencyKey ?? 'null',
394
+ retryDelay:
395
+ job.retryDelay !== undefined ? job.retryDelay.toString() : 'null',
396
+ retryBackoff:
397
+ job.retryBackoff !== undefined ? job.retryBackoff.toString() : 'null',
398
+ retryDelayMax:
399
+ job.retryDelayMax !== undefined ? job.retryDelayMax.toString() : 'null',
400
+ }));
401
+
402
+ const result = (await this.client.eval(
403
+ ADD_JOBS_SCRIPT,
404
+ 1,
405
+ this.prefix,
406
+ JSON.stringify(jobsPayload),
407
+ now,
408
+ )) as number[];
409
+
410
+ const ids = result.map(Number);
411
+ log(`Batch-inserted ${jobs.length} jobs, IDs: [${ids.join(', ')}]`);
412
+
413
+ // Record events for newly inserted jobs (skip idempotency duplicates)
414
+ const existingIdempotencyIds = new Set<number>();
415
+ for (let i = 0; i < jobs.length; i++) {
416
+ if (jobs[i].idempotencyKey) {
417
+ // If the returned ID existed before this batch, it was a duplicate.
418
+ // We detect this by checking if the same ID appears for a different
419
+ // idempotency-keyed job (unlikely) or by checking if the ID was less
420
+ // than what we'd expect. The simplest approach: record events for all,
421
+ // since the Lua script returns the existing ID for duplicates but
422
+ // doesn't tell us if it was newly created. We can compare: if
423
+ // multiple jobs have the same idempotency key in the batch and got
424
+ // the same ID, only record once.
425
+ if (existingIdempotencyIds.has(ids[i])) {
426
+ continue;
427
+ }
428
+ existingIdempotencyIds.add(ids[i]);
429
+ }
430
+ await this.recordJobEvent(ids[i], JobEventType.Added, {
431
+ jobType: jobs[i].jobType,
432
+ payload: jobs[i].payload,
433
+ tags: jobs[i].tags,
434
+ idempotencyKey: jobs[i].idempotencyKey,
435
+ });
436
+ }
437
+
438
+ return ids;
439
+ }
440
+
306
441
  async getJob<PayloadMap, T extends JobType<PayloadMap>>(
307
442
  id: number,
308
443
  ): Promise<JobRecord<PayloadMap, T> | null> {
@@ -469,9 +604,18 @@ export class RedisBackend implements QueueBackend {
469
604
  return jobs;
470
605
  }
471
606
 
472
- async completeJob(jobId: number): Promise<void> {
607
+ async completeJob(jobId: number, output?: unknown): Promise<void> {
473
608
  const now = this.nowMs();
474
- await this.client.eval(COMPLETE_JOB_SCRIPT, 1, this.prefix, jobId, now);
609
+ const outputArg =
610
+ output !== undefined ? JSON.stringify(output) : '__NONE__';
611
+ await this.client.eval(
612
+ COMPLETE_JOB_SCRIPT,
613
+ 1,
614
+ this.prefix,
615
+ jobId,
616
+ now,
617
+ outputArg,
618
+ );
475
619
  await this.recordJobEvent(jobId, JobEventType.Completed);
476
620
  log(`Completed job ${jobId}`);
477
621
  }
@@ -535,6 +679,24 @@ export class RedisBackend implements QueueBackend {
535
679
  }
536
680
  }
537
681
 
682
+ // ── Output ────────────────────────────────────────────────────────────
683
+
684
+ async updateOutput(jobId: number, output: unknown): Promise<void> {
685
+ try {
686
+ const now = this.nowMs();
687
+ await this.client.hset(
688
+ `${this.prefix}job:${jobId}`,
689
+ 'output',
690
+ JSON.stringify(output),
691
+ 'updatedAt',
692
+ now.toString(),
693
+ );
694
+ log(`Updated output for job ${jobId}`);
695
+ } catch (error) {
696
+ log(`Error updating output for job ${jobId}: ${error}`);
697
+ }
698
+ }
699
+
538
700
  // ── Job management ────────────────────────────────────────────────────
539
701
 
540
702
  async retryJob(jobId: number): Promise<void> {
@@ -657,6 +819,31 @@ export class RedisBackend implements QueueBackend {
657
819
  }
658
820
  metadata.tags = updates.tags;
659
821
  }
822
+ if (updates.retryDelay !== undefined) {
823
+ fields.push(
824
+ 'retryDelay',
825
+ updates.retryDelay !== null ? updates.retryDelay.toString() : 'null',
826
+ );
827
+ metadata.retryDelay = updates.retryDelay;
828
+ }
829
+ if (updates.retryBackoff !== undefined) {
830
+ fields.push(
831
+ 'retryBackoff',
832
+ updates.retryBackoff !== null
833
+ ? updates.retryBackoff.toString()
834
+ : 'null',
835
+ );
836
+ metadata.retryBackoff = updates.retryBackoff;
837
+ }
838
+ if (updates.retryDelayMax !== undefined) {
839
+ fields.push(
840
+ 'retryDelayMax',
841
+ updates.retryDelayMax !== null
842
+ ? updates.retryDelayMax.toString()
843
+ : 'null',
844
+ );
845
+ metadata.retryDelayMax = updates.retryDelayMax;
846
+ }
660
847
 
661
848
  if (fields.length === 0) {
662
849
  log(`No fields to update for job ${jobId}`);
@@ -1236,6 +1423,18 @@ export class RedisBackend implements QueueBackend {
1236
1423
  now.toString(),
1237
1424
  'updatedAt',
1238
1425
  now.toString(),
1426
+ 'retryDelay',
1427
+ input.retryDelay !== null && input.retryDelay !== undefined
1428
+ ? input.retryDelay.toString()
1429
+ : 'null',
1430
+ 'retryBackoff',
1431
+ input.retryBackoff !== null && input.retryBackoff !== undefined
1432
+ ? input.retryBackoff.toString()
1433
+ : 'null',
1434
+ 'retryDelayMax',
1435
+ input.retryDelayMax !== null && input.retryDelayMax !== undefined
1436
+ ? input.retryDelayMax.toString()
1437
+ : 'null',
1239
1438
  ];
1240
1439
 
1241
1440
  await (this.client as any).hmset(key, ...fields);
@@ -1417,6 +1616,28 @@ export class RedisBackend implements QueueBackend {
1417
1616
  if (updates.allowOverlap !== undefined) {
1418
1617
  fields.push('allowOverlap', updates.allowOverlap ? 'true' : 'false');
1419
1618
  }
1619
+ if (updates.retryDelay !== undefined) {
1620
+ fields.push(
1621
+ 'retryDelay',
1622
+ updates.retryDelay !== null ? updates.retryDelay.toString() : 'null',
1623
+ );
1624
+ }
1625
+ if (updates.retryBackoff !== undefined) {
1626
+ fields.push(
1627
+ 'retryBackoff',
1628
+ updates.retryBackoff !== null
1629
+ ? updates.retryBackoff.toString()
1630
+ : 'null',
1631
+ );
1632
+ }
1633
+ if (updates.retryDelayMax !== undefined) {
1634
+ fields.push(
1635
+ 'retryDelayMax',
1636
+ updates.retryDelayMax !== null
1637
+ ? updates.retryDelayMax.toString()
1638
+ : 'null',
1639
+ );
1640
+ }
1420
1641
  if (nextRunAt !== undefined) {
1421
1642
  const val = nextRunAt !== null ? nextRunAt.getTime().toString() : 'null';
1422
1643
  fields.push('nextRunAt', val);
@@ -1557,6 +1778,14 @@ export class RedisBackend implements QueueBackend {
1557
1778
  nextRunAt: dateOrNull(h.nextRunAt),
1558
1779
  createdAt: new Date(Number(h.createdAt)),
1559
1780
  updatedAt: new Date(Number(h.updatedAt)),
1781
+ retryDelay: numOrNull(h.retryDelay),
1782
+ retryBackoff:
1783
+ h.retryBackoff === 'true'
1784
+ ? true
1785
+ : h.retryBackoff === 'false'
1786
+ ? false
1787
+ : null,
1788
+ retryDelayMax: numOrNull(h.retryDelayMax),
1560
1789
  };
1561
1790
  }
1562
1791
 
package/src/cli.test.ts CHANGED
@@ -23,6 +23,10 @@ function makeDeps() {
23
23
  spawnSyncImpl: vi.fn(() => makeSpawnSyncReturns(0)),
24
24
  migrationsDir: '/migrations',
25
25
  runInitImpl: vi.fn(),
26
+ runInstallSkillsImpl: vi.fn(),
27
+ runInstallRulesImpl: vi.fn(async () => {}),
28
+ runInstallMcpImpl: vi.fn(async () => {}),
29
+ startMcpServerImpl: vi.fn(async () => ({}) as any),
26
30
  } satisfies CliDeps;
27
31
  }
28
32
 
@@ -138,4 +142,65 @@ describe('runCli', () => {
138
142
  runCli(['node', 'cli.js', 'migrate'], deps);
139
143
  expect(deps.exit).toHaveBeenCalledWith(1);
140
144
  });
145
+
146
+ it('routes install-skills command to runInstallSkillsImpl', () => {
147
+ // Act
148
+ runCli(['node', 'cli.js', 'install-skills'], deps);
149
+
150
+ // Assert
151
+ expect(deps.runInstallSkillsImpl).toHaveBeenCalledWith(
152
+ expect.objectContaining({
153
+ log: deps.log,
154
+ error: deps.error,
155
+ exit: deps.exit,
156
+ }),
157
+ );
158
+ });
159
+
160
+ it('routes install-rules command to runInstallRulesImpl', () => {
161
+ // Act
162
+ runCli(['node', 'cli.js', 'install-rules'], deps);
163
+
164
+ // Assert
165
+ expect(deps.runInstallRulesImpl).toHaveBeenCalledWith(
166
+ expect.objectContaining({
167
+ log: deps.log,
168
+ error: deps.error,
169
+ exit: deps.exit,
170
+ }),
171
+ );
172
+ });
173
+
174
+ it('routes install-mcp command to runInstallMcpImpl', () => {
175
+ // Act
176
+ runCli(['node', 'cli.js', 'install-mcp'], deps);
177
+
178
+ // Assert
179
+ expect(deps.runInstallMcpImpl).toHaveBeenCalledWith(
180
+ expect.objectContaining({
181
+ log: deps.log,
182
+ error: deps.error,
183
+ exit: deps.exit,
184
+ }),
185
+ );
186
+ });
187
+
188
+ it('routes mcp command to startMcpServerImpl', () => {
189
+ // Act
190
+ runCli(['node', 'cli.js', 'mcp'], deps);
191
+
192
+ // Assert
193
+ expect(deps.startMcpServerImpl).toHaveBeenCalled();
194
+ });
195
+
196
+ it('shows new commands in usage output', () => {
197
+ // Act
198
+ runCli(['node', 'cli.js'], deps);
199
+
200
+ // Assert
201
+ expect(deps.log).toHaveBeenCalledWith(' dataqueue-cli install-skills');
202
+ expect(deps.log).toHaveBeenCalledWith(' dataqueue-cli install-rules');
203
+ expect(deps.log).toHaveBeenCalledWith(' dataqueue-cli install-mcp');
204
+ expect(deps.log).toHaveBeenCalledWith(' dataqueue-cli mcp');
205
+ });
141
206
  });
package/src/cli.ts CHANGED
@@ -3,6 +3,13 @@ import { spawnSync, SpawnSyncReturns } from 'child_process';
3
3
  import path from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { InitDeps, runInit } from './init-command.js';
6
+ import {
7
+ runInstallSkills,
8
+ InstallSkillsDeps,
9
+ } from './install-skills-command.js';
10
+ import { runInstallRules, InstallRulesDeps } from './install-rules-command.js';
11
+ import { runInstallMcp, InstallMcpDeps } from './install-mcp-command.js';
12
+ import { startMcpServer } from './mcp-server.js';
6
13
 
7
14
  const __filename = fileURLToPath(import.meta.url);
8
15
  const __dirname = path.dirname(__filename);
@@ -15,6 +22,13 @@ export interface CliDeps {
15
22
  migrationsDir?: string;
16
23
  initDeps?: InitDeps;
17
24
  runInitImpl?: (deps?: InitDeps) => void;
25
+ installSkillsDeps?: InstallSkillsDeps;
26
+ runInstallSkillsImpl?: (deps?: InstallSkillsDeps) => void;
27
+ installRulesDeps?: InstallRulesDeps;
28
+ runInstallRulesImpl?: (deps?: InstallRulesDeps) => Promise<void>;
29
+ installMcpDeps?: InstallMcpDeps;
30
+ runInstallMcpImpl?: (deps?: InstallMcpDeps) => Promise<void>;
31
+ startMcpServerImpl?: typeof startMcpServer;
18
32
  }
19
33
 
20
34
  export function runCli(
@@ -27,19 +41,27 @@ export function runCli(
27
41
  migrationsDir = path.join(__dirname, '../migrations'),
28
42
  initDeps,
29
43
  runInitImpl = runInit,
44
+ installSkillsDeps,
45
+ runInstallSkillsImpl = runInstallSkills,
46
+ installRulesDeps,
47
+ runInstallRulesImpl = runInstallRules,
48
+ installMcpDeps,
49
+ runInstallMcpImpl = runInstallMcp,
50
+ startMcpServerImpl = startMcpServer,
30
51
  }: CliDeps = {},
31
52
  ): void {
32
53
  const [, , command, ...restArgs] = argv;
33
54
 
34
- /**
35
- * Prints CLI usage and exits with non-zero code.
36
- */
37
55
  function printUsage() {
38
56
  log('Usage:');
39
57
  log(
40
58
  ' dataqueue-cli migrate [--envPath <path>] [-s <schema> | --schema <schema>]',
41
59
  );
42
60
  log(' dataqueue-cli init');
61
+ log(' dataqueue-cli install-skills');
62
+ log(' dataqueue-cli install-rules');
63
+ log(' dataqueue-cli install-mcp');
64
+ log(' dataqueue-cli mcp');
43
65
  log('');
44
66
  log('Options for migrate:');
45
67
  log(
@@ -49,24 +71,13 @@ export function runCli(
49
71
  ' -s, --schema <schema> Set the schema to use (passed to node-pg-migrate)',
50
72
  );
51
73
  log('');
52
- log('Notes:');
74
+ log('AI tooling commands:');
75
+ log(' install-skills Install DataQueue skill files for AI assistants');
76
+ log(' install-rules Install DataQueue agent rules for AI clients');
53
77
  log(
54
- ' - The PG_DATAQUEUE_DATABASE environment variable must be set to your Postgres connection string.',
55
- );
56
- log(
57
- ' - For managed Postgres (e.g., DigitalOcean) with SSL, set PGSSLMODE=require and PGSSLROOTCERT to your CA .crt file.',
58
- );
59
- log(
60
- ' Example: PGSSLMODE=require NODE_EXTRA_CA_CERTS=/absolute/path/to/ca.crt PG_DATAQUEUE_DATABASE=... npx dataqueue-cli migrate',
61
- );
62
- log('');
63
- log('Notes for init:');
64
- log(
65
- ' - Supports both Next.js App Router and Pages Router (prefers App Router if both exist).',
66
- );
67
- log(
68
- ' - Scaffolds endpoint, cron.sh, queue placeholder, and package.json entries.',
78
+ ' install-mcp Configure the DataQueue MCP server for AI clients',
69
79
  );
80
+ log(' mcp Start the DataQueue MCP server (stdio)');
70
81
  exit(1);
71
82
  }
72
83
 
@@ -115,6 +126,32 @@ export function runCli(
115
126
  exit,
116
127
  ...initDeps,
117
128
  });
129
+ } else if (command === 'install-skills') {
130
+ runInstallSkillsImpl({
131
+ log,
132
+ error,
133
+ exit,
134
+ ...installSkillsDeps,
135
+ });
136
+ } else if (command === 'install-rules') {
137
+ runInstallRulesImpl({
138
+ log,
139
+ error,
140
+ exit,
141
+ ...installRulesDeps,
142
+ });
143
+ } else if (command === 'install-mcp') {
144
+ runInstallMcpImpl({
145
+ log,
146
+ error,
147
+ exit,
148
+ ...installMcpDeps,
149
+ });
150
+ } else if (command === 'mcp') {
151
+ startMcpServerImpl().catch((err) => {
152
+ error('Failed to start MCP server:', err);
153
+ exit(1);
154
+ });
118
155
  } else {
119
156
  printUsage();
120
157
  }
package/src/db-util.ts CHANGED
@@ -27,7 +27,7 @@ function loadPemOrFile(value?: string): string | undefined {
27
27
  * }
28
28
  */
29
29
  export const createPool = (
30
- config: PostgresJobQueueConfig['databaseConfig'],
30
+ config: NonNullable<PostgresJobQueueConfig['databaseConfig']>,
31
31
  ): Pool => {
32
32
  let searchPath: string | undefined;
33
33
  let ssl: any = undefined;