travelmolt-sdk 1.3.1 → 1.4.1

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/dist/cli.cjs CHANGED
@@ -1917,6 +1917,14 @@ function formatUsdc(raw) {
1917
1917
  const frac = raw % 1000000n;
1918
1918
  return `${whole}.${frac.toString().padStart(6, "0")} USDC`;
1919
1919
  }
1920
+ function formatDuration(secs) {
1921
+ const abs = Math.abs(secs);
1922
+ if (abs < 60) return `${abs}s`;
1923
+ if (abs < 3600) return `${Math.floor(abs / 60)}m`;
1924
+ const h = Math.floor(abs / 3600);
1925
+ const m = Math.floor(abs % 3600 / 60);
1926
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
1927
+ }
1920
1928
  function printJob(id, job) {
1921
1929
  const statusNames = ["Open", "Claimed", "Completed", "Cancelled"];
1922
1930
  let payload = {};
@@ -2064,6 +2072,35 @@ Use --force to bypass this check.`);
2064
2072
  console.log(`Unclaimed! tx: ${hash}`);
2065
2073
  break;
2066
2074
  }
2075
+ case "reclaim": {
2076
+ const id = args[1];
2077
+ if (!id) {
2078
+ console.error("Usage: travelmolt reclaim <id>");
2079
+ process.exit(1);
2080
+ }
2081
+ const agent = getAgent();
2082
+ await agent.init();
2083
+ const job = await agent.getMonitoringJob(BigInt(id));
2084
+ const statusNames = ["Open", "Claimed", "Completed", "Cancelled"];
2085
+ if (job.status !== 1) {
2086
+ console.error(`Job #${id} is ${statusNames[job.status] ?? "Unknown"}, not Claimed. Cannot reclaim.`);
2087
+ process.exit(1);
2088
+ }
2089
+ const refTime = Number(job.lastReportAt) > 0 ? Number(job.lastReportAt) : Number(job.claimedAt);
2090
+ const now = Math.floor(Date.now() / 1e3);
2091
+ const interval = Number(job.checkIntervalSeconds);
2092
+ const missed = Math.floor((now - refTime) / interval);
2093
+ if (missed < 2) {
2094
+ console.error(`Job #${id} has only missed ${missed} interval(s). Need >= 2 to reclaim.`);
2095
+ console.error(`Reclaimable after: ${new Date((refTime + interval * 2) * 1e3).toISOString()}`);
2096
+ process.exit(1);
2097
+ }
2098
+ console.log(`Reclaiming job #${id} (${missed} intervals missed by ${job.worker})...`);
2099
+ const hash = await agent.reclaimMonitoringJob(BigInt(id));
2100
+ console.log(`Reclaimed! Job #${id} is now Open. tx: ${hash}`);
2101
+ console.log(`You can now claim it: npx travelmolt-sdk@latest claim ${id}`);
2102
+ break;
2103
+ }
2067
2104
  case "rename": {
2068
2105
  const newName = args[1];
2069
2106
  if (!newName) {
@@ -2136,6 +2173,129 @@ Use --force to bypass this check.`);
2136
2173
  console.log(` Expected reports: ${expected}`);
2137
2174
  break;
2138
2175
  }
2176
+ case "my-jobs": {
2177
+ const addrFlag = flag("address");
2178
+ let workerAddress;
2179
+ if (addrFlag) {
2180
+ workerAddress = addrFlag.toLowerCase();
2181
+ } else {
2182
+ const pk = flag("private-key") ?? process.env.PRIVATE_KEY;
2183
+ if (!pk) {
2184
+ console.error("Usage: travelmolt my-jobs --address 0x... (read-only)");
2185
+ console.error(" or: travelmolt my-jobs (uses PRIVATE_KEY / --private-key)");
2186
+ process.exit(1);
2187
+ }
2188
+ workerAddress = (0, import_accounts.privateKeyToAccount)(pk).address.toLowerCase();
2189
+ }
2190
+ const maxId = Number(flag("max") ?? "100");
2191
+ const publicClient = getPublicClient();
2192
+ const readAgent = new TravelMoltAgent({ publicClient, walletClient: null });
2193
+ const ZERO = "0x0000000000000000000000000000000000000000";
2194
+ const now = Math.floor(Date.now() / 1e3);
2195
+ const myJobs = [];
2196
+ let needsAction = false;
2197
+ for (let id = 1; id <= maxId; id++) {
2198
+ try {
2199
+ const job = await readAgent.getMonitoringJob(BigInt(id));
2200
+ if (job.poster === ZERO) continue;
2201
+ if (job.status !== 1) continue;
2202
+ if (job.worker.toLowerCase() !== workerAddress) continue;
2203
+ let payload = {};
2204
+ try {
2205
+ payload = JSON.parse(job.moltbookPostId);
2206
+ } catch {
2207
+ }
2208
+ const lastReport = Number(job.lastReportAt);
2209
+ const interval = Number(job.checkIntervalSeconds);
2210
+ const referenceTime = lastReport > 0 ? lastReport : Number(job.claimedAt);
2211
+ const nextCheckAt = referenceTime + interval;
2212
+ const missedIntervals = Math.max(0, Math.floor((now - referenceTime) / interval));
2213
+ let status;
2214
+ let nextInSecs = nextCheckAt - now;
2215
+ if (missedIntervals >= 2) {
2216
+ status = "AT_RISK";
2217
+ needsAction = true;
2218
+ } else if (now > nextCheckAt + interval) {
2219
+ status = "OVERDUE";
2220
+ needsAction = true;
2221
+ } else if (now > nextCheckAt) {
2222
+ status = "DUE_NOW";
2223
+ needsAction = true;
2224
+ } else if (nextCheckAt - now <= 300) {
2225
+ status = "DUE_SOON";
2226
+ needsAction = true;
2227
+ } else {
2228
+ status = "SAFE";
2229
+ }
2230
+ myJobs.push({
2231
+ id,
2232
+ hotel: payload.hotel ?? "(unknown)",
2233
+ status,
2234
+ reportCount: Number(job.reportCount),
2235
+ nextInSecs,
2236
+ intervalSecs: interval,
2237
+ missedIntervals
2238
+ });
2239
+ } catch {
2240
+ break;
2241
+ }
2242
+ }
2243
+ if (myJobs.length === 0) {
2244
+ console.log(`No claimed jobs found for ${workerAddress.slice(0, 10)}...`);
2245
+ console.log(`
2246
+ Looking for work? Run: npx travelmolt-sdk@latest list-jobs`);
2247
+ break;
2248
+ }
2249
+ if (hasFlag("json")) {
2250
+ console.log(JSON.stringify({ worker: workerAddress, jobs: myJobs, needsAction, timestamp: now }));
2251
+ if (needsAction) process.exit(2);
2252
+ break;
2253
+ }
2254
+ console.log(`Jobs for ${workerAddress.slice(0, 10)}...
2255
+ `);
2256
+ for (const job of myJobs) {
2257
+ let statusTag;
2258
+ let detail;
2259
+ switch (job.status) {
2260
+ case "AT_RISK":
2261
+ statusTag = "!! AT RISK";
2262
+ detail = `${job.missedIntervals} intervals missed \u2014 submit NOW or you will be reclaimed!`;
2263
+ break;
2264
+ case "OVERDUE":
2265
+ statusTag = "! OVERDUE";
2266
+ detail = `overdue by ${formatDuration(Math.abs(job.nextInSecs))} \u2014 submit report now`;
2267
+ break;
2268
+ case "DUE_NOW":
2269
+ statusTag = "! DUE NOW";
2270
+ detail = `report due \u2014 submit now`;
2271
+ break;
2272
+ case "DUE_SOON":
2273
+ statusTag = "~ DUE SOON";
2274
+ detail = `due in ${formatDuration(job.nextInSecs)}`;
2275
+ break;
2276
+ case "SAFE":
2277
+ statusTag = " OK";
2278
+ detail = `next in ${formatDuration(job.nextInSecs)}`;
2279
+ break;
2280
+ }
2281
+ console.log(` [${statusTag}] Job #${job.id}: ${job.hotel}`);
2282
+ console.log(` ${detail} | ${job.reportCount} reports | interval ${formatDuration(job.intervalSecs)}`);
2283
+ if (job.status !== "SAFE") {
2284
+ console.log(` -> npx travelmolt-sdk@latest check-price ${job.id}`);
2285
+ }
2286
+ console.log();
2287
+ }
2288
+ console.log(`${myJobs.length} job(s) total.`);
2289
+ if (needsAction) {
2290
+ console.log(`
2291
+ ACTION REQUIRED: ${myJobs.filter((j) => j.status !== "SAFE").length} job(s) need attention.`);
2292
+ process.exit(2);
2293
+ } else {
2294
+ console.log(`
2295
+ All jobs on schedule.`);
2296
+ }
2297
+ break;
2298
+ }
2139
2299
  case "check-price": {
2140
2300
  const id = args[1];
2141
2301
  if (!id) {
@@ -2365,8 +2525,10 @@ Commands:
2365
2525
  get-job <id> Show job details
2366
2526
  post-job --hotel "..." ... Create a monitoring job (needs PRIVATE_KEY)
2367
2527
  claim <id> Claim a monitoring job (needs PRIVATE_KEY)
2528
+ reclaim <id> Reclaim a stuck job (>= 2 missed intervals, then claim it)
2368
2529
  unclaim <id> Unclaim a monitoring job (needs PRIVATE_KEY)
2369
2530
  report <id> --price <cents> [--alert] [--force] Submit a monitoring report
2531
+ my-jobs [--address 0x...] Show your claimed jobs and what's due (exit code 2 if action needed)
2370
2532
  check-price <id> Check hotel price via SerpAPI (needs SERPAPI_API_KEY)
2371
2533
  monitor <id> Long-running price check + report loop
2372
2534
  register --name "Name" Register agent identity with display name