@dench.com/cli 2.2.6 → 2.2.7
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/cron.ts +94 -17
- package/package.json +1 -1
package/cron.ts
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* enable <jobId> Re-arm a paused cron.
|
|
34
34
|
* disable <jobId> Soft-pause without delete.
|
|
35
35
|
* run <jobId> [--wait] Manual fire; --wait blocks.
|
|
36
|
+
* secret <jobId> Mint/rotate a signing secret (any type).
|
|
36
37
|
* runs <jobId> [--limit N] [--status] Run history per trigger.
|
|
37
38
|
* history [--limit N] Flat history across all crons.
|
|
38
39
|
* help Print this help.
|
|
@@ -196,7 +197,9 @@ export type CronScheduleSpec =
|
|
|
196
197
|
*
|
|
197
198
|
* `--at` implicitly drops `--every` / `--cron` if both are passed.
|
|
198
199
|
*/
|
|
199
|
-
export function consumeScheduleFlags(
|
|
200
|
+
export function consumeScheduleFlags(
|
|
201
|
+
args: string[],
|
|
202
|
+
): CronScheduleSpec | undefined {
|
|
200
203
|
const at = getFlag(args, "--at");
|
|
201
204
|
if (at) {
|
|
202
205
|
const ts = new Date(at).getTime();
|
|
@@ -335,10 +338,15 @@ async function runGet(ctx: CronCliContext): Promise<void> {
|
|
|
335
338
|
}
|
|
336
339
|
|
|
337
340
|
async function runCreate(ctx: CronCliContext): Promise<void> {
|
|
338
|
-
const name = shift(
|
|
341
|
+
const name = shift(
|
|
342
|
+
ctx.args,
|
|
343
|
+
'name (positional, e.g. dench cron create "Daily standup digest" --prompt … --every 1d)',
|
|
344
|
+
);
|
|
339
345
|
const prompt = getFlag(ctx.args, "--prompt");
|
|
340
346
|
if (!prompt || !prompt.trim()) {
|
|
341
|
-
throw new CronCliError(
|
|
347
|
+
throw new CronCliError(
|
|
348
|
+
"--prompt is required (becomes the user message of each cron-fired chat thread)",
|
|
349
|
+
);
|
|
342
350
|
}
|
|
343
351
|
const description = getFlag(ctx.args, "--description");
|
|
344
352
|
const overlapPolicy = getFlag(ctx.args, "--overlap") as
|
|
@@ -346,10 +354,7 @@ async function runCreate(ctx: CronCliContext): Promise<void> {
|
|
|
346
354
|
| "queue"
|
|
347
355
|
| "kill_old"
|
|
348
356
|
| undefined;
|
|
349
|
-
if (
|
|
350
|
-
overlapPolicy &&
|
|
351
|
-
!["skip", "queue", "kill_old"].includes(overlapPolicy)
|
|
352
|
-
) {
|
|
357
|
+
if (overlapPolicy && !["skip", "queue", "kill_old"].includes(overlapPolicy)) {
|
|
353
358
|
throw new CronCliError(
|
|
354
359
|
`--overlap must be one of skip|queue|kill_old (got "${overlapPolicy}")`,
|
|
355
360
|
);
|
|
@@ -360,26 +365,86 @@ async function runCreate(ctx: CronCliContext): Promise<void> {
|
|
|
360
365
|
if (target && target !== "chat_turn") {
|
|
361
366
|
throw new CronCliError(`--target must be chat_turn (got "${target}")`);
|
|
362
367
|
}
|
|
363
|
-
const
|
|
364
|
-
|
|
368
|
+
const triggerType = (getFlag(ctx.args, "--type") ?? "schedule") as
|
|
369
|
+
| "schedule"
|
|
370
|
+
| "webhook";
|
|
371
|
+
if (triggerType !== "schedule" && triggerType !== "webhook") {
|
|
365
372
|
throw new CronCliError(
|
|
366
|
-
|
|
367
|
-
" dench cron create \"Hourly health check\" --prompt 'Check uptime…' --every 1h\n" +
|
|
368
|
-
' dench cron create "Weekday digest" --prompt \'Pull yesterday…\' --cron "0 9 * * 1-5"\n' +
|
|
369
|
-
' dench cron create "May 4 launch" --prompt \'Send launch tweet\' --at 2026-05-04T09:00:00Z',
|
|
373
|
+
`--type must be schedule|webhook (got "${triggerType}"). app-event / CRM automations are created from the dashboard.`,
|
|
370
374
|
);
|
|
371
375
|
}
|
|
372
|
-
const
|
|
376
|
+
const isWebhook = triggerType === "webhook";
|
|
377
|
+
|
|
378
|
+
let schedule: CronScheduleSpec | undefined;
|
|
379
|
+
if (isWebhook) {
|
|
380
|
+
// Webhook automations fire on demand / via their signed URL — no schedule.
|
|
381
|
+
if (consumeScheduleFlags(ctx.args)) {
|
|
382
|
+
throw new CronCliError(
|
|
383
|
+
"Webhook automations don't take a schedule. Omit --every/--cron/--at.",
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
schedule = consumeScheduleFlags(ctx.args);
|
|
388
|
+
if (!schedule) {
|
|
389
|
+
throw new CronCliError(
|
|
390
|
+
"One of --every / --cron / --at is required for schedule automations. Examples:\n" +
|
|
391
|
+
" dench cron create \"Hourly health check\" --prompt 'Check uptime…' --every 1h\n" +
|
|
392
|
+
' dench cron create "Weekday digest" --prompt \'Pull yesterday…\' --cron "0 9 * * 1-5"\n' +
|
|
393
|
+
" dench cron create \"Inbound lead\" --prompt 'Triage the lead…' --type webhook",
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const created = (await callMutation(ctx, createCronTriggerRef, {
|
|
373
398
|
name,
|
|
374
399
|
prompt,
|
|
400
|
+
triggerType,
|
|
375
401
|
schedule,
|
|
376
402
|
description,
|
|
377
403
|
overlapPolicy,
|
|
378
404
|
enabled: disabled ? false : undefined,
|
|
379
405
|
deleteAfterRun: deleteAfterRun ? true : undefined,
|
|
380
406
|
target,
|
|
407
|
+
})) as { jobId?: string };
|
|
408
|
+
|
|
409
|
+
// Webhook automations need a signing secret to be externally callable. Mint
|
|
410
|
+
// one now (unless --no-secret) so `create` hands back a working URL+secret.
|
|
411
|
+
if (isWebhook && !hasFlag(ctx.args, "--no-secret") && created.jobId) {
|
|
412
|
+
const secret = await mintAutomationSecret(ctx, created.jobId);
|
|
413
|
+
out(ctx, { ...created, ...secret });
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
out(ctx, created);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Mint (or rotate) a signing secret for an automation via the Next REST route
|
|
421
|
+
* (secret encryption runs in the Node route, not Convex's V8 runtime). Returns
|
|
422
|
+
* the plaintext secret + callable URL — shown once.
|
|
423
|
+
*/
|
|
424
|
+
async function mintAutomationSecret(
|
|
425
|
+
ctx: CronCliContext,
|
|
426
|
+
jobId: string,
|
|
427
|
+
): Promise<{ webhookUrl: string | null; webhookSecret: string | null }> {
|
|
428
|
+
const url = `${getApiBase()}/api/cron/jobs/${encodeURIComponent(jobId)}/regenerate-secret`;
|
|
429
|
+
const res = (await postJson(
|
|
430
|
+
url,
|
|
431
|
+
apiKeyHeaders(ctx.sessionToken ?? ""),
|
|
432
|
+
{},
|
|
433
|
+
)) as { webhookUrl?: string | null; webhookSecret?: string | null };
|
|
434
|
+
return {
|
|
435
|
+
webhookUrl: res.webhookUrl ?? null,
|
|
436
|
+
webhookSecret: res.webhookSecret ?? null,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function runSecret(ctx: CronCliContext): Promise<void> {
|
|
441
|
+
const jobId = shift(ctx.args, "job id");
|
|
442
|
+
const secret = await mintAutomationSecret(ctx, jobId);
|
|
443
|
+
out(ctx, {
|
|
444
|
+
jobId,
|
|
445
|
+
...secret,
|
|
446
|
+
note: "Secret shown once — store it now. Re-running rotates it (invalidates the old one).",
|
|
381
447
|
});
|
|
382
|
-
out(ctx, result);
|
|
383
448
|
}
|
|
384
449
|
|
|
385
450
|
async function runUpdate(ctx: CronCliContext): Promise<void> {
|
|
@@ -458,7 +523,11 @@ async function runRunNow(ctx: CronCliContext): Promise<void> {
|
|
|
458
523
|
})) as { entries: Array<{ ts: number; status?: string }> };
|
|
459
524
|
const matching = runs.entries.find(() => true);
|
|
460
525
|
if (matching && matching.status) {
|
|
461
|
-
out(ctx, {
|
|
526
|
+
out(ctx, {
|
|
527
|
+
...fired,
|
|
528
|
+
finalStatus: matching.status,
|
|
529
|
+
durationMs: Date.now() - start,
|
|
530
|
+
});
|
|
462
531
|
return;
|
|
463
532
|
}
|
|
464
533
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -510,9 +579,10 @@ Browse:
|
|
|
510
579
|
dench cron history [--limit 50] [--status ok|error|skipped] [--json]
|
|
511
580
|
dench cron runs <jobId> [--limit 50] [--status ok|error|skipped] [--json]
|
|
512
581
|
|
|
513
|
-
Create / update
|
|
582
|
+
Create / update:
|
|
514
583
|
dench cron create "Daily standup digest" \\
|
|
515
584
|
--prompt "Pull yesterday's commits + Slack #standup msgs and post a summary…" \\
|
|
585
|
+
--type schedule # default; or --type webhook (no schedule)
|
|
516
586
|
--every 1d # or --cron "0 9 * * 1-5" or --at 2026-05-04T09:00:00Z
|
|
517
587
|
[--description "..."] # human-readable note shown in dashboard / list
|
|
518
588
|
[--overlap skip|queue|kill_old] # default: skip
|
|
@@ -521,6 +591,10 @@ Create / update (one of --every / --cron / --at is required for create):
|
|
|
521
591
|
[--target chat_turn] # default: chat_turn (fires a fresh chat thread)
|
|
522
592
|
[--json]
|
|
523
593
|
|
|
594
|
+
# Webhook automation (fires on demand or via a signed URL). Mints + prints a
|
|
595
|
+
# signing secret unless --no-secret. The secret is shown ONCE.
|
|
596
|
+
dench cron create "Inbound lead triage" --prompt "Triage the lead…" --type webhook
|
|
597
|
+
|
|
524
598
|
dench cron update <jobId> [--name N] [--prompt P] [--description D] \\
|
|
525
599
|
[--every X | --cron EXPR | --at ISO] [--enabled true|false] [--overlap …]
|
|
526
600
|
|
|
@@ -529,6 +603,7 @@ Lifecycle:
|
|
|
529
603
|
dench cron disable <jobId>
|
|
530
604
|
dench cron delete <jobId>
|
|
531
605
|
dench cron run <jobId> [--wait] [--json] # manual fire; --wait blocks until run finishes
|
|
606
|
+
dench cron secret <jobId> [--json] # mint/rotate a signing secret → callable signed URL (any automation type)
|
|
532
607
|
|
|
533
608
|
Schedule formats:
|
|
534
609
|
--every <duration> Repeat every N. Accepts 30s, 5m, 2h, 1d, 12h, etc.
|
|
@@ -599,6 +674,8 @@ export async function runCronCommand(opts: {
|
|
|
599
674
|
return await runEnable(ctx, false);
|
|
600
675
|
case "run":
|
|
601
676
|
return await runRunNow(ctx);
|
|
677
|
+
case "secret":
|
|
678
|
+
return await runSecret(ctx);
|
|
602
679
|
case "runs":
|
|
603
680
|
return await runRuns(ctx);
|
|
604
681
|
case "history":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dench.com/cli",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.7",
|
|
4
4
|
"description": "Dench agent workspace CLI. v2 unifies auth behind `dench signin`; the legacy `dench login` / `dench onboard` / `dench setup` / `dench what-can-i-do` / `dench register` commands now error with a redirect.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|