@hedge-layer/cli 1.7.0 → 2.0.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/index.mjs CHANGED
@@ -838,11 +838,12 @@ function registerResearchCommands(program2) {
838
838
  });
839
839
  table(rows, ["ID", "Status", "Brief", "Markets", "Created"]);
840
840
  });
841
- research.command("show <id>").description("Show research session details").action(async (id) => {
841
+ research.command("show <id>").description("Show research session details (accepts the short ID shown by `hl research list`)").action(async (id) => {
842
842
  const globalOpts = program2.opts();
843
843
  const client = new ApiClient(globalOpts);
844
844
  requireAuth3(client);
845
- const assessment = await client.get(`/api/assessments/${id}`);
845
+ const fullId = await resolveOrExit(client, id);
846
+ const assessment = await client.get(`/api/assessments/${fullId}`);
846
847
  if (globalOpts.json) {
847
848
  json(assessment);
848
849
  return;
@@ -857,14 +858,53 @@ function registerResearchCommands(program2) {
857
858
  displayMarketBrief(assessment.market_brief, globalOpts);
858
859
  }
859
860
  });
860
- research.command("delete <id>").description("Delete a research session").action(async (id) => {
861
+ research.command("delete <id>").description("Delete a research session (accepts the short ID shown by `hl research list`)").action(async (id) => {
861
862
  const globalOpts = program2.opts();
862
863
  const client = new ApiClient(globalOpts);
863
864
  requireAuth3(client);
864
- await client.delete(`/api/assessments/${id}`);
865
+ const fullId = await resolveOrExit(client, id);
866
+ await client.delete(`/api/assessments/${fullId}`);
865
867
  success("Research session deleted.");
866
868
  });
867
869
  }
870
+ var FULL_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
871
+ function matchAssessmentId(idOrPrefix, ids) {
872
+ const needle = idOrPrefix.trim().toLowerCase();
873
+ if (!needle) {
874
+ throw new Error("No research session ID provided.");
875
+ }
876
+ const exact = ids.find((id) => id.toLowerCase() === needle);
877
+ if (exact) return exact;
878
+ const matches = ids.filter((id) => id.toLowerCase().startsWith(needle));
879
+ if (matches.length === 1) return matches[0];
880
+ if (matches.length === 0) {
881
+ throw new Error(
882
+ `No research session found matching "${idOrPrefix}". Run \`hl research list\` to see available sessions.`
883
+ );
884
+ }
885
+ const shortIds = matches.map((id) => id.slice(0, 8)).join(", ");
886
+ throw new Error(
887
+ `"${idOrPrefix}" matches ${matches.length} research sessions (${shortIds}). Use a longer ID prefix to disambiguate.`
888
+ );
889
+ }
890
+ async function resolveAssessmentId(client, idOrPrefix) {
891
+ const trimmed = idOrPrefix.trim();
892
+ if (FULL_UUID.test(trimmed)) return trimmed;
893
+ const data = await client.get("/api/assessments", { list: "true" });
894
+ return matchAssessmentId(
895
+ trimmed,
896
+ data.assessments.map((a) => a.id)
897
+ );
898
+ }
899
+ async function resolveOrExit(client, idOrPrefix) {
900
+ try {
901
+ return await resolveAssessmentId(client, idOrPrefix);
902
+ } catch (e) {
903
+ if (e instanceof Error && e.message.includes("API error")) throw e;
904
+ error(e instanceof Error ? e.message : String(e));
905
+ process.exit(1);
906
+ }
907
+ }
868
908
  function requireAuth3(client) {
869
909
  if (!client.isAuthenticated) {
870
910
  error("Not logged in. Run " + bold("hl auth login") + " first.");
@@ -1033,10 +1073,9 @@ function registerFeedCommand(program2) {
1033
1073
  });
1034
1074
  }
1035
1075
 
1036
- // src/commands/allocator.ts
1037
- import { InvalidArgumentError } from "commander";
1038
- import { readFile } from "fs/promises";
1039
- import chalk6 from "chalk";
1076
+ // src/commands/lp.ts
1077
+ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
1078
+ import { writeFile } from "fs/promises";
1040
1079
 
1041
1080
  // src/allocator-display.ts
1042
1081
  import chalk5 from "chalk";
@@ -1060,38 +1099,6 @@ function num(value, fallback = 0) {
1060
1099
  const n = Number(value);
1061
1100
  return Number.isFinite(n) ? n : fallback;
1062
1101
  }
1063
- function feedMarketsToAllocatorMarkets(markets) {
1064
- return markets.slice(0, 25).map((m) => {
1065
- const probability = num(m.probability ?? m.yesPrice, 0.5);
1066
- const daysToEnd = m.daysToEnd == null || m.daysToEnd === void 0 ? void 0 : num(m.daysToEnd);
1067
- return {
1068
- slug: String(m.slug ?? ""),
1069
- question: String(m.question ?? ""),
1070
- yesTokenId: m.yesTokenId ? String(m.yesTokenId) : void 0,
1071
- noTokenId: m.noTokenId ? String(m.noTokenId) : void 0,
1072
- yesPrice: num(m.yesPrice, probability),
1073
- noPrice: num(m.noPrice, 1 - probability),
1074
- liquidity: num(m.liquidity),
1075
- volume24h: num(m.volume24h),
1076
- spread: num(m.spread),
1077
- rewardsDailyRate: num(m.rewardsDailyRate),
1078
- oneDayPriceChange: num(m.oneDayPriceChange),
1079
- ...daysToEnd !== void 0 && { daysToEnd },
1080
- active: m.active == null ? true : Boolean(m.active)
1081
- };
1082
- });
1083
- }
1084
- function allocationsFromDecisions(decisions) {
1085
- return decisions.filter((d) => String(d.market_slug ?? "")).map((d) => ({
1086
- market_slug: String(d.market_slug),
1087
- status: String(d.action ?? "planned").toLowerCase(),
1088
- allocated_capital: num(d.target_capital),
1089
- locked_capital: 0,
1090
- inventory_yes: 0,
1091
- inventory_no: 0,
1092
- open_order_notional: Array.isArray(d.order_plan) ? d.order_plan.reduce((sum, order) => sum + num(order.notional), 0) : 0
1093
- }));
1094
- }
1095
1102
  function displayAllocatorCycleResult(result, globalOpts) {
1096
1103
  if (globalOpts.json) {
1097
1104
  json(result);
@@ -1105,7 +1112,7 @@ function displayAllocatorCycleResult(result, globalOpts) {
1105
1112
  chalk5.dim(
1106
1113
  ` ${num(result.total_markets, decisions.length)} markets \xB7 ${currency(
1107
1114
  num(summary.target_capital)
1108
- )} target capital \xB7 ${num(summary.orders_planned)} planned orders
1115
+ )} target capital
1109
1116
 
1110
1117
  `
1111
1118
  )
@@ -1118,7 +1125,6 @@ function displayAllocatorCycleResult(result, globalOpts) {
1118
1125
  const action = String(d.action ?? "UNKNOWN");
1119
1126
  const score = num(d.score?.score);
1120
1127
  const expected = num(d.score?.expected_return_daily_pct);
1121
- const orders = Array.isArray(d.order_plan) ? d.order_plan.length : 0;
1122
1128
  const failedChecks = Array.isArray(d.safety_checks) ? d.safety_checks.filter((check) => check.passed === false).length : 0;
1123
1129
  return [
1124
1130
  truncate(String(d.question ?? d.market_slug ?? "\u2014"), 42),
@@ -1126,12 +1132,21 @@ function displayAllocatorCycleResult(result, globalOpts) {
1126
1132
  currency(num(d.target_capital)),
1127
1133
  signedCurrency(num(d.capital_delta)),
1128
1134
  `${expected.toFixed(3)}%`,
1135
+ regimeLabel(String(d.quote_regime ?? "\u2014")),
1129
1136
  String(Math.round(score)),
1130
- String(orders),
1131
1137
  failedChecks === 0 ? chalk5.green("0") : chalk5.yellow(String(failedChecks))
1132
1138
  ];
1133
1139
  });
1134
- table(rows, ["Market", "Action", "Target", "Delta", "Exp/day", "Score", "Orders", "Fails"]);
1140
+ table(rows, [
1141
+ "Market",
1142
+ "Action",
1143
+ "Target",
1144
+ "Delta",
1145
+ "Exp/day",
1146
+ "Regime",
1147
+ "Score",
1148
+ "Fails"
1149
+ ]);
1135
1150
  process.stdout.write("\n");
1136
1151
  for (const decision of decisions.slice(0, 5)) {
1137
1152
  const action = String(decision.action ?? "UNKNOWN");
@@ -1141,17 +1156,13 @@ function displayAllocatorCycleResult(result, globalOpts) {
1141
1156
  process.stdout.write(" " + chalk5.dim("Rationale: ") + truncate(decision.rationale, 120));
1142
1157
  }
1143
1158
  process.stdout.write("\n");
1144
- const orders = Array.isArray(decision.order_plan) ? decision.order_plan : [];
1145
- for (const order of orders.slice(0, 2)) {
1159
+ const economics = decision.economics ?? {};
1160
+ if (economics.realized_spread_pnl !== void 0 || economics.reward_income !== void 0 || economics.net_realized_pnl !== void 0) {
1146
1161
  process.stdout.write(
1147
- " " + chalk5.dim("Order: ") + `${String(order.side ?? "BUY")} ${String(order.outcome ?? "?")} @ ${num(order.price).toFixed(3)} for ${currency(num(order.notional))}
1162
+ " " + chalk5.dim("Economics: ") + `spread ${signedCurrency(num(economics.realized_spread_pnl))}, rewards ${signedCurrency(num(economics.reward_income))}, net ${signedCurrency(num(economics.net_realized_pnl))}
1148
1163
  `
1149
1164
  );
1150
1165
  }
1151
- if (orders.length > 2) {
1152
- process.stdout.write(chalk5.dim(` \u2026 ${orders.length - 2} more orders
1153
- `));
1154
- }
1155
1166
  }
1156
1167
  if (decisions.length > 5) {
1157
1168
  process.stdout.write(chalk5.dim(`
@@ -1159,6 +1170,18 @@ function displayAllocatorCycleResult(result, globalOpts) {
1159
1170
  `));
1160
1171
  }
1161
1172
  }
1173
+ function regimeLabel(value) {
1174
+ switch (value) {
1175
+ case "reward_optimized":
1176
+ return chalk5.green("reward");
1177
+ case "defensive":
1178
+ return chalk5.yellow("defense");
1179
+ case "no_quote":
1180
+ return chalk5.red("no quote");
1181
+ default:
1182
+ return chalk5.dim(value);
1183
+ }
1184
+ }
1162
1185
  function signedCurrency(value) {
1163
1186
  const formatted = currency(Math.abs(value));
1164
1187
  if (value > 0) return chalk5.green(`+${formatted}`);
@@ -1167,23 +1190,8 @@ function signedCurrency(value) {
1167
1190
  }
1168
1191
 
1169
1192
  // src/commands/allocator.ts
1170
- var PROFILE_CHOICES2 = ["lp-opportunity", "liquidity-provider", "liquid-new-or-long"];
1171
- function requireAuth5(client) {
1172
- if (!client.isAuthenticated) {
1173
- error("Not authenticated. Run `hl auth login` first.");
1174
- process.exit(1);
1175
- }
1176
- }
1177
- function isProfile2(s) {
1178
- return s !== void 0 && PROFILE_CHOICES2.includes(s);
1179
- }
1180
- function parseNonNegative(value) {
1181
- const n = Number(value);
1182
- if (!Number.isFinite(n) || n < 0) {
1183
- throw new InvalidArgumentError("Expected a non-negative number");
1184
- }
1185
- return n;
1186
- }
1193
+ import { InvalidArgumentError } from "commander";
1194
+ import { readFile } from "fs/promises";
1187
1195
  function parsePositiveNumber(value) {
1188
1196
  const n = Number(value);
1189
1197
  if (!Number.isFinite(n) || n <= 0) {
@@ -1191,61 +1199,6 @@ function parsePositiveNumber(value) {
1191
1199
  }
1192
1200
  return n;
1193
1201
  }
1194
- function parsePositiveInt(value) {
1195
- const n = Number(value);
1196
- if (!Number.isInteger(n) || n < 1) {
1197
- throw new InvalidArgumentError("Expected a positive integer");
1198
- }
1199
- return n;
1200
- }
1201
- function maybeAdd(entries, key, value) {
1202
- if (value !== void 0 && value !== "") entries.push([key, value]);
1203
- }
1204
- function feedQueryParams2(profile, opts) {
1205
- const entries = [];
1206
- maybeAdd(entries, "profile", profile);
1207
- maybeAdd(entries, "sortBy", opts.sortBy);
1208
- maybeAdd(entries, "tag", opts.tag);
1209
- maybeAdd(entries, "minVolume", opts.minVolume);
1210
- maybeAdd(entries, "minLiquidity", opts.minLiquidity);
1211
- maybeAdd(entries, "maxLiquidity", opts.maxLiquidity);
1212
- maybeAdd(entries, "minRewardsDailyRate", opts.minRewardsDailyRate);
1213
- maybeAdd(entries, "minDaysToEnd", opts.minDaysToEnd);
1214
- maybeAdd(entries, "maxDaysToEnd", opts.maxDaysToEnd);
1215
- maybeAdd(entries, "maxMarketAgeHours", opts.maxMarketAgeHours);
1216
- maybeAdd(entries, "liquidProfile", opts.liquidProfile);
1217
- maybeAdd(entries, "limit", opts.limit);
1218
- return Object.fromEntries(entries);
1219
- }
1220
- function strategyFromOptions(opts) {
1221
- return {
1222
- id: "cli-dry-run",
1223
- name: "CLI dry-run LP strategy",
1224
- status: opts.paused ? "paused" : "dry_run",
1225
- ...opts.totalHoldings !== void 0 && { total_holdings: opts.totalHoldings },
1226
- ...opts.capitalLimitPct !== void 0 && { capital_limit_pct: opts.capitalLimitPct },
1227
- ...opts.perMarketLimitPct !== void 0 && { per_market_limit_pct: opts.perMarketLimitPct },
1228
- capital_limit: opts.capitalLimit,
1229
- per_market_limit: opts.perMarketLimit,
1230
- min_expected_return_daily_pct: opts.minExpectedReturnDailyPct,
1231
- max_inventory_imbalance: opts.maxInventoryImbalance,
1232
- max_order_notional: opts.maxOrderNotional,
1233
- quote_edge_bps: opts.quoteEdgeBps,
1234
- min_liquidity: opts.allocatorMinLiquidity,
1235
- max_spread: opts.maxSpread,
1236
- min_days_to_end: opts.allocatorMinDaysToEnd,
1237
- max_markets: opts.maxMarkets
1238
- };
1239
- }
1240
- function validatePercentageSizing(opts) {
1241
- const usesPercentageSizing = opts.capitalLimitPct !== void 0 || opts.perMarketLimitPct !== void 0;
1242
- if (usesPercentageSizing && opts.totalHoldings === void 0) {
1243
- error(
1244
- "Percentage sizing requires --total-holdings so the allocator can convert percentages into dollar caps."
1245
- );
1246
- process.exit(1);
1247
- }
1248
- }
1249
1202
  async function readAllocations(path) {
1250
1203
  if (!path) return [];
1251
1204
  const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
@@ -1255,6 +1208,18 @@ async function readAllocations(path) {
1255
1208
  }
1256
1209
  return parsed;
1257
1210
  }
1211
+ async function readMarketPayload(path) {
1212
+ if (!path) return void 0;
1213
+ const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
1214
+ const parsed = JSON.parse(raw);
1215
+ if (Array.isArray(parsed)) {
1216
+ return parsed;
1217
+ }
1218
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed.markets)) {
1219
+ return parsed.markets;
1220
+ }
1221
+ throw new Error("Markets JSON must be an array or { markets: [...] }");
1222
+ }
1258
1223
  async function readStdin() {
1259
1224
  const chunks = [];
1260
1225
  for await (const chunk of process.stdin) {
@@ -1262,92 +1227,18 @@ async function readStdin() {
1262
1227
  }
1263
1228
  return Buffer.concat(chunks).toString("utf8");
1264
1229
  }
1265
- async function runAllocatorCycle(client, payload) {
1266
- return client.post("/api/allocator/cycle", payload);
1267
- }
1268
- function registerAllocatorCommands(program2) {
1269
- const allocator = program2.command("allocator").description("Run dry-run liquidity allocation cycles");
1270
- allocator.command("cycle").description("Find LP candidates from /api/feed and run a dry-run allocator cycle").argument(
1271
- "[screening]",
1272
- `Optional screening preset: ${PROFILE_CHOICES2.join(" | ")} (same as --profile)`,
1273
- "lp-opportunity"
1274
- ).option("--profile <name>", `Screening defaults: ${PROFILE_CHOICES2.join(", ")}`).option("--sort-by <key>", "score | volume | liquidity | movement | spread | rewards | rewardYield | lpExpectedReturn | horizon").option("--tag <slug>", "Polymarket category tag, e.g. crypto, politics").option("--min-volume <usd>", "Minimum 24h volume (USD)").option("--min-liquidity <usd>", "Minimum displayed liquidity (USD)").option("--max-liquidity <usd>", "Maximum displayed liquidity (USD)").option("--min-rewards-daily-rate <usd>", "Minimum LP rewards USD/day").option("--min-days-to-end <n>", "Feed filter: minimum days until resolution").option("--max-days-to-end <n>", "Feed filter: maximum days until resolution").option("--max-market-age-hours <n>", "With liquid-new-or-long: max age for the new branch").option("--liquid-profile <mode>", "new-or-long (used by liquid-new-or-long screen)").option("--limit <n>", "Max feed markets to fetch (1-100, default 15)", "15").option("--total-holdings <usd>", "Total user holdings / portfolio value used for percentage sizing", parsePositiveNumber).option("--capital-limit-pct <pct>", "Portfolio-level allocation cap as a percent of total holdings", parsePositiveNumber).option("--per-market-limit-pct <pct>", "Per-market target cap as a percent of total holdings", parsePositiveNumber).option("--capital-limit <usd>", "Portfolio capital limit for this cycle", parseNonNegative, 500).option("--per-market-limit <usd>", "Per-market target cap", parseNonNegative, 100).option("--min-expected-return-daily-pct <pct>", "Minimum expected daily return percent", parseNonNegative, 0.02).option("--max-inventory-imbalance <ratio>", "Maximum inventory imbalance", parseNonNegative, 0.25).option("--max-order-notional <usd>", "Maximum notional per planned passive order", parseNonNegative, 25).option("--quote-edge-bps <bps>", "Passive quote edge in basis points", parseNonNegative, 100).option("--allocator-min-liquidity <usd>", "Allocator safety gate: minimum market liquidity", parseNonNegative, 500).option("--max-spread <ratio>", "Allocator safety gate: maximum spread", parseNonNegative, 0.12).option("--allocator-min-days-to-end <n>", "Allocator safety gate: minimum days to resolution", parseNonNegative, 3).option("--max-markets <n>", "Maximum markets allocator may target", parsePositiveInt, 5).option("--allocations <file>", "Existing allocations JSON array; use '-' to read stdin").option("--repeat", "Run a second cycle using targets returned by the first cycle").option("--paused", "Send strategy status paused instead of dry_run").action(async (screening, o) => {
1275
- const globalOpts = program2.opts();
1276
- const client = new ApiClient(globalOpts);
1277
- requireAuth5(client);
1278
- validatePercentageSizing(o);
1279
- let profile = o.profile ?? screening ?? "lp-opportunity";
1280
- if (!isProfile2(profile)) {
1281
- error(`Unknown screening "${profile}". Use: ${PROFILE_CHOICES2.join(" or ")}`);
1282
- process.exit(1);
1283
- }
1284
- if (o.profile && screening && screening !== "lp-opportunity" && o.profile !== screening) {
1285
- warn(`Both positional and --profile set; using --profile (${o.profile}).`);
1286
- profile = o.profile;
1287
- }
1288
- const feed = await client.get("/api/feed", feedQueryParams2(profile, o));
1289
- if (feed.error) {
1290
- error(feed.error);
1291
- process.exit(1);
1292
- }
1293
- if (feed.markets.length === 0) {
1294
- warn("No feed markets matched the allocator criteria.");
1295
- return;
1296
- }
1297
- const allocations = await readAllocations(o.allocations);
1298
- const payload = {
1299
- strategy: strategyFromOptions(o),
1300
- markets: feedMarketsToAllocatorMarkets(feed.markets),
1301
- allocations
1302
- };
1303
- if (!globalOpts.json) {
1304
- process.stderr.write(
1305
- chalk6.dim(
1306
- ` Running allocator dry-run on ${feed.markets.length} ${profile} candidates...
1307
- `
1308
- )
1309
- );
1310
- }
1311
- const first = await runAllocatorCycle(client, payload);
1312
- const firstResult = first.result ?? {};
1313
- if (o.repeat) {
1314
- const repeatPayload = {
1315
- ...payload,
1316
- allocations: allocationsFromDecisions(firstResult.decisions ?? [])
1317
- };
1318
- const second = await runAllocatorCycle(client, repeatPayload);
1319
- if (globalOpts.json) {
1320
- json({ initial: first, repeat: second });
1321
- } else {
1322
- displayAllocatorCycleResult(firstResult, globalOpts);
1323
- process.stdout.write("\n" + chalk6.bold("Repeated with target allocations") + "\n");
1324
- displayAllocatorCycleResult(second.result ?? {}, globalOpts);
1325
- }
1326
- return;
1327
- }
1328
- if (globalOpts.json) {
1329
- json(first);
1330
- } else {
1331
- displayAllocatorCycleResult(firstResult, globalOpts);
1332
- }
1333
- });
1334
- }
1335
-
1336
- // src/commands/lp.ts
1337
- import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
1338
- import { writeFile } from "fs/promises";
1339
1230
 
1340
1231
  // src/lp-display.ts
1341
- import chalk7 from "chalk";
1232
+ import chalk6 from "chalk";
1342
1233
  function num2(value, fallback = 0) {
1343
1234
  const n = Number(value);
1344
1235
  return Number.isFinite(n) ? n : fallback;
1345
1236
  }
1346
1237
  function signedCurrency2(value) {
1347
1238
  const formatted = currency(Math.abs(value));
1348
- if (value > 0) return chalk7.green(`+${formatted}`);
1349
- if (value < 0) return chalk7.yellow(`-${formatted}`);
1350
- return chalk7.dim("$0.00");
1239
+ if (value > 0) return chalk6.green(`+${formatted}`);
1240
+ if (value < 0) return chalk6.yellow(`-${formatted}`);
1241
+ return chalk6.dim("$0.00");
1351
1242
  }
1352
1243
  function actionSummary(actions) {
1353
1244
  if (!actions) return "none";
@@ -1360,13 +1251,13 @@ function displayLpScanResult(result, globalOpts) {
1360
1251
  }
1361
1252
  heading("LP Scan");
1362
1253
  process.stdout.write(
1363
- chalk7.dim(
1254
+ chalk6.dim(
1364
1255
  ` scan ${result.scanId} \xB7 strategy ${result.strategyId} \xB7 ${result.evidenceSaved} evidence rows saved
1365
1256
  `
1366
1257
  )
1367
1258
  );
1368
1259
  process.stdout.write(
1369
- chalk7.dim(
1260
+ chalk6.dim(
1370
1261
  ` ${result.totalScanned.toLocaleString()} scanned \xB7 ${result.totalAfterFilter.toLocaleString()} after filters \xB7 profile ${result.profile}
1371
1262
 
1372
1263
  `
@@ -1395,13 +1286,13 @@ function displayLpRecommendResult(result, globalOpts) {
1395
1286
  }
1396
1287
  heading("LP Recommendations");
1397
1288
  process.stdout.write(
1398
- chalk7.dim(
1289
+ chalk6.dim(
1399
1290
  ` cycle ${result.cycleId} \xB7 strategy ${result.strategyId} \xB7 ${result.candidatesSubmitted} markets \xB7 ${result.allocationsSubmitted} current allocations
1400
1291
  `
1401
1292
  )
1402
1293
  );
1403
1294
  process.stdout.write(
1404
- chalk7.dim(
1295
+ chalk6.dim(
1405
1296
  ` PnL context ${result.pnlContextCount} rows${result.pnlSynced ? " \xB7 synced" : ""} \xB7 approvals required
1406
1297
 
1407
1298
  `
@@ -1416,11 +1307,11 @@ function displayLpEvaluateResult(result, globalOpts) {
1416
1307
  }
1417
1308
  heading("LP Evaluation");
1418
1309
  process.stdout.write(
1419
- chalk7.dim(
1310
+ chalk6.dim(
1420
1311
  ` strategy ${result.strategyId} \xB7 ${result.summary.snapshots} snapshots \xB7 ${result.summary.markets} markets`
1421
1312
  )
1422
1313
  );
1423
- if (result.pnlSynced) process.stdout.write(chalk7.dim(" \xB7 synced"));
1314
+ if (result.pnlSynced) process.stdout.write(chalk6.dim(" \xB7 synced"));
1424
1315
  process.stdout.write("\n\n");
1425
1316
  if (result.syncError) warn(result.syncError);
1426
1317
  table(
@@ -1448,58 +1339,58 @@ function displayLpEvaluateResult(result, globalOpts) {
1448
1339
  ["Market", "Outcome", "Net", "Lesson"]
1449
1340
  );
1450
1341
  }
1451
- function displayLpRunResult(result, globalOpts) {
1452
- if (globalOpts.json) {
1453
- json(result);
1454
- return;
1455
- }
1456
- heading("LP Run");
1457
- process.stdout.write(
1458
- chalk7.dim(
1459
- ` cycle ${result.run.cycleId} \xB7 scan ${result.run.scanId ?? "n/a"} \xB7 strategy ${result.run.strategyId}
1460
- `
1461
- )
1462
- );
1463
- process.stdout.write(
1464
- chalk7.dim(
1465
- ` ${result.run.opportunitiesFound} opportunities \xB7 PnL ${result.run.pnlSynced ? "synced" : "not synced"} \xB7 approvals required
1466
-
1467
- `
1468
- )
1469
- );
1470
- displayAllocatorCycleResult(result.run.result, globalOpts);
1471
- if (result.evaluation) {
1472
- process.stdout.write("\n");
1473
- process.stdout.write(
1474
- chalk7.dim(
1475
- ` Evaluation: ${signedCurrency2(result.evaluation.summary.netPnl)} net PnL across ${result.evaluation.summary.markets} markets
1476
- `
1477
- )
1478
- );
1479
- }
1480
- }
1481
1342
 
1482
1343
  // src/commands/lp.ts
1483
- function requireAuth6(client) {
1344
+ function requireAuth5(client) {
1484
1345
  if (!client.isAuthenticated) {
1485
1346
  error("Not authenticated. Run `hl auth login` first.");
1486
1347
  process.exit(1);
1487
1348
  }
1488
1349
  }
1489
- function parseNonNegative2(value) {
1350
+ function parseNonNegative(value) {
1490
1351
  const n = Number(value);
1491
1352
  if (!Number.isFinite(n) || n < 0) {
1492
1353
  throw new InvalidArgumentError2("Expected a non-negative number");
1493
1354
  }
1494
1355
  return n;
1495
1356
  }
1496
- function parsePositiveInt2(value) {
1357
+ function parsePositiveInt(value) {
1497
1358
  const n = Number(value);
1498
1359
  if (!Number.isInteger(n) || n < 1) {
1499
1360
  throw new InvalidArgumentError2("Expected a positive integer");
1500
1361
  }
1501
1362
  return n;
1502
1363
  }
1364
+ function strategyFromAllocatorOptions(opts) {
1365
+ return {
1366
+ id: "cli-lp-allocator",
1367
+ name: "CLI LP allocator dry run",
1368
+ status: opts.paused ? "paused" : "dry_run",
1369
+ ...opts.totalHoldings !== void 0 && { total_holdings: opts.totalHoldings },
1370
+ ...opts.capitalLimitPct !== void 0 && { capital_limit_pct: opts.capitalLimitPct },
1371
+ ...opts.perMarketLimitPct !== void 0 && { per_market_limit_pct: opts.perMarketLimitPct },
1372
+ capital_limit: opts.capitalLimit,
1373
+ per_market_limit: opts.perMarketLimit,
1374
+ min_expected_return_daily_pct: opts.minExpectedReturnDailyPct,
1375
+ max_inventory_imbalance: opts.maxInventoryImbalance,
1376
+ volatility_fill_spike_threshold: opts.volatilityFillSpikeThreshold,
1377
+ event_no_quote_minutes_before: opts.eventNoQuoteMinutesBefore,
1378
+ event_no_quote_minutes_after: opts.eventNoQuoteMinutesAfter,
1379
+ min_liquidity: opts.allocatorMinLiquidity,
1380
+ max_spread: opts.maxSpread,
1381
+ min_days_to_end: opts.allocatorMinDaysToEnd,
1382
+ max_markets: opts.maxMarkets
1383
+ };
1384
+ }
1385
+ function validateAllocatorPercentageSizing(opts) {
1386
+ const usesPercentageSizing = opts.capitalLimitPct !== void 0 || opts.perMarketLimitPct !== void 0;
1387
+ if (usesPercentageSizing && opts.totalHoldings === void 0) {
1388
+ error(
1389
+ "Percentage sizing requires --total-holdings so the allocator can convert percentages into dollar caps."
1390
+ );
1391
+ process.exit(1);
1392
+ }
1393
+ }
1503
1394
  function compact(payload) {
1504
1395
  return Object.fromEntries(
1505
1396
  Object.entries(payload).filter(([, value]) => value !== void 0 && value !== "")
@@ -1547,19 +1438,43 @@ function buildLpEvaluatePayload(opts) {
1547
1438
  limit: opts.limit
1548
1439
  });
1549
1440
  }
1550
- function buildLpRunPayload(opts) {
1551
- return compact({
1552
- strategyId: opts.strategyId,
1553
- limit: opts.limit,
1554
- syncPnl: opts.syncPnl
1555
- });
1441
+ async function runAllocatorCycle(client, payload) {
1442
+ return client.post("/api/lp/allocator", payload);
1556
1443
  }
1557
1444
  function registerLpCommands(program2) {
1558
1445
  const lp = program2.command("lp").description("Run persisted liquidity-provider scan, recommendation, and evaluation workflows");
1559
- lp.command("scan").description("Scan LP candidates and persist them as evidence").argument("[topic]", "Human label for this scan, e.g. liquidity opportunities").option("--profile <name>", "lp-opportunity | liquidity-provider | liquid-new-or-long", "liquidity-provider").option("--sort-by <key>", "score | volume | liquidity | movement | spread | rewards | rewardYield | lpExpectedReturn | horizon").option("--tag <slug>", "Polymarket category tag, e.g. crypto, politics").option("--min-volume <usd>", "Minimum 24h volume (USD)", parseNonNegative2).option("--min-liquidity <usd>", "Minimum displayed liquidity (USD)", parseNonNegative2).option("--max-liquidity <usd>", "Maximum displayed liquidity (USD)", parseNonNegative2).option("--min-rewards-daily-rate <usd>", "Minimum LP rewards USD/day", parseNonNegative2).option("--min-days-to-end <n>", "Minimum days until resolution", parseNonNegative2).option("--max-days-to-end <n>", "Maximum days until resolution", parseNonNegative2).option("--max-market-age-hours <n>", "With liquid-new-or-long: max age for the new branch", parseNonNegative2).option("--liquid-profile <mode>", "new-or-long").option("--limit <n>", "Max markets to scan/save (1-100, default 15)", parsePositiveInt2, 15).option("--strategy-id <uuid>", "LP strategy id").option("--output <file>", "Write the evidence JSON response to a local file").action(async (topic, opts) => {
1446
+ lp.command("allocator").description("Run the allocator agent on an explicit market list").requiredOption("--markets <file>", "Candidate market JSON array or { markets }; use '-' to read stdin").option("--allocations <file>", "Existing allocations JSON array; use '-' to read stdin").option("--total-holdings <usd>", "Total holdings / portfolio value used for percentage sizing", parsePositiveNumber).option("--capital-limit-pct <pct>", "Portfolio-level allocation cap as a percent of total holdings", parsePositiveNumber).option("--per-market-limit-pct <pct>", "Per-market target cap as a percent of total holdings", parsePositiveNumber).option("--capital-limit <usd>", "Portfolio capital limit for this allocator request", parseNonNegative, 500).option("--per-market-limit <usd>", "Per-market target cap", parseNonNegative, 100).option("--min-expected-return-daily-pct <pct>", "Minimum expected daily return percent", parseNonNegative, 0.02).option("--max-inventory-imbalance <ratio>", "Maximum inventory imbalance", parseNonNegative, 0.25).option("--volatility-fill-spike-threshold <ratio>", "Fill-rate imbalance that switches quotes to defensive mode", parseNonNegative, 0.35).option("--event-no-quote-minutes-before <n>", "No-quote window before scheduled events", parseNonNegative, 60).option("--event-no-quote-minutes-after <n>", "No-quote window after scheduled events", parseNonNegative, 30).option("--allocator-min-liquidity <usd>", "Allocator safety gate: minimum market liquidity", parseNonNegative, 500).option("--max-spread <ratio>", "Allocator safety gate: maximum spread", parseNonNegative, 0.12).option("--allocator-min-days-to-end <n>", "Allocator safety gate: minimum days to resolution", parseNonNegative, 3).option("--max-markets <n>", "Maximum markets allocator may target", parsePositiveInt, 5).option("--paused", "Send strategy status paused instead of dry_run").action(async (opts) => {
1560
1447
  const globalOpts = program2.opts();
1561
1448
  const client = new ApiClient(globalOpts);
1562
- requireAuth6(client);
1449
+ requireAuth5(client);
1450
+ validateAllocatorPercentageSizing(opts);
1451
+ const markets = await readMarketPayload(opts.markets);
1452
+ if (!markets || markets.length === 0) {
1453
+ error("Markets JSON must include at least one market.");
1454
+ process.exit(1);
1455
+ }
1456
+ const payload = {
1457
+ strategy: strategyFromAllocatorOptions(opts),
1458
+ markets,
1459
+ allocations: await readAllocations(opts.allocations)
1460
+ };
1461
+ if (!globalOpts.json) {
1462
+ process.stderr.write(
1463
+ dim(` Running allocator dry-run on ${markets.length} provided candidates...
1464
+ `)
1465
+ );
1466
+ }
1467
+ const response = await runAllocatorCycle(client, payload);
1468
+ if (globalOpts.json) {
1469
+ json(response);
1470
+ } else {
1471
+ displayAllocatorCycleResult(response.result ?? {}, globalOpts);
1472
+ }
1473
+ });
1474
+ lp.command("scan").description("Scan LP candidates and persist them as evidence").argument("[topic]", "Human label for this scan, e.g. liquidity opportunities").option("--profile <name>", "lp-opportunity | liquidity-provider | liquid-new-or-long", "liquidity-provider").option("--sort-by <key>", "score | volume | liquidity | movement | spread | rewards | rewardYield | lpExpectedReturn | horizon").option("--tag <slug>", "Polymarket category tag, e.g. crypto, politics").option("--min-volume <usd>", "Minimum 24h volume (USD)", parseNonNegative).option("--min-liquidity <usd>", "Minimum displayed liquidity (USD)", parseNonNegative).option("--max-liquidity <usd>", "Maximum displayed liquidity (USD)", parseNonNegative).option("--min-rewards-daily-rate <usd>", "Minimum LP rewards USD/day", parseNonNegative).option("--min-days-to-end <n>", "Minimum days until resolution", parseNonNegative).option("--max-days-to-end <n>", "Maximum days until resolution", parseNonNegative).option("--max-market-age-hours <n>", "With liquid-new-or-long: max age for the new branch", parseNonNegative).option("--liquid-profile <mode>", "new-or-long").option("--limit <n>", "Max markets to scan/save (1-100, default 15)", parsePositiveInt, 15).option("--strategy-id <uuid>", "LP strategy id").option("--output <file>", "Write the evidence JSON response to a local file").action(async (topic, opts) => {
1475
+ const globalOpts = program2.opts();
1476
+ const client = new ApiClient(globalOpts);
1477
+ requireAuth5(client);
1563
1478
  const result = await client.post(
1564
1479
  "/api/lp/scan",
1565
1480
  buildLpScanPayload(topic, opts)
@@ -1567,10 +1482,10 @@ function registerLpCommands(program2) {
1567
1482
  await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1568
1483
  displayLpScanResult(result, globalOpts);
1569
1484
  });
1570
- lp.command("recommend").description("Run allocator recommendations from saved LP evidence and current allocations").option("--strategy-id <uuid>", "LP strategy id").option("--scan-id <uuid>", "Use candidates from a specific saved scan").option("--limit <n>", "Max saved candidates to submit (1-25, default 15)", parsePositiveInt2, 15).option("--sync-pnl", "Refresh wallet PnL before recommending").option("--output <file>", "Write the recommendation JSON response to a local file").action(async (opts) => {
1485
+ lp.command("recommend").description("Run allocator recommendations from saved LP evidence and current allocations").option("--strategy-id <uuid>", "LP strategy id").option("--scan-id <uuid>", "Use candidates from a specific saved scan").option("--limit <n>", "Max saved candidates to submit (1-25, default 15)", parsePositiveInt, 15).option("--sync-pnl", "Refresh wallet PnL before recommending").option("--output <file>", "Write the recommendation JSON response to a local file").action(async (opts) => {
1571
1486
  const globalOpts = program2.opts();
1572
1487
  const client = new ApiClient(globalOpts);
1573
- requireAuth6(client);
1488
+ requireAuth5(client);
1574
1489
  const result = await client.post(
1575
1490
  "/api/lp/recommend",
1576
1491
  buildLpRecommendPayload(opts)
@@ -1578,10 +1493,10 @@ function registerLpCommands(program2) {
1578
1493
  await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1579
1494
  displayLpRecommendResult(result, globalOpts);
1580
1495
  });
1581
- lp.command("evaluate").description("Evaluate LP performance and return compact lessons").option("--strategy-id <uuid>", "LP strategy id").option("--wallet-address <address>", "Wallet address to sync PnL from").option("--no-sync-pnl", "Use existing PnL snapshots without refreshing").option("--limit <n>", "Max PnL snapshots to evaluate (1-100, default 50)", parsePositiveInt2, 50).option("--output <file>", "Write the evaluation JSON response to a local file").action(async (opts) => {
1496
+ lp.command("evaluate").description("Evaluate LP performance and return compact lessons").option("--strategy-id <uuid>", "LP strategy id").option("--wallet-address <address>", "Wallet address to sync PnL from").option("--no-sync-pnl", "Use existing PnL snapshots without refreshing").option("--limit <n>", "Max PnL snapshots to evaluate (1-100, default 50)", parsePositiveInt, 50).option("--output <file>", "Write the evaluation JSON response to a local file").action(async (opts) => {
1582
1497
  const globalOpts = program2.opts();
1583
1498
  const client = new ApiClient(globalOpts);
1584
- requireAuth6(client);
1499
+ requireAuth5(client);
1585
1500
  const result = await client.post(
1586
1501
  "/api/lp/evaluate",
1587
1502
  buildLpEvaluatePayload(opts)
@@ -1589,31 +1504,6 @@ function registerLpCommands(program2) {
1589
1504
  await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1590
1505
  displayLpEvaluateResult(result, globalOpts);
1591
1506
  });
1592
- lp.command("run").description("Run scan, recommendation, and evaluation as one dry-run chain").option("--strategy-id <uuid>", "LP strategy id").option("--limit <n>", "Max candidates to scan/recommend (1-25, default 15)", parsePositiveInt2, 15).option("--no-sync-pnl", "Use existing PnL snapshots without refreshing").option("--output <file>", "Write the chained run JSON response to a local file").action(async (opts) => {
1593
- const globalOpts = program2.opts();
1594
- const client = new ApiClient(globalOpts);
1595
- requireAuth6(client);
1596
- const run = await client.post(
1597
- "/api/lp/run",
1598
- buildLpRunPayload(opts)
1599
- );
1600
- let evaluation = null;
1601
- try {
1602
- evaluation = await client.post("/api/lp/evaluate", {
1603
- strategyId: run.strategyId,
1604
- syncPnl: false
1605
- });
1606
- } catch (error2) {
1607
- if (!globalOpts.json) {
1608
- warn(
1609
- `Could not load evaluation summary: ${error2 instanceof Error ? error2.message : String(error2)}`
1610
- );
1611
- }
1612
- }
1613
- const combined = { run, evaluation };
1614
- await writeArtifact(opts.output, combined, Boolean(globalOpts.json));
1615
- displayLpRunResult(combined, globalOpts);
1616
- });
1617
1507
  }
1618
1508
 
1619
1509
  // src/commands/wallet.ts
@@ -1624,7 +1514,7 @@ var POLYGON_NATIVE_USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
1624
1514
  var SUPPORTED_ASSETS = ["pUsd", "usdcE", "matic"];
1625
1515
  var POLYGON_NATIVE_ASSET_LABEL = "POL";
1626
1516
  var TERMINAL_WITHDRAWAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "expired"]);
1627
- function requireAuth7(client) {
1517
+ function requireAuth6(client) {
1628
1518
  if (!client.isAuthenticated) {
1629
1519
  error("Not authenticated. Run `hl auth login` first.");
1630
1520
  process.exit(1);
@@ -1809,7 +1699,7 @@ function registerWalletCommands(program2) {
1809
1699
  wallet.command("status").description("Show linked owner and Polymarket deposit wallet status").action(async () => {
1810
1700
  const globalOpts = program2.opts();
1811
1701
  const client = new ApiClient(globalOpts);
1812
- requireAuth7(client);
1702
+ requireAuth6(client);
1813
1703
  const status = await client.get("/api/wallet/status");
1814
1704
  if (globalOpts.json) {
1815
1705
  json(status);
@@ -1820,7 +1710,7 @@ function registerWalletCommands(program2) {
1820
1710
  wallet.command("balances").alias("funds").description("Show available wallet funds").action(async () => {
1821
1711
  const globalOpts = program2.opts();
1822
1712
  const client = new ApiClient(globalOpts);
1823
- requireAuth7(client);
1713
+ requireAuth6(client);
1824
1714
  const balances = await client.get("/api/wallet/balances");
1825
1715
  if (globalOpts.json) {
1826
1716
  json(balances);
@@ -1831,7 +1721,7 @@ function registerWalletCommands(program2) {
1831
1721
  wallet.command("deposit").description("Show deposit address and supported Polygon assets").option("--asset <asset>", "pUSD | USDC.e | POL").option("--bridge", "Also request Polymarket Bridge deposit addresses").action(async (opts) => {
1832
1722
  const globalOpts = program2.opts();
1833
1723
  const client = new ApiClient(globalOpts);
1834
- requireAuth7(client);
1724
+ requireAuth6(client);
1835
1725
  const asset = normalizeWalletAsset(opts.asset);
1836
1726
  const result = await client.post("/api/wallet/deposit", {
1837
1727
  bridge: Boolean(opts.bridge)
@@ -1854,7 +1744,7 @@ function registerWalletCommands(program2) {
1854
1744
  wallet.command("withdraw").description("Create a browser-signed withdrawal intent and poll for completion").requiredOption("--to <address>", "Recipient EVM wallet address", parseWalletAddress).requiredOption("--amount <amount>", "Amount to withdraw", parsePositiveNumber2).option("--asset <asset>", "Source asset; currently only pUSD is supported", "pUSD").option("--to-chain-id <id>", "Destination chain id", "137").option("--to-token-address <address>", "Destination token address", parseWalletAddress, POLYGON_NATIVE_USDC).option("--no-open", "Do not open the browser signing URL").option("--no-wait", "Do not poll for completion after creating the intent").option("--poll-interval <seconds>", "Polling interval in seconds", parsePositiveNumber2, 5).option("--timeout <seconds>", "Maximum seconds to wait for completion", parsePositiveNumber2, 600).action(async (opts) => {
1855
1745
  const globalOpts = program2.opts();
1856
1746
  const client = new ApiClient(globalOpts);
1857
- requireAuth7(client);
1747
+ requireAuth6(client);
1858
1748
  const created = await client.post(
1859
1749
  "/api/wallet/withdraw/intents",
1860
1750
  buildWithdrawRequestPayload(opts)
@@ -1879,9 +1769,209 @@ function registerWalletCommands(program2) {
1879
1769
  });
1880
1770
  }
1881
1771
 
1772
+ // src/commands/signal.ts
1773
+ import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
1774
+ import { readFile as readFile2 } from "fs/promises";
1775
+
1776
+ // src/signal-display.ts
1777
+ import chalk7 from "chalk";
1778
+ function pctFromSignalValue(value) {
1779
+ if (value === null || value === void 0 || Number.isNaN(value)) return null;
1780
+ return Math.abs(value) <= 1 ? value * 100 : value;
1781
+ }
1782
+ function formatProbability(value) {
1783
+ const pct = pctFromSignalValue(value);
1784
+ return pct === null ? "n/a" : `${pct.toFixed(1)}%`;
1785
+ }
1786
+ function formatGap(value) {
1787
+ if (value === null || value === void 0 || Number.isNaN(value)) return "n/a";
1788
+ const points = Math.abs(value) <= 1 ? value * 100 : value;
1789
+ const formatted = `${points >= 0 ? "+" : ""}${points.toFixed(1)}pp`;
1790
+ if (points > 0) return chalk7.green(formatted);
1791
+ if (points < 0) return chalk7.red(formatted);
1792
+ return chalk7.dim(formatted);
1793
+ }
1794
+ function formatStrength(value) {
1795
+ if (value === "strong") return chalk7.green("strong");
1796
+ if (value === "weak") return chalk7.yellow("weak");
1797
+ return value ? chalk7.dim(value) : "n/a";
1798
+ }
1799
+ function analysisItems(result) {
1800
+ if (!result) return [];
1801
+ if (result.analysis) return [result];
1802
+ return result.analyses ?? [];
1803
+ }
1804
+ function titleFor(analysis) {
1805
+ return analysis.market_name || analysis.market_slug || "market";
1806
+ }
1807
+ function displaySignalAnalysis(response, globalOpts) {
1808
+ if (globalOpts.json) {
1809
+ json(response);
1810
+ return;
1811
+ }
1812
+ if (response.error) {
1813
+ error(response.error);
1814
+ return;
1815
+ }
1816
+ const result = response.result;
1817
+ if (result?.error) {
1818
+ error(result.error);
1819
+ return;
1820
+ }
1821
+ const items = analysisItems(result);
1822
+ if (items.length === 0) {
1823
+ warn("No signal analysis returned.");
1824
+ return;
1825
+ }
1826
+ heading(
1827
+ items.length === 1 ? "Signal Analysis" : `Signal Analysis \u2014 ${items.length} markets`
1828
+ );
1829
+ const rows = items.map((item) => {
1830
+ const analysis = item.analysis ?? {};
1831
+ return [
1832
+ truncate(titleFor(analysis), 44),
1833
+ formatProbability(analysis.current_yes_prob),
1834
+ formatProbability(analysis.predicted_prob),
1835
+ formatGap(analysis.probability_gap),
1836
+ formatStrength(analysis.signal_strength),
1837
+ analysis.confidence ?? "n/a"
1838
+ ];
1839
+ });
1840
+ table(rows, ["Market", "Market YES", "Agent YES", "Gap", "Signal", "Conf"]);
1841
+ for (const item of items.slice(0, 3)) {
1842
+ const analysis = item.analysis;
1843
+ if (!analysis) continue;
1844
+ process.stdout.write("\n " + chalk7.bold(truncate(titleFor(analysis), 76)) + "\n");
1845
+ if (analysis.market_link) {
1846
+ process.stdout.write(" " + chalk7.dim("Polymarket: ") + analysis.market_link + "\n");
1847
+ }
1848
+ if (analysis.key_factors && analysis.key_factors.length > 0) {
1849
+ process.stdout.write(" " + chalk7.dim("Key factors: ") + analysis.key_factors.slice(0, 4).join("; ") + "\n");
1850
+ }
1851
+ if (analysis.research_findings) {
1852
+ process.stdout.write(
1853
+ " " + chalk7.dim("Research: ") + truncate(analysis.research_findings, 180) + "\n"
1854
+ );
1855
+ }
1856
+ }
1857
+ if (items.length > 3) {
1858
+ process.stdout.write(chalk7.dim(`
1859
+ ... and ${items.length - 3} more
1860
+ `));
1861
+ }
1862
+ if (result?.strong_signal_count !== void 0) {
1863
+ process.stdout.write(
1864
+ chalk7.dim(`
1865
+ Strong signals: ${result.strong_signal_count}
1866
+ `)
1867
+ );
1868
+ }
1869
+ }
1870
+
1871
+ // src/commands/signal.ts
1872
+ function requireAuth7(client) {
1873
+ if (!client.isAuthenticated) {
1874
+ error("Not authenticated. Run `hl auth login` first.");
1875
+ process.exit(1);
1876
+ }
1877
+ }
1878
+ function collect(value, previous = []) {
1879
+ return [...previous, value];
1880
+ }
1881
+ function parseProbability(value) {
1882
+ const n = Number(value);
1883
+ if (!Number.isFinite(n) || n < 0 || n > 100) {
1884
+ throw new InvalidArgumentError4("Expected a probability between 0 and 100");
1885
+ }
1886
+ return n;
1887
+ }
1888
+ async function readStdin2() {
1889
+ const chunks = [];
1890
+ for await (const chunk of process.stdin) {
1891
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1892
+ }
1893
+ return Buffer.concat(chunks).toString("utf8");
1894
+ }
1895
+ async function readMarketPayload2(path) {
1896
+ if (!path) return {};
1897
+ const raw = path === "-" ? await readStdin2() : await readFile2(path, "utf8");
1898
+ const parsed = JSON.parse(raw);
1899
+ if (Array.isArray(parsed)) {
1900
+ return { markets: parsed };
1901
+ }
1902
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed.markets)) {
1903
+ return { markets: parsed.markets };
1904
+ }
1905
+ if (parsed && typeof parsed === "object" && parsed.market) {
1906
+ return { market: parsed.market };
1907
+ }
1908
+ if (parsed && typeof parsed === "object") {
1909
+ return { market: parsed };
1910
+ }
1911
+ throw new Error("Market JSON must be an object, an array, or { market | markets }");
1912
+ }
1913
+ function inlineMarketFromOptions(opts) {
1914
+ const market = {};
1915
+ if (opts.question) market.question = opts.question;
1916
+ if (opts.description) market.description = opts.description;
1917
+ if (opts.yesProb !== void 0) market.yesPrice = opts.yesProb;
1918
+ if (opts.noProb !== void 0) market.noPrice = opts.noProb;
1919
+ if (opts.slug) market.slug = opts.slug;
1920
+ if (opts.link) market.link = opts.link;
1921
+ return Object.keys(market).length > 0 ? market : void 0;
1922
+ }
1923
+ async function buildSignalPayload(positionalUrl, opts) {
1924
+ const urls = [positionalUrl, ...opts.url ?? []].filter(
1925
+ (value) => Boolean(value)
1926
+ );
1927
+ const filePayload = await readMarketPayload2(opts.market);
1928
+ const inlineMarket = inlineMarketFromOptions(opts);
1929
+ const hasMarketInput = Boolean(filePayload.market || filePayload.markets || inlineMarket);
1930
+ if (urls.length > 0 && hasMarketInput) {
1931
+ throw new Error("Use either URL input or market JSON/options, not both.");
1932
+ }
1933
+ if (filePayload.market && inlineMarket) {
1934
+ throw new Error("Use either --market or inline market options, not both.");
1935
+ }
1936
+ if (filePayload.markets && inlineMarket) {
1937
+ throw new Error("Use either --market or inline market options, not both.");
1938
+ }
1939
+ if (urls.length === 0 && !hasMarketInput) {
1940
+ throw new Error("Provide a Polymarket URL or a market payload.");
1941
+ }
1942
+ const payload = urls.length === 1 ? { url: urls[0] } : urls.length > 1 ? { urls } : filePayload.market ? { market: filePayload.market } : filePayload.markets ? { markets: filePayload.markets } : { market: inlineMarket };
1943
+ if (opts.context) {
1944
+ payload.previous_analysis_context = opts.context;
1945
+ }
1946
+ return payload;
1947
+ }
1948
+ async function runSignalAnalysis(client, payload) {
1949
+ return client.post("/api/signal/analyze", payload);
1950
+ }
1951
+ function registerSignalCommands(program2) {
1952
+ const signal = program2.command("signal").description("Analyze Polymarket probability gaps with the signal agent");
1953
+ signal.command("analyze").description("Estimate true YES probability and compare it with market pricing").argument("[url]", "Polymarket market/event URL to analyze").option("--url <url>", "Additional Polymarket URL; repeat for multiple markets", collect, []).option("--market <file>", "Inline market JSON object/array; use '-' to read stdin").option("--context <text>", "Prior search notes or analysis context for the agent").option("--question <text>", "Inline market question when not using a URL").option("--description <text>", "Inline market description or resolution criteria").option("--yes-prob <prob>", "Current YES probability or price, e.g. 0.52 or 52", parseProbability).option("--no-prob <prob>", "Current NO probability or price, e.g. 0.48 or 48", parseProbability).option("--slug <slug>", "Inline market slug").option("--link <url>", "Inline market link").action(async (url, o) => {
1954
+ const globalOpts = program2.opts();
1955
+ const client = new ApiClient(globalOpts);
1956
+ requireAuth7(client);
1957
+ let payload;
1958
+ try {
1959
+ payload = await buildSignalPayload(url, o);
1960
+ } catch (e) {
1961
+ error(e instanceof Error ? e.message : String(e));
1962
+ process.exit(1);
1963
+ }
1964
+ if (!globalOpts.json) {
1965
+ process.stderr.write(dim(" Running signal analysis...\n"));
1966
+ }
1967
+ const result = await runSignalAnalysis(client, payload);
1968
+ displaySignalAnalysis(result, globalOpts);
1969
+ });
1970
+ }
1971
+
1882
1972
  // src/index.ts
1883
1973
  var program = new Command4();
1884
- program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("1.7.0").option("--json", "Output as JSON (machine-readable)").option("--api-url <url>", "Override API base URL").option("--token <token>", "Override stored API token").option("--verbose", "Show HTTP request details").option("--no-color", "Disable colored output");
1974
+ program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("2.0.0").option("--json", "Output as JSON (machine-readable)").option("--api-url <url>", "Override API base URL").option("--token <token>", "Override stored API token").option("--verbose", "Show HTTP request details").option("--no-color", "Disable colored output");
1885
1975
  registerAuthCommands(program);
1886
1976
  registerBriefCommands(program);
1887
1977
  registerProfileCommand(program);
@@ -1889,7 +1979,7 @@ registerResearchCommands(program);
1889
1979
  registerFeedCommand(program);
1890
1980
  registerWalletCommands(program);
1891
1981
  registerLpCommands(program);
1892
- registerAllocatorCommands(program);
1982
+ registerSignalCommands(program);
1893
1983
  program.hook("preAction", (_thisCommand, actionCommand) => {
1894
1984
  const opts = program.opts();
1895
1985
  if (opts.color === false) {