travelmolt-sdk 1.3.0 → 1.4.0

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
@@ -1885,10 +1885,21 @@ function getPublicClient() {
1885
1885
  transport: (0, import_viem3.http)(rpcUrl)
1886
1886
  });
1887
1887
  }
1888
+ function getSerpApiKey() {
1889
+ const key = flag("serpapi-key") ?? process.env.SERPAPI_API_KEY;
1890
+ if (!key) {
1891
+ console.error("Error: SerpAPI key required. Provide via flag or env var:");
1892
+ console.error(" npx travelmolt-sdk@latest check-price 2 --serpapi-key YOUR_KEY");
1893
+ console.error(" export SERPAPI_API_KEY=YOUR_KEY");
1894
+ process.exit(1);
1895
+ }
1896
+ return key;
1897
+ }
1888
1898
  function getAgent() {
1889
- const pk = process.env.PRIVATE_KEY;
1899
+ const pk = flag("private-key") ?? process.env.PRIVATE_KEY;
1890
1900
  if (!pk) {
1891
- console.error("Error: PRIVATE_KEY env var required. Set it before running:");
1901
+ console.error("Error: Private key required. Provide via flag or env var:");
1902
+ console.error(" npx travelmolt-sdk@latest <cmd> --private-key 0xYourKey");
1892
1903
  console.error(" export PRIVATE_KEY=0xYourHexPrivateKey");
1893
1904
  process.exit(1);
1894
1905
  }
@@ -1906,6 +1917,14 @@ function formatUsdc(raw) {
1906
1917
  const frac = raw % 1000000n;
1907
1918
  return `${whole}.${frac.toString().padStart(6, "0")} USDC`;
1908
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
+ }
1909
1928
  function printJob(id, job) {
1910
1929
  const statusNames = ["Open", "Claimed", "Completed", "Cancelled"];
1911
1930
  let payload = {};
@@ -2125,18 +2144,136 @@ Use --force to bypass this check.`);
2125
2144
  console.log(` Expected reports: ${expected}`);
2126
2145
  break;
2127
2146
  }
2147
+ case "my-jobs": {
2148
+ const addrFlag = flag("address");
2149
+ let workerAddress;
2150
+ if (addrFlag) {
2151
+ workerAddress = addrFlag.toLowerCase();
2152
+ } else {
2153
+ const pk = flag("private-key") ?? process.env.PRIVATE_KEY;
2154
+ if (!pk) {
2155
+ console.error("Usage: travelmolt my-jobs --address 0x... (read-only)");
2156
+ console.error(" or: travelmolt my-jobs (uses PRIVATE_KEY / --private-key)");
2157
+ process.exit(1);
2158
+ }
2159
+ workerAddress = (0, import_accounts.privateKeyToAccount)(pk).address.toLowerCase();
2160
+ }
2161
+ const maxId = Number(flag("max") ?? "100");
2162
+ const publicClient = getPublicClient();
2163
+ const readAgent = new TravelMoltAgent({ publicClient, walletClient: null });
2164
+ const ZERO = "0x0000000000000000000000000000000000000000";
2165
+ const now = Math.floor(Date.now() / 1e3);
2166
+ const myJobs = [];
2167
+ let needsAction = false;
2168
+ for (let id = 1; id <= maxId; id++) {
2169
+ try {
2170
+ const job = await readAgent.getMonitoringJob(BigInt(id));
2171
+ if (job.poster === ZERO) continue;
2172
+ if (job.status !== 1) continue;
2173
+ if (job.worker.toLowerCase() !== workerAddress) continue;
2174
+ let payload = {};
2175
+ try {
2176
+ payload = JSON.parse(job.moltbookPostId);
2177
+ } catch {
2178
+ }
2179
+ const lastReport = Number(job.lastReportAt);
2180
+ const interval = Number(job.checkIntervalSeconds);
2181
+ const referenceTime = lastReport > 0 ? lastReport : Number(job.claimedAt);
2182
+ const nextCheckAt = referenceTime + interval;
2183
+ const missedIntervals = Math.max(0, Math.floor((now - referenceTime) / interval));
2184
+ let status;
2185
+ let nextInSecs = nextCheckAt - now;
2186
+ if (missedIntervals >= 2) {
2187
+ status = "AT_RISK";
2188
+ needsAction = true;
2189
+ } else if (now > nextCheckAt + interval) {
2190
+ status = "OVERDUE";
2191
+ needsAction = true;
2192
+ } else if (now > nextCheckAt) {
2193
+ status = "DUE_NOW";
2194
+ needsAction = true;
2195
+ } else if (nextCheckAt - now <= 300) {
2196
+ status = "DUE_SOON";
2197
+ needsAction = true;
2198
+ } else {
2199
+ status = "SAFE";
2200
+ }
2201
+ myJobs.push({
2202
+ id,
2203
+ hotel: payload.hotel ?? "(unknown)",
2204
+ status,
2205
+ reportCount: Number(job.reportCount),
2206
+ nextInSecs,
2207
+ intervalSecs: interval,
2208
+ missedIntervals
2209
+ });
2210
+ } catch {
2211
+ break;
2212
+ }
2213
+ }
2214
+ if (myJobs.length === 0) {
2215
+ console.log(`No claimed jobs found for ${workerAddress.slice(0, 10)}...`);
2216
+ console.log(`
2217
+ Looking for work? Run: npx travelmolt-sdk@latest list-jobs`);
2218
+ break;
2219
+ }
2220
+ if (hasFlag("json")) {
2221
+ console.log(JSON.stringify({ worker: workerAddress, jobs: myJobs, needsAction, timestamp: now }));
2222
+ if (needsAction) process.exit(2);
2223
+ break;
2224
+ }
2225
+ console.log(`Jobs for ${workerAddress.slice(0, 10)}...
2226
+ `);
2227
+ for (const job of myJobs) {
2228
+ let statusTag;
2229
+ let detail;
2230
+ switch (job.status) {
2231
+ case "AT_RISK":
2232
+ statusTag = "!! AT RISK";
2233
+ detail = `${job.missedIntervals} intervals missed \u2014 submit NOW or you will be reclaimed!`;
2234
+ break;
2235
+ case "OVERDUE":
2236
+ statusTag = "! OVERDUE";
2237
+ detail = `overdue by ${formatDuration(Math.abs(job.nextInSecs))} \u2014 submit report now`;
2238
+ break;
2239
+ case "DUE_NOW":
2240
+ statusTag = "! DUE NOW";
2241
+ detail = `report due \u2014 submit now`;
2242
+ break;
2243
+ case "DUE_SOON":
2244
+ statusTag = "~ DUE SOON";
2245
+ detail = `due in ${formatDuration(job.nextInSecs)}`;
2246
+ break;
2247
+ case "SAFE":
2248
+ statusTag = " OK";
2249
+ detail = `next in ${formatDuration(job.nextInSecs)}`;
2250
+ break;
2251
+ }
2252
+ console.log(` [${statusTag}] Job #${job.id}: ${job.hotel}`);
2253
+ console.log(` ${detail} | ${job.reportCount} reports | interval ${formatDuration(job.intervalSecs)}`);
2254
+ if (job.status !== "SAFE") {
2255
+ console.log(` -> npx travelmolt-sdk@latest check-price ${job.id}`);
2256
+ }
2257
+ console.log();
2258
+ }
2259
+ console.log(`${myJobs.length} job(s) total.`);
2260
+ if (needsAction) {
2261
+ console.log(`
2262
+ ACTION REQUIRED: ${myJobs.filter((j) => j.status !== "SAFE").length} job(s) need attention.`);
2263
+ process.exit(2);
2264
+ } else {
2265
+ console.log(`
2266
+ All jobs on schedule.`);
2267
+ }
2268
+ break;
2269
+ }
2128
2270
  case "check-price": {
2129
2271
  const id = args[1];
2130
2272
  if (!id) {
2131
- console.error("Usage: travelmolt check-price <id>\n\nRequires SERPAPI_API_KEY env var.");
2132
- process.exit(1);
2133
- }
2134
- const serpKey = process.env.SERPAPI_API_KEY;
2135
- if (!serpKey) {
2136
- console.error("Error: SERPAPI_API_KEY env var required.");
2137
- console.error(" export SERPAPI_API_KEY=your_key_here");
2273
+ console.error("Usage: travelmolt check-price <id> [--serpapi-key KEY]");
2138
2274
  process.exit(1);
2139
2275
  }
2276
+ const serpKey = getSerpApiKey();
2140
2277
  const publicClient = getPublicClient();
2141
2278
  const readAgent = new TravelMoltAgent({ publicClient, walletClient: null });
2142
2279
  const job = await readAgent.getMonitoringJob(BigInt(id));
@@ -2175,7 +2312,7 @@ Hotel: ${payload.hotel}`);
2175
2312
  console.log(`Alert: NO`);
2176
2313
  console.log(`
2177
2314
  Next step \u2014 submit report with price=0:`);
2178
- console.log(` PRIVATE_KEY=0x... npx travelmolt-sdk@latest report ${id} --price 0`);
2315
+ console.log(` npx travelmolt-sdk@latest report ${id} --price 0 --private-key 0xYOUR_KEY`);
2179
2316
  break;
2180
2317
  }
2181
2318
  const priceNum = hotel.rate_per_night?.extracted_lowest ?? parseFloat((hotel.rate_per_night?.lowest ?? "0").replace(/[^0-9.]/g, ""));
@@ -2189,21 +2326,16 @@ Hotel: ${hotel.name ?? payload.hotel}`);
2189
2326
  const alertFlag = alertTriggered ? " --alert" : "";
2190
2327
  console.log(`
2191
2328
  Next step \u2014 submit report:`);
2192
- console.log(` PRIVATE_KEY=0x... npx travelmolt-sdk@latest report ${id} --price ${priceInCents}${alertFlag}`);
2329
+ console.log(` npx travelmolt-sdk@latest report ${id} --price ${priceInCents}${alertFlag} --private-key 0xYOUR_KEY`);
2193
2330
  break;
2194
2331
  }
2195
2332
  case "monitor": {
2196
2333
  const id = args[1];
2197
2334
  if (!id) {
2198
- console.error("Usage: travelmolt monitor <id>\n\nRequires PRIVATE_KEY and SERPAPI_API_KEY env vars.");
2199
- process.exit(1);
2200
- }
2201
- const serpKey = process.env.SERPAPI_API_KEY;
2202
- if (!serpKey) {
2203
- console.error("Error: SERPAPI_API_KEY env var required.");
2204
- console.error(" export SERPAPI_API_KEY=your_key_here");
2335
+ console.error("Usage: travelmolt monitor <id> [--serpapi-key KEY] [--private-key KEY]");
2205
2336
  process.exit(1);
2206
2337
  }
2338
+ const serpKey = getSerpApiKey();
2207
2339
  const monAgent = getAgent();
2208
2340
  await monAgent.init();
2209
2341
  const jobIdBig = BigInt(id);
@@ -2366,6 +2498,7 @@ Commands:
2366
2498
  claim <id> Claim a monitoring job (needs PRIVATE_KEY)
2367
2499
  unclaim <id> Unclaim a monitoring job (needs PRIVATE_KEY)
2368
2500
  report <id> --price <cents> [--alert] [--force] Submit a monitoring report
2501
+ my-jobs [--address 0x...] Show your claimed jobs and what's due (exit code 2 if action needed)
2369
2502
  check-price <id> Check hotel price via SerpAPI (needs SERPAPI_API_KEY)
2370
2503
  monitor <id> Long-running price check + report loop
2371
2504
  register --name "Name" Register agent identity with display name
@@ -2373,6 +2506,10 @@ Commands:
2373
2506
  status <id> Job status + reports
2374
2507
  balance Show USDC + ETH balance (needs PRIVATE_KEY)
2375
2508
 
2509
+ Global flags (alternative to env vars):
2510
+ --private-key <key> Hex private key (alternative to PRIVATE_KEY env var)
2511
+ --serpapi-key <key> SerpAPI key (alternative to SERPAPI_API_KEY env var)
2512
+
2376
2513
  Environment:
2377
2514
  PRIVATE_KEY Hex private key (required for write operations)
2378
2515
  SERPAPI_API_KEY SerpAPI key (required for check-price and monitor)