Skip to content

Commit e2f8077

Browse files
khaliqgantclaude
andcommitted
scripts(register-schedules): drop @relaycron/sdk, use plain fetch
The SDK is ESM-only with strict exports; tsx kept routing through CJS resolution and failing to find the entry. The relaycron API surface is small enough that fetch is cleaner anyway — fewer deps, no resolution drama, same surface used elsewhere in the repo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bf9e277 commit e2f8077

3 files changed

Lines changed: 62 additions & 62 deletions

File tree

package-lock.json

Lines changed: 0 additions & 34 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
"@cloudflare/workers-types": "^4.20260511.1",
1313
"@gsap/react": "^2.1.2",
1414
"@octokit/app": "^16.1.2",
15-
"@relaycron/sdk": "^0.1.2",
1615
"class-variance-authority": "^0.7.1",
1716
"clsx": "^2.1.1",
1817
"gray-matter": "^4.0.3",

scripts/register-schedules.ts

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
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-
1718
import weeklyDigest from "../agents/weekly-digest/agent";
1819

1920
const RELAYCRON_BASE = process.env.RELAYCRON_BASE_URL ?? "https://api.relaycron.dev";
2021
const 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+
2230
type 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

Comments
 (0)