11/**
22 * One-shot: register every time-triggered agent's cron with relaycron.
3- * Idempotent — uses a stable schedule name; updates if it exists, creates
4- * otherwise.
3+ * Idempotent — looks up by stable schedule name; updates if it exists,
4+ * creates otherwise.
55 *
6- * Run from your laptop after wrangler-pages secrets are set:
6+ * Run from your laptop after the Pages secrets are set:
77 *
88 * RELAYCRON_API_KEY=ac_... CRON_WEBHOOK_SECRET=... \
99 * npx tsx scripts/register-schedules.ts
1010 *
1111 * The CRON_WEBHOOK_SECRET passed here MUST match the value set as the
1212 * Cloudflare Pages secret of the same name. Otherwise the function rejects
1313 * the delivery 401.
14+ *
15+ * We use plain fetch instead of @relaycron/sdk to dodge ESM/CJS resolution
16+ * drama; the API surface is small enough to inline.
1417 */
15- import { AgentCron } from "@relaycron/sdk" ;
16-
1718import weeklyDigest from "../agents/weekly-digest/agent" ;
1819
1920const RELAYCRON_BASE = process . env . RELAYCRON_BASE_URL ?? "https://api.relaycron.dev" ;
2021const SITE_BASE = process . env . SITE_BASE_URL ?? "https://proactiveagents.dev" ;
2122
23+ type Schedule = {
24+ id : string ;
25+ name : string ;
26+ cron_expression ?: string ;
27+ timezone ?: string ;
28+ } ;
29+
2230type RegisterableSchedule = {
2331 agentName : string ;
2432 cron : string ;
@@ -44,48 +52,75 @@ function extractTz(s: unknown): string | null {
4452 return null ;
4553}
4654
47- async function main ( ) {
55+ async function api < T > ( method : string , path : string , body ?: unknown ) : Promise < T > {
4856 const apiKey = required ( "RELAYCRON_API_KEY" ) ;
49- const webhookSecret = required ( "CRON_WEBHOOK_SECRET" ) ;
57+ const res = await fetch ( `${ RELAYCRON_BASE } ${ path } ` , {
58+ method,
59+ headers : {
60+ Authorization : `Bearer ${ apiKey } ` ,
61+ "Content-Type" : "application/json" ,
62+ } ,
63+ body : body ? JSON . stringify ( body ) : undefined ,
64+ } ) ;
65+ const json = ( await res . json ( ) ) as
66+ | { ok : true ; data : T }
67+ | { ok : false ; error : { code : string ; message : string } } ;
68+ if ( ! json . ok ) {
69+ throw new Error ( `relaycron ${ res . status } ${ json . error . code } : ${ json . error . message } ` ) ;
70+ }
71+ return json . data ;
72+ }
73+
74+ async function listAllSchedules ( ) : Promise < Schedule [ ] > {
75+ const out : Schedule [ ] = [ ] ;
76+ let cursor : string | undefined ;
77+ do {
78+ const qs = cursor ? `?cursor=${ encodeURIComponent ( cursor ) } &limit=100` : "?limit=100" ;
79+ const page = ( await api < Schedule [ ] > ( "GET" , `/v1/schedules${ qs } ` ) ) as Schedule [ ] & {
80+ cursor ?: string | null ;
81+ } ;
82+ out . push ( ...page ) ;
83+ cursor = ( page as unknown as { cursor ?: string | null } ) . cursor ?? undefined ;
84+ } while ( cursor ) ;
85+ return out ;
86+ }
5087
51- const ac = new AgentCron ( { apiKey, baseUrl : RELAYCRON_BASE } ) ;
88+ async function main ( ) {
89+ required ( "RELAYCRON_API_KEY" ) ;
90+ const webhookSecret = required ( "CRON_WEBHOOK_SECRET" ) ;
5291
53- // List existing schedules so we can update by name rather than create dupes.
54- const list = await ac . listSchedules ( { limit : 100 } ) ;
55- const byName = new Map ( list . data . map ( ( s ) => [ s . name , s ] ) ) ;
92+ const existing = await listAllSchedules ( ) ;
93+ const byName = new Map ( existing . map ( ( s ) => [ s . name , s ] ) ) ;
5694
5795 for ( const reg of REGISTRY ) {
5896 const name = `proactive-agents/${ reg . agentName } ` ;
5997 const url = `${ SITE_BASE } /api/cron/${ reg . agentName } ` ;
6098
61- const existing = byName . get ( name ) ;
62- if ( existing ) {
63- await ac . updateSchedule ( existing . id , {
99+ const transport = {
100+ type : "webhook" as const ,
101+ url,
102+ headers : { "X-Cron-Secret" : webhookSecret } ,
103+ timeout_ms : 30000 ,
104+ } ;
105+
106+ const prior = byName . get ( name ) ;
107+ if ( prior ) {
108+ await api ( "PATCH" , `/v1/schedules/${ prior . id } ` , {
64109 cron_expression : reg . cron ,
65110 timezone : reg . tz ,
66- transport : {
67- type : "webhook" ,
68- url,
69- headers : { "X-Cron-Secret" : webhookSecret } ,
70- timeout_ms : 30000 ,
71- } ,
111+ transport,
72112 status : "active" ,
73113 } ) ;
74114 console . log ( `updated ${ name } ${ reg . cron } ${ reg . tz } → ${ url } ` ) ;
75115 } else {
76- const created = await ac . createSchedule ( {
116+ const created = await api < Schedule > ( "POST" , "/v1/schedules" , {
77117 name,
78118 description : `Auto-registered from agents/${ reg . agentName } /agent.ts` ,
79119 schedule_type : "cron" ,
80120 cron_expression : reg . cron ,
81121 timezone : reg . tz ,
82122 payload : { agent : reg . agentName } ,
83- transport : {
84- type : "webhook" ,
85- url,
86- headers : { "X-Cron-Secret" : webhookSecret } ,
87- timeout_ms : 30000 ,
88- } ,
123+ transport,
89124 } ) ;
90125 console . log ( `created ${ name } ${ reg . cron } ${ reg . tz } → ${ url } (id ${ created . id } )` ) ;
91126 }
0 commit comments