opentool 0.8.27 → 0.8.28

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
@@ -74,6 +74,10 @@ For private tools, say for internal trading apps:
74
74
  - GET-only (scheduled default profile)
75
75
  - POST-only (one-off, parameterized with Zod)
76
76
  - `profile.category` defaults to `tracker` if omitted; set to `strategy` or `orchestrator` for PnL/automation tools.
77
+ - Strategy tools must define `profile.templatePreview` with:
78
+ - `subtitle` (required short line)
79
+ - `description` (required multi-line summary, 3-8 non-empty lines; target ~5)
80
+ - `title` is optional and defaults to the tool name when omitted.
77
81
 
78
82
  GET-only (scheduled default)
79
83
 
@@ -83,6 +87,16 @@ export const profile = {
83
87
  description: "Stake 100 USDC daily at 12:00 UTC",
84
88
  category: "strategy",
85
89
  schedule: { cron: "0 12 * * *", enabled: false },
90
+ templatePreview: {
91
+ subtitle: "Automated daily staking strategy",
92
+ description: [
93
+ "Runs once per day on your configured schedule.",
94
+ "Uses fixed, explicit sizing controls from template config.",
95
+ "Designed for long-running automated execution.",
96
+ "Keeps logic deterministic and easy to audit.",
97
+ "Best for hands-off recurring onchain actions.",
98
+ ].join("\\n"),
99
+ },
86
100
  };
87
101
 
88
102
  export async function GET(_req: Request) {
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { M as Metadata, I as InternalToolDefinition } from '../validate-C4a9tmrQ.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-C4a9tmrQ.js';
2
+ import { M as Metadata, I as InternalToolDefinition } from '../validate-3WEA0Ezt.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-3WEA0Ezt.js';
4
4
  import 'zod';
5
5
  import '../x402/index.js';
6
6
  import 'viem';
package/dist/cli/index.js CHANGED
@@ -1002,6 +1002,11 @@ var SUPPORTED_EXTENSIONS = [
1002
1002
  ".cjs"
1003
1003
  ];
1004
1004
  var MIN_TEMPLATE_CONFIG_VERSION = 2;
1005
+ var TEMPLATE_PREVIEW_TITLE_MAX = 80;
1006
+ var TEMPLATE_PREVIEW_SUBTITLE_MAX = 120;
1007
+ var TEMPLATE_PREVIEW_DESCRIPTION_MAX = 1200;
1008
+ var TEMPLATE_PREVIEW_MIN_LINES = 3;
1009
+ var TEMPLATE_PREVIEW_MAX_LINES = 8;
1005
1010
  function normalizeTemplateConfigVersion(value) {
1006
1011
  if (typeof value === "number" && Number.isFinite(value)) {
1007
1012
  return value;
@@ -1024,6 +1029,67 @@ function normalizeTemplateConfigVersion(value) {
1024
1029
  const major = Number.parseInt(majorMatch[1], 10);
1025
1030
  return Number.isFinite(major) ? major : null;
1026
1031
  }
1032
+ function parseNonEmptyString(value, fieldPath, opts = {}) {
1033
+ const { max, required = false } = opts;
1034
+ if (value == null) {
1035
+ if (required) {
1036
+ throw new Error(`${fieldPath} is required and must be a non-empty string.`);
1037
+ }
1038
+ return null;
1039
+ }
1040
+ if (typeof value !== "string") {
1041
+ throw new Error(`${fieldPath} must be a string.`);
1042
+ }
1043
+ const trimmed = value.trim();
1044
+ if (!trimmed) {
1045
+ throw new Error(`${fieldPath} must be a non-empty string.`);
1046
+ }
1047
+ if (typeof max === "number" && trimmed.length > max) {
1048
+ throw new Error(`${fieldPath} must be <= ${max} characters.`);
1049
+ }
1050
+ return trimmed;
1051
+ }
1052
+ function normalizeTemplatePreview(value, file, toolName, requirePreview) {
1053
+ const pathPrefix = `${file}: profile.templatePreview`;
1054
+ if (value == null) {
1055
+ if (requirePreview) {
1056
+ throw new Error(
1057
+ `${pathPrefix} is required for strategy tools and must define subtitle + description.`
1058
+ );
1059
+ }
1060
+ return null;
1061
+ }
1062
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1063
+ throw new Error(`${pathPrefix} must be an object.`);
1064
+ }
1065
+ const record = value;
1066
+ const title = parseNonEmptyString(record.title, `${pathPrefix}.title`, {
1067
+ max: TEMPLATE_PREVIEW_TITLE_MAX
1068
+ }) ?? toolName;
1069
+ const subtitle = parseNonEmptyString(record.subtitle, `${pathPrefix}.subtitle`, {
1070
+ required: true,
1071
+ max: TEMPLATE_PREVIEW_SUBTITLE_MAX
1072
+ });
1073
+ const description = parseNonEmptyString(
1074
+ record.description,
1075
+ `${pathPrefix}.description`,
1076
+ {
1077
+ required: true,
1078
+ max: TEMPLATE_PREVIEW_DESCRIPTION_MAX
1079
+ }
1080
+ );
1081
+ const descriptionLineCount = description.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).length;
1082
+ if (descriptionLineCount < TEMPLATE_PREVIEW_MIN_LINES || descriptionLineCount > TEMPLATE_PREVIEW_MAX_LINES) {
1083
+ throw new Error(
1084
+ `${pathPrefix}.description must contain ${TEMPLATE_PREVIEW_MIN_LINES}-${TEMPLATE_PREVIEW_MAX_LINES} non-empty lines (target ~5 lines).`
1085
+ );
1086
+ }
1087
+ return {
1088
+ title,
1089
+ subtitle,
1090
+ description
1091
+ };
1092
+ }
1027
1093
  async function validateCommand(options) {
1028
1094
  console.log("\u{1F50D} Validating OpenTool metadata...");
1029
1095
  try {
@@ -1135,16 +1201,26 @@ async function loadAndValidateTools(toolsDir, options = {}) {
1135
1201
  throw new Error(`${file}: export exactly one of GET or POST`);
1136
1202
  }
1137
1203
  let normalizedSchedule = null;
1138
- const schedule = toolModule?.profile?.schedule;
1139
- const profileNotifyEmail = typeof toolModule?.profile?.notifyEmail === "boolean" ? toolModule.profile.notifyEmail : void 0;
1140
- const profileCategoryRaw = typeof toolModule?.profile?.category === "string" ? toolModule.profile.category : void 0;
1141
- const allowedProfileCategories = /* @__PURE__ */ new Set(["strategy", "tracker", "orchestrator"]);
1142
- if (profileCategoryRaw && !allowedProfileCategories.has(profileCategoryRaw)) {
1143
- throw new Error(
1144
- `${file}: profile.category must be one of ${Array.from(allowedProfileCategories).join(", ")}`
1145
- );
1204
+ const profileRaw = toolModule?.profile && typeof toolModule.profile === "object" ? toolModule.profile : null;
1205
+ const schedule = profileRaw?.schedule ?? null;
1206
+ const profileNotifyEmail = typeof profileRaw?.notifyEmail === "boolean" ? profileRaw.notifyEmail : void 0;
1207
+ const allowedProfileCategories = [
1208
+ "strategy",
1209
+ "tracker",
1210
+ "orchestrator"
1211
+ ];
1212
+ const profileCategoryCandidate = typeof profileRaw?.category === "string" ? profileRaw.category : void 0;
1213
+ let profileCategoryRaw;
1214
+ if (profileCategoryCandidate !== void 0) {
1215
+ const isAllowed = allowedProfileCategories.includes(profileCategoryCandidate);
1216
+ if (!isAllowed) {
1217
+ throw new Error(
1218
+ `${file}: profile.category must be one of ${allowedProfileCategories.join(", ")}`
1219
+ );
1220
+ }
1221
+ profileCategoryRaw = profileCategoryCandidate;
1146
1222
  }
1147
- const profileAssetsRaw = toolModule?.profile?.assets;
1223
+ const profileAssetsRaw = profileRaw?.assets;
1148
1224
  if (profileAssetsRaw !== void 0) {
1149
1225
  if (!Array.isArray(profileAssetsRaw)) {
1150
1226
  throw new Error(`${file}: profile.assets must be an array.`);
@@ -1202,7 +1278,7 @@ async function loadAndValidateTools(toolsDir, options = {}) {
1202
1278
  }
1203
1279
  });
1204
1280
  }
1205
- const templateConfigRaw = toolModule?.profile?.templateConfig;
1281
+ const templateConfigRaw = profileRaw?.templateConfig;
1206
1282
  if (templateConfigRaw !== void 0) {
1207
1283
  if (!templateConfigRaw || typeof templateConfigRaw !== "object") {
1208
1284
  throw new Error(`${file}: profile.templateConfig must be an object.`);
@@ -1239,6 +1315,13 @@ async function loadAndValidateTools(toolsDir, options = {}) {
1239
1315
  );
1240
1316
  }
1241
1317
  }
1318
+ const normalizedTemplatePreview = normalizeTemplatePreview(
1319
+ profileRaw?.templatePreview,
1320
+ file,
1321
+ toolName,
1322
+ profileCategoryRaw === "strategy"
1323
+ );
1324
+ const normalizedProfile = profileRaw && normalizedTemplatePreview ? { ...profileRaw, templatePreview: normalizedTemplatePreview } : profileRaw;
1242
1325
  if (hasGET && schedule && typeof schedule.cron === "string" && schedule.cron.trim().length > 0) {
1243
1326
  normalizedSchedule = normalizeScheduleExpression(schedule.cron, file);
1244
1327
  if (typeof schedule.enabled === "boolean") {
@@ -1308,9 +1391,9 @@ async function loadAndValidateTools(toolsDir, options = {}) {
1308
1391
  handler: async (params) => adapter(params),
1309
1392
  payment: paymentExport ?? null,
1310
1393
  schedule: normalizedSchedule,
1311
- profile: toolModule?.profile && typeof toolModule.profile === "object" ? toolModule.profile : null,
1394
+ profile: normalizedProfile,
1312
1395
  ...profileNotifyEmail !== void 0 ? { notifyEmail: profileNotifyEmail } : {},
1313
- profileDescription: typeof toolModule?.profile?.description === "string" ? toolModule.profile?.description ?? null : null,
1396
+ profileDescription: typeof profileRaw?.description === "string" ? profileRaw.description : null,
1314
1397
  ...profileCategoryRaw ? { profileCategory: profileCategoryRaw } : {}
1315
1398
  };
1316
1399
  tools.push(tool);