opentool 0.7.9 → 0.7.11

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/README.md CHANGED
@@ -82,7 +82,7 @@ export const profile = {
82
82
  description: "Stake 100 USDC daily at 12:00 UTC",
83
83
  fixedAmount: "100",
84
84
  tokenSymbol: "USDC",
85
- schedule: { cron: "0 12 * * *", enabled: true },
85
+ schedule: { cron: "0 12 * * *", enabled: false },
86
86
  limits: { concurrency: 1, dailyCap: 1 },
87
87
  };
88
88
 
@@ -115,6 +115,11 @@ export async function POST(req: Request) {
115
115
  }
116
116
  ```
117
117
 
118
+ ### Cron schedules (`profile.schedule`)
119
+
120
+ - GET-only tools require `profile.schedule` with a standard 5–6 field cron expression (e.g., `0 12 * * *` or `0 0 ? * MON-FRI *`).
121
+ - Build validates the cron shape and emits `.well-known/opentool/cron.json` capturing each scheduled tool (`toolName`, `toolPath`, `scheduleExpression`). Enabled defaults to `false` even if authors set it to `true` in code. Deployment targets can translate these cron strings to their provider format (e.g., EventBridge) downstream.
122
+
118
123
  ### Public tools: Add x402 payments (optional)
119
124
 
120
125
  Protect your public tools with crypto payments using x402:
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { M as Metadata, I as InternalToolDefinition } from '../validate-BBjyq5nS.js';
3
- export { G as GenerateMetadataOptions, a as GenerateMetadataResult, V as ValidateOptions, b as generateMetadata, g as generateMetadataCommand, l as loadAndValidateTools, v as validateCommand, c as validateFullCommand } from '../validate-BBjyq5nS.js';
2
+ import { M as Metadata, I as InternalToolDefinition } from '../validate-uetwG5jo.js';
3
+ export { G as GenerateMetadataOptions, a as GenerateMetadataResult, V as ValidateOptions, b as generateMetadata, g as generateMetadataCommand, l as loadAndValidateTools, v as validateCommand, c as validateFullCommand } from '../validate-uetwG5jo.js';
4
4
  import 'zod';
5
5
  import '../x402/index.js';
6
6
  import 'viem';
@@ -17,6 +17,7 @@ interface BuildArtifacts {
17
17
  tools: InternalToolDefinition[];
18
18
  compiledTools: CompiledToolArtifact[];
19
19
  workflowBundles: WorkflowBundleArtifact | null;
20
+ cronManifestPath: string | null;
20
21
  }
21
22
  interface CompiledToolArtifact {
22
23
  name: string;
package/dist/cli/index.js CHANGED
@@ -932,6 +932,40 @@ var HTTP_METHODS = [
932
932
  "OPTIONS"
933
933
  ];
934
934
 
935
+ // src/utils/schedule.ts
936
+ var CRON_WRAPPED_REGEX = /^cron\((.*)\)$/i;
937
+ var CRON_TOKEN_REGEX = /^[A-Za-z0-9*?/,\-#L]+$/;
938
+ function normalizeScheduleExpression(raw, context) {
939
+ const value = raw?.trim();
940
+ if (!value) {
941
+ throw new Error(`${context}: profile.schedule.cron must be a non-empty string`);
942
+ }
943
+ const cronBody = extractCronBody(value);
944
+ const cronFields = cronBody.trim().split(/\s+/).filter(Boolean);
945
+ if (cronFields.length !== 5 && cronFields.length !== 6) {
946
+ throw new Error(`${context}: cron expression must have 5 or 6 fields (got ${cronFields.length})`);
947
+ }
948
+ validateCronTokens(cronFields, context);
949
+ return {
950
+ type: "cron",
951
+ expression: cronFields.join(" ")
952
+ };
953
+ }
954
+ function extractCronBody(value) {
955
+ const cronMatch = CRON_WRAPPED_REGEX.exec(value);
956
+ if (cronMatch) {
957
+ return (cronMatch[1] ?? "").trim();
958
+ }
959
+ return value;
960
+ }
961
+ function validateCronTokens(fields, context) {
962
+ fields.forEach((token, idx) => {
963
+ if (!CRON_TOKEN_REGEX.test(token)) {
964
+ throw new Error(`${context}: invalid cron token "${token}" at position ${idx + 1}`);
965
+ }
966
+ });
967
+ }
968
+
935
969
  // src/cli/validate.ts
936
970
  var SUPPORTED_EXTENSIONS = [
937
971
  ".ts",
@@ -1051,11 +1085,16 @@ async function loadAndValidateTools(toolsDir, options = {}) {
1051
1085
  if (hasGET === hasPOST) {
1052
1086
  throw new Error(`${file}: export exactly one of GET or POST`);
1053
1087
  }
1088
+ let normalizedSchedule = null;
1054
1089
  if (hasGET) {
1055
1090
  const schedule = toolModule?.profile?.schedule;
1056
1091
  if (!schedule || typeof schedule?.cron !== "string" || schedule.cron.trim().length === 0) {
1057
1092
  throw new Error(`${file}: GET tools require profile.schedule { cron }`);
1058
1093
  }
1094
+ normalizedSchedule = normalizeScheduleExpression(schedule.cron, file);
1095
+ if (typeof schedule.enabled === "boolean") {
1096
+ normalizedSchedule.authoredEnabled = schedule.enabled;
1097
+ }
1059
1098
  }
1060
1099
  if (hasPOST) {
1061
1100
  if (!schema) {
@@ -1112,7 +1151,9 @@ async function loadAndValidateTools(toolsDir, options = {}) {
1112
1151
  filename: toBaseName(file),
1113
1152
  sourcePath: path5.join(toolsDir, file),
1114
1153
  handler: async (params) => adapter(params),
1115
- payment: paymentExport ?? null
1154
+ payment: paymentExport ?? null,
1155
+ schedule: normalizedSchedule,
1156
+ profileDescription: typeof toolModule?.profile?.description === "string" ? toolModule.profile?.description ?? null : null
1116
1157
  };
1117
1158
  tools.push(tool);
1118
1159
  }
@@ -1334,6 +1375,11 @@ async function buildProject(options) {
1334
1375
  projectRoot,
1335
1376
  outputDir
1336
1377
  });
1378
+ const cronManifestPath = await writeCronManifest({
1379
+ tools,
1380
+ compiledTools,
1381
+ outputDir
1382
+ });
1337
1383
  const workflowBundles = await buildWorkflowsIfPresent({
1338
1384
  projectRoot,
1339
1385
  outputDir
@@ -1356,7 +1402,8 @@ async function buildProject(options) {
1356
1402
  defaultsApplied,
1357
1403
  tools,
1358
1404
  compiledTools,
1359
- workflowBundles
1405
+ workflowBundles,
1406
+ cronManifestPath
1360
1407
  };
1361
1408
  }
1362
1409
  async function emitTools(tools, config) {
@@ -1499,6 +1546,55 @@ async function writeMcpServer(options) {
1499
1546
  fs4.writeFileSync(serverPath, serverCode);
1500
1547
  fs4.chmodSync(serverPath, 493);
1501
1548
  }
1549
+ function writeCronManifest(options) {
1550
+ const scheduledTools = options.tools.filter((tool) => tool.schedule?.expression);
1551
+ const manifestDir = path5.join(options.outputDir, ".well-known", "opentool");
1552
+ const manifestPath = path5.join(manifestDir, "cron.json");
1553
+ if (scheduledTools.length === 0) {
1554
+ if (fs4.existsSync(manifestPath)) {
1555
+ fs4.rmSync(manifestPath);
1556
+ }
1557
+ return null;
1558
+ }
1559
+ const entries = scheduledTools.map((tool) => {
1560
+ const schedule = tool.schedule;
1561
+ if (!schedule) {
1562
+ throw new Error(`Internal error: missing schedule for tool ${tool.filename}`);
1563
+ }
1564
+ const compiled = options.compiledTools.find(
1565
+ (artifact) => artifact.filename === tool.filename
1566
+ );
1567
+ if (!compiled) {
1568
+ throw new Error(`Internal error: missing compiled artifact for ${tool.filename}`);
1569
+ }
1570
+ const toolName = tool.metadata?.name ?? tool.filename;
1571
+ const description = tool.metadata?.description ?? tool.profileDescription ?? void 0;
1572
+ const payloadPath = compiled.modulePath.replace(/\\/g, "/");
1573
+ const entry = {
1574
+ toolName,
1575
+ scheduleType: schedule.type,
1576
+ scheduleExpression: schedule.expression,
1577
+ enabledDefault: false,
1578
+ ...schedule.authoredEnabled !== void 0 ? { authoredEnabled: schedule.authoredEnabled } : {},
1579
+ payload: {
1580
+ toolPath: payloadPath,
1581
+ httpMethod: "GET"
1582
+ }
1583
+ };
1584
+ if (description !== void 0) {
1585
+ entry.description = description;
1586
+ }
1587
+ return entry;
1588
+ });
1589
+ const manifest = {
1590
+ version: 1,
1591
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1592
+ entries
1593
+ };
1594
+ fs4.mkdirSync(manifestDir, { recursive: true });
1595
+ fs4.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
1596
+ return manifestPath;
1597
+ }
1502
1598
  function logBuildSummary(artifacts, options) {
1503
1599
  const end = timestamp();
1504
1600
  console.log(`[${end}] Build completed successfully!`);
@@ -1515,6 +1611,9 @@ function logBuildSummary(artifacts, options) {
1515
1611
  console.log(` - ${tool.name} [${methods}]${walletBadge}`);
1516
1612
  });
1517
1613
  console.log(" \u2022 metadata.json (registry artifact)");
1614
+ if (artifacts.cronManifestPath) {
1615
+ console.log(" \u2022 .well-known/opentool/cron.json (cron manifest)");
1616
+ }
1518
1617
  if (artifacts.workflowBundles) {
1519
1618
  console.log(" \u2022 .well-known/workflow/v1/ (workflow bundles)");
1520
1619
  console.log(" - flow.js");