@hedge-layer/cli 1.8.0 → 2.1.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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command5 } from "commander";
4
+ import { Command as Command4 } from "commander";
5
5
 
6
6
  // src/commands/auth.ts
7
7
  import readline from "readline/promises";
@@ -980,6 +980,7 @@ function displayMarketBrief(brief, globalOpts) {
980
980
  }
981
981
 
982
982
  // src/commands/feed.ts
983
+ import { writeFile } from "fs/promises";
983
984
  function requireAuth4(client) {
984
985
  if (!client.isAuthenticated) {
985
986
  error("Not authenticated. Run `hl auth login` first.");
@@ -1024,8 +1025,82 @@ function feedQueryParams(opts) {
1024
1025
  add("limit", opts.limit);
1025
1026
  return Object.fromEntries(entries);
1026
1027
  }
1028
+ var ENSEMBLE_SOURCES = [
1029
+ { name: "liquid-new-or-long", params: { profile: "liquid-new-or-long", sortBy: "liquidity" } },
1030
+ { name: "liquidity-provider", params: { profile: "liquidity-provider", sortBy: "lpExpectedReturn" } },
1031
+ { name: "lp-opportunity", params: { profile: "lp-opportunity", sortBy: "rewardYield" } },
1032
+ { name: "movement", params: { sortBy: "movement", preset: "price-movers" } },
1033
+ { name: "spread", params: { sortBy: "spread", preset: "liquidity-provider" } },
1034
+ { name: "reward-yield", params: { sortBy: "rewardYield", preset: "rewards-optimizer", minRewardsDailyRate: "0.01" } }
1035
+ ];
1036
+ function num(value) {
1037
+ return Number.isFinite(value) ? Number(value) : 0;
1038
+ }
1039
+ function scoreCandidate(candidate, sourceCount) {
1040
+ const liquidityScore = Math.min(25, Math.log1p(Math.max(0, candidate.liquidity)) / Math.log1p(1e6) * 25);
1041
+ const volumeScore = Math.min(25, Math.log1p(Math.max(0, candidate.volume24h)) / Math.log1p(1e6) * 25);
1042
+ const spreadScore = Math.max(0, Math.min(15, (0.12 - Math.max(0, candidate.spread)) / 0.12 * 15));
1043
+ const movementPenalty = Math.min(20, Math.abs(candidate.oneDayPriceChange) * 100);
1044
+ const days = candidate.daysToEnd ?? null;
1045
+ const horizonScore = days === null ? 2 : days < 3 ? 0 : Math.min(10, Math.log1p(days) / Math.log1p(365) * 10);
1046
+ const rewardScore = Math.max(
1047
+ 0,
1048
+ Math.min(20, num(candidate.components?.rewardYield) * 0.1 + Math.max(0, num(candidate.lpExpectedReturnDailyPct)) * 50)
1049
+ );
1050
+ const sourceScore = Math.min(16, sourceCount * 4);
1051
+ return Math.round((liquidityScore + volumeScore + spreadScore + horizonScore + rewardScore + sourceScore - movementPenalty) * 10) / 10;
1052
+ }
1053
+ function buildFeedEnsemble(sourceResults, limit, outputPath = "candidates.json", generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
1054
+ const bySlug = /* @__PURE__ */ new Map();
1055
+ let totalRawMarkets = 0;
1056
+ for (const { source, result } of sourceResults) {
1057
+ for (const market of result.markets ?? []) {
1058
+ totalRawMarkets++;
1059
+ const existing = bySlug.get(market.slug);
1060
+ if (!existing) {
1061
+ bySlug.set(market.slug, {
1062
+ ...market,
1063
+ ensembleScore: 0,
1064
+ sourceProfiles: [source],
1065
+ sourceRanks: { [source]: market.rank }
1066
+ });
1067
+ continue;
1068
+ }
1069
+ existing.sourceProfiles.push(source);
1070
+ existing.sourceRanks[source] = market.rank;
1071
+ if (market.score > existing.score) existing.score = market.score;
1072
+ existing.volume24h = Math.max(existing.volume24h, market.volume24h);
1073
+ existing.liquidity = Math.max(existing.liquidity, market.liquidity);
1074
+ existing.rewardsDailyRate = Math.max(existing.rewardsDailyRate, market.rewardsDailyRate);
1075
+ existing.lpExpectedReturnDailyPct = Math.max(
1076
+ num(existing.lpExpectedReturnDailyPct),
1077
+ num(market.lpExpectedReturnDailyPct)
1078
+ );
1079
+ existing.lpRiskFlags = [.../* @__PURE__ */ new Set([...existing.lpRiskFlags ?? [], ...market.lpRiskFlags ?? []])];
1080
+ }
1081
+ }
1082
+ const candidates = [...bySlug.values()].map((candidate) => ({
1083
+ ...candidate,
1084
+ sourceProfiles: [...new Set(candidate.sourceProfiles)],
1085
+ ensembleScore: scoreCandidate(candidate, new Set(candidate.sourceProfiles).size)
1086
+ })).sort((a, b) => b.ensembleScore - a.ensembleScore || b.score - a.score).slice(0, limit);
1087
+ return {
1088
+ generatedAt,
1089
+ outputPath,
1090
+ totalSources: sourceResults.length,
1091
+ totalRawMarkets,
1092
+ totalCandidates: bySlug.size,
1093
+ marketsReturned: candidates.length,
1094
+ candidates
1095
+ };
1096
+ }
1097
+ function parseLimit(value, fallback) {
1098
+ const parsed = Number.parseInt(value ?? "", 10);
1099
+ if (!Number.isFinite(parsed)) return fallback;
1100
+ return Math.max(1, Math.min(100, parsed));
1101
+ }
1027
1102
  function registerFeedCommand(program2) {
1028
- program2.command("feed").description(
1103
+ const feed = program2.command("feed").description(
1029
1104
  "Rank active Polymarket markets (same engine as chat getFeed / GET /api/feed). Use --profile for curated screens."
1030
1105
  ).argument(
1031
1106
  "[screening]",
@@ -1033,7 +1108,50 @@ function registerFeedCommand(program2) {
1033
1108
  ).option(
1034
1109
  "--profile <name>",
1035
1110
  `Screening defaults: ${PROFILE_CHOICES.join(", ")} \u2014 explicit flags override`
1036
- ).option("--sort-by <key>", "score | volume | liquidity | movement | spread | recency | extremity | rewards | rewardYield | lpExpectedReturn | horizon").option("--preset <name>", "Attention weight preset (default, volume-hunter, lp-opportunity, \u2026)").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>", "Min calendar days until resolution").option("--max-days-to-end <n>", "Max calendar days until resolution").option("--max-market-age-hours <n>", 'With liquid-new-or-long: max age (hours) for the "new" branch').option("--liquid-profile <mode>", "new-or-long (used by liquid-new-or-long screen)").option("--limit <n>", "Max markets to return (1\u2013100, default 15)", "15").action(async (screening, o) => {
1111
+ ).option("--sort-by <key>", "score | volume | liquidity | movement | spread | recency | extremity | rewards | rewardYield | lpExpectedReturn | horizon").option("--preset <name>", "Attention weight preset (default, volume-hunter, lp-opportunity, \u2026)").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>", "Min calendar days until resolution").option("--max-days-to-end <n>", "Max calendar days until resolution").option("--max-market-age-hours <n>", 'With liquid-new-or-long: max age (hours) for the "new" branch').option("--liquid-profile <mode>", "new-or-long (used by liquid-new-or-long screen)").option("--limit <n>", "Max markets to return (1\u2013100, default 15)", "15");
1112
+ feed.command("ensemble").description("Run multiple feed screens, merge by slug, and write ranked candidates JSON").option("--limit <n>", "Max candidates to return/write (1-100, default 25)", "25").option("--output <file>", "Output JSON path", "candidates.json").action(async (o) => {
1113
+ const globalOpts = program2.opts();
1114
+ const client = new ApiClient(globalOpts);
1115
+ requireAuth4(client);
1116
+ const perSourceLimit = "100";
1117
+ const sourceResults = [];
1118
+ try {
1119
+ for (const source of ENSEMBLE_SOURCES) {
1120
+ const result = await client.get("/api/feed", {
1121
+ ...source.params,
1122
+ limit: perSourceLimit
1123
+ });
1124
+ if (result.error) {
1125
+ throw new Error(result.error);
1126
+ }
1127
+ sourceResults.push({ source: source.name, result });
1128
+ }
1129
+ const outputPath = o.output ?? "candidates.json";
1130
+ const ensemble = buildFeedEnsemble(sourceResults, parseLimit(o.limit, 25), outputPath);
1131
+ await writeFile(outputPath, JSON.stringify(ensemble, null, 2) + "\n", "utf8");
1132
+ if (globalOpts.json) {
1133
+ json(ensemble);
1134
+ return;
1135
+ }
1136
+ heading(`Feed Ensemble \u2014 ${ensemble.marketsReturned} candidates`);
1137
+ table(
1138
+ ensemble.candidates.slice(0, 15).map((m) => [
1139
+ String(Math.round(m.ensembleScore)),
1140
+ truncate(m.question, 48),
1141
+ `${Math.round(m.yesPrice * 100)}%`,
1142
+ compactCurrency(m.volume24h),
1143
+ compactCurrency(m.liquidity),
1144
+ m.sourceProfiles.join(",")
1145
+ ]),
1146
+ ["Score", "Market", "YES", "24h Vol", "Liq", "Sources"]
1147
+ );
1148
+ success(`Wrote ${outputPath}`);
1149
+ } catch (e) {
1150
+ error(e instanceof Error ? e.message : String(e));
1151
+ process.exit(1);
1152
+ }
1153
+ });
1154
+ feed.action(async (screening, o) => {
1037
1155
  const globalOpts = program2.opts();
1038
1156
  let profile;
1039
1157
  try {
@@ -1073,10 +1191,9 @@ function registerFeedCommand(program2) {
1073
1191
  });
1074
1192
  }
1075
1193
 
1076
- // src/commands/allocator.ts
1077
- import { InvalidArgumentError } from "commander";
1078
- import { readFile } from "fs/promises";
1079
- import chalk6 from "chalk";
1194
+ // src/commands/lp.ts
1195
+ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
1196
+ import { writeFile as writeFile2 } from "fs/promises";
1080
1197
 
1081
1198
  // src/allocator-display.ts
1082
1199
  import chalk5 from "chalk";
@@ -1096,42 +1213,10 @@ function actionColor(action) {
1096
1213
  return action;
1097
1214
  }
1098
1215
  }
1099
- function num(value, fallback = 0) {
1216
+ function num2(value, fallback = 0) {
1100
1217
  const n = Number(value);
1101
1218
  return Number.isFinite(n) ? n : fallback;
1102
1219
  }
1103
- function feedMarketsToAllocatorMarkets(markets) {
1104
- return markets.slice(0, 25).map((m) => {
1105
- const probability = num(m.probability ?? m.yesPrice, 0.5);
1106
- const daysToEnd = m.daysToEnd == null || m.daysToEnd === void 0 ? void 0 : num(m.daysToEnd);
1107
- return {
1108
- slug: String(m.slug ?? ""),
1109
- question: String(m.question ?? ""),
1110
- yesTokenId: m.yesTokenId ? String(m.yesTokenId) : void 0,
1111
- noTokenId: m.noTokenId ? String(m.noTokenId) : void 0,
1112
- yesPrice: num(m.yesPrice, probability),
1113
- noPrice: num(m.noPrice, 1 - probability),
1114
- liquidity: num(m.liquidity),
1115
- volume24h: num(m.volume24h),
1116
- spread: num(m.spread),
1117
- rewardsDailyRate: num(m.rewardsDailyRate),
1118
- oneDayPriceChange: num(m.oneDayPriceChange),
1119
- ...daysToEnd !== void 0 && { daysToEnd },
1120
- active: m.active == null ? true : Boolean(m.active)
1121
- };
1122
- });
1123
- }
1124
- function allocationsFromDecisions(decisions) {
1125
- return decisions.filter((d) => String(d.market_slug ?? "")).map((d) => ({
1126
- market_slug: String(d.market_slug),
1127
- status: String(d.action ?? "planned").toLowerCase(),
1128
- allocated_capital: num(d.target_capital),
1129
- locked_capital: 0,
1130
- inventory_yes: 0,
1131
- inventory_no: 0,
1132
- open_order_notional: Array.isArray(d.order_plan) ? d.order_plan.reduce((sum, order) => sum + num(order.notional), 0) : 0
1133
- }));
1134
- }
1135
1220
  function displayAllocatorCycleResult(result, globalOpts) {
1136
1221
  if (globalOpts.json) {
1137
1222
  json(result);
@@ -1143,9 +1228,9 @@ function displayAllocatorCycleResult(result, globalOpts) {
1143
1228
  heading(`Allocator Cycle \u2014 ${dryRun}`);
1144
1229
  process.stdout.write(
1145
1230
  chalk5.dim(
1146
- ` ${num(result.total_markets, decisions.length)} markets \xB7 ${currency(
1147
- num(summary.target_capital)
1148
- )} target capital \xB7 ${num(summary.orders_planned)} planned orders
1231
+ ` ${num2(result.total_markets, decisions.length)} markets \xB7 ${currency(
1232
+ num2(summary.target_capital)
1233
+ )} target capital
1149
1234
 
1150
1235
  `
1151
1236
  )
@@ -1156,22 +1241,30 @@ function displayAllocatorCycleResult(result, globalOpts) {
1156
1241
  }
1157
1242
  const rows = decisions.map((d) => {
1158
1243
  const action = String(d.action ?? "UNKNOWN");
1159
- const score = num(d.score?.score);
1160
- const expected = num(d.score?.expected_return_daily_pct);
1161
- const orders = Array.isArray(d.order_plan) ? d.order_plan.length : 0;
1244
+ const score = num2(d.score?.score);
1245
+ const expected = num2(d.score?.expected_return_daily_pct);
1162
1246
  const failedChecks = Array.isArray(d.safety_checks) ? d.safety_checks.filter((check) => check.passed === false).length : 0;
1163
1247
  return [
1164
1248
  truncate(String(d.question ?? d.market_slug ?? "\u2014"), 42),
1165
1249
  actionColor(action),
1166
- currency(num(d.target_capital)),
1167
- signedCurrency(num(d.capital_delta)),
1250
+ currency(num2(d.target_capital)),
1251
+ signedCurrency(num2(d.capital_delta)),
1168
1252
  `${expected.toFixed(3)}%`,
1253
+ regimeLabel(String(d.quote_regime ?? "\u2014")),
1169
1254
  String(Math.round(score)),
1170
- String(orders),
1171
1255
  failedChecks === 0 ? chalk5.green("0") : chalk5.yellow(String(failedChecks))
1172
1256
  ];
1173
1257
  });
1174
- table(rows, ["Market", "Action", "Target", "Delta", "Exp/day", "Score", "Orders", "Fails"]);
1258
+ table(rows, [
1259
+ "Market",
1260
+ "Action",
1261
+ "Target",
1262
+ "Delta",
1263
+ "Exp/day",
1264
+ "Regime",
1265
+ "Score",
1266
+ "Fails"
1267
+ ]);
1175
1268
  process.stdout.write("\n");
1176
1269
  for (const decision of decisions.slice(0, 5)) {
1177
1270
  const action = String(decision.action ?? "UNKNOWN");
@@ -1181,17 +1274,13 @@ function displayAllocatorCycleResult(result, globalOpts) {
1181
1274
  process.stdout.write(" " + chalk5.dim("Rationale: ") + truncate(decision.rationale, 120));
1182
1275
  }
1183
1276
  process.stdout.write("\n");
1184
- const orders = Array.isArray(decision.order_plan) ? decision.order_plan : [];
1185
- for (const order of orders.slice(0, 2)) {
1277
+ const economics = decision.economics ?? {};
1278
+ if (economics.realized_spread_pnl !== void 0 || economics.reward_income !== void 0 || economics.net_realized_pnl !== void 0) {
1186
1279
  process.stdout.write(
1187
- " " + chalk5.dim("Order: ") + `${String(order.side ?? "BUY")} ${String(order.outcome ?? "?")} @ ${num(order.price).toFixed(3)} for ${currency(num(order.notional))}
1280
+ " " + chalk5.dim("Economics: ") + `spread ${signedCurrency(num2(economics.realized_spread_pnl))}, rewards ${signedCurrency(num2(economics.reward_income))}, net ${signedCurrency(num2(economics.net_realized_pnl))}
1188
1281
  `
1189
1282
  );
1190
1283
  }
1191
- if (orders.length > 2) {
1192
- process.stdout.write(chalk5.dim(` \u2026 ${orders.length - 2} more orders
1193
- `));
1194
- }
1195
1284
  }
1196
1285
  if (decisions.length > 5) {
1197
1286
  process.stdout.write(chalk5.dim(`
@@ -1199,6 +1288,18 @@ function displayAllocatorCycleResult(result, globalOpts) {
1199
1288
  `));
1200
1289
  }
1201
1290
  }
1291
+ function regimeLabel(value) {
1292
+ switch (value) {
1293
+ case "reward_optimized":
1294
+ return chalk5.green("reward");
1295
+ case "defensive":
1296
+ return chalk5.yellow("defense");
1297
+ case "no_quote":
1298
+ return chalk5.red("no quote");
1299
+ default:
1300
+ return chalk5.dim(value);
1301
+ }
1302
+ }
1202
1303
  function signedCurrency(value) {
1203
1304
  const formatted = currency(Math.abs(value));
1204
1305
  if (value > 0) return chalk5.green(`+${formatted}`);
@@ -1207,23 +1308,8 @@ function signedCurrency(value) {
1207
1308
  }
1208
1309
 
1209
1310
  // src/commands/allocator.ts
1210
- var PROFILE_CHOICES2 = ["lp-opportunity", "liquidity-provider", "liquid-new-or-long"];
1211
- function requireAuth5(client) {
1212
- if (!client.isAuthenticated) {
1213
- error("Not authenticated. Run `hl auth login` first.");
1214
- process.exit(1);
1215
- }
1216
- }
1217
- function isProfile2(s) {
1218
- return s !== void 0 && PROFILE_CHOICES2.includes(s);
1219
- }
1220
- function parseNonNegative(value) {
1221
- const n = Number(value);
1222
- if (!Number.isFinite(n) || n < 0) {
1223
- throw new InvalidArgumentError("Expected a non-negative number");
1224
- }
1225
- return n;
1226
- }
1311
+ import { InvalidArgumentError } from "commander";
1312
+ import { readFile } from "fs/promises";
1227
1313
  function parsePositiveNumber(value) {
1228
1314
  const n = Number(value);
1229
1315
  if (!Number.isFinite(n) || n <= 0) {
@@ -1231,69 +1317,49 @@ function parsePositiveNumber(value) {
1231
1317
  }
1232
1318
  return n;
1233
1319
  }
1234
- function parsePositiveInt(value) {
1235
- const n = Number(value);
1236
- if (!Number.isInteger(n) || n < 1) {
1237
- throw new InvalidArgumentError("Expected a positive integer");
1320
+ function parseAllocationsInput(parsed) {
1321
+ const rows = extractArray(parsed, "allocations");
1322
+ if (!rows) {
1323
+ throw new Error("Allocations JSON must be an array or { allocations: [...] }");
1238
1324
  }
1239
- return n;
1325
+ return rows;
1240
1326
  }
1241
- function maybeAdd(entries, key, value) {
1242
- if (value !== void 0 && value !== "") entries.push([key, value]);
1327
+ function parsePnlContextInput(parsed) {
1328
+ const rows = extractArray(parsed, "pnl_context");
1329
+ if (!rows) {
1330
+ throw new Error("PnL JSON must be an array or { pnl_context: [...] }");
1331
+ }
1332
+ return rows;
1243
1333
  }
1244
- function feedQueryParams2(profile, opts) {
1245
- const entries = [];
1246
- maybeAdd(entries, "profile", profile);
1247
- maybeAdd(entries, "sortBy", opts.sortBy);
1248
- maybeAdd(entries, "tag", opts.tag);
1249
- maybeAdd(entries, "minVolume", opts.minVolume);
1250
- maybeAdd(entries, "minLiquidity", opts.minLiquidity);
1251
- maybeAdd(entries, "maxLiquidity", opts.maxLiquidity);
1252
- maybeAdd(entries, "minRewardsDailyRate", opts.minRewardsDailyRate);
1253
- maybeAdd(entries, "minDaysToEnd", opts.minDaysToEnd);
1254
- maybeAdd(entries, "maxDaysToEnd", opts.maxDaysToEnd);
1255
- maybeAdd(entries, "maxMarketAgeHours", opts.maxMarketAgeHours);
1256
- maybeAdd(entries, "liquidProfile", opts.liquidProfile);
1257
- maybeAdd(entries, "limit", opts.limit);
1258
- return Object.fromEntries(entries);
1334
+ async function readAllocations(path) {
1335
+ if (!path) return [];
1336
+ return parseAllocationsInput(await readJsonInput(path));
1259
1337
  }
1260
- function strategyFromOptions(opts) {
1261
- return {
1262
- id: "cli-dry-run",
1263
- name: "CLI dry-run LP strategy",
1264
- status: opts.paused ? "paused" : "dry_run",
1265
- ...opts.totalHoldings !== void 0 && { total_holdings: opts.totalHoldings },
1266
- ...opts.capitalLimitPct !== void 0 && { capital_limit_pct: opts.capitalLimitPct },
1267
- ...opts.perMarketLimitPct !== void 0 && { per_market_limit_pct: opts.perMarketLimitPct },
1268
- capital_limit: opts.capitalLimit,
1269
- per_market_limit: opts.perMarketLimit,
1270
- min_expected_return_daily_pct: opts.minExpectedReturnDailyPct,
1271
- max_inventory_imbalance: opts.maxInventoryImbalance,
1272
- max_order_notional: opts.maxOrderNotional,
1273
- quote_edge_bps: opts.quoteEdgeBps,
1274
- min_liquidity: opts.allocatorMinLiquidity,
1275
- max_spread: opts.maxSpread,
1276
- min_days_to_end: opts.allocatorMinDaysToEnd,
1277
- max_markets: opts.maxMarkets
1278
- };
1338
+ async function readPnlContext(path) {
1339
+ if (!path) return [];
1340
+ return parsePnlContextInput(await readJsonInput(path));
1279
1341
  }
1280
- function validatePercentageSizing(opts) {
1281
- const usesPercentageSizing = opts.capitalLimitPct !== void 0 || opts.perMarketLimitPct !== void 0;
1282
- if (usesPercentageSizing && opts.totalHoldings === void 0) {
1283
- error(
1284
- "Percentage sizing requires --total-holdings so the allocator can convert percentages into dollar caps."
1285
- );
1286
- process.exit(1);
1342
+ async function readMarketPayload(path) {
1343
+ if (!path) return void 0;
1344
+ const parsed = await readJsonInput(path);
1345
+ const rows = extractArray(parsed, "markets");
1346
+ if (!rows) {
1347
+ throw new Error("Markets JSON must be an array or { markets: [...] }");
1287
1348
  }
1349
+ return rows;
1288
1350
  }
1289
- async function readAllocations(path) {
1290
- if (!path) return [];
1291
- const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
1292
- const parsed = JSON.parse(raw);
1293
- if (!Array.isArray(parsed)) {
1294
- throw new Error("Allocations JSON must be an array");
1351
+ function extractArray(parsed, key) {
1352
+ if (Array.isArray(parsed)) {
1353
+ return parsed;
1354
+ }
1355
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed[key])) {
1356
+ return parsed[key];
1295
1357
  }
1296
- return parsed;
1358
+ return void 0;
1359
+ }
1360
+ async function readJsonInput(path) {
1361
+ const raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
1362
+ return JSON.parse(raw);
1297
1363
  }
1298
1364
  async function readStdin() {
1299
1365
  const chunks = [];
@@ -1302,96 +1368,22 @@ async function readStdin() {
1302
1368
  }
1303
1369
  return Buffer.concat(chunks).toString("utf8");
1304
1370
  }
1305
- async function runAllocatorCycle(client, payload) {
1306
- return client.post("/api/allocator/cycle", payload);
1307
- }
1308
- function registerAllocatorCommands(program2) {
1309
- const allocator = program2.command("allocator").description("Run dry-run liquidity allocation cycles");
1310
- allocator.command("cycle").description("Find LP candidates from /api/feed and run a dry-run allocator cycle").argument(
1311
- "[screening]",
1312
- `Optional screening preset: ${PROFILE_CHOICES2.join(" | ")} (same as --profile)`,
1313
- "lp-opportunity"
1314
- ).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) => {
1315
- const globalOpts = program2.opts();
1316
- const client = new ApiClient(globalOpts);
1317
- requireAuth5(client);
1318
- validatePercentageSizing(o);
1319
- let profile = o.profile ?? screening ?? "lp-opportunity";
1320
- if (!isProfile2(profile)) {
1321
- error(`Unknown screening "${profile}". Use: ${PROFILE_CHOICES2.join(" or ")}`);
1322
- process.exit(1);
1323
- }
1324
- if (o.profile && screening && screening !== "lp-opportunity" && o.profile !== screening) {
1325
- warn(`Both positional and --profile set; using --profile (${o.profile}).`);
1326
- profile = o.profile;
1327
- }
1328
- const feed = await client.get("/api/feed", feedQueryParams2(profile, o));
1329
- if (feed.error) {
1330
- error(feed.error);
1331
- process.exit(1);
1332
- }
1333
- if (feed.markets.length === 0) {
1334
- warn("No feed markets matched the allocator criteria.");
1335
- return;
1336
- }
1337
- const allocations = await readAllocations(o.allocations);
1338
- const payload = {
1339
- strategy: strategyFromOptions(o),
1340
- markets: feedMarketsToAllocatorMarkets(feed.markets),
1341
- allocations
1342
- };
1343
- if (!globalOpts.json) {
1344
- process.stderr.write(
1345
- chalk6.dim(
1346
- ` Running allocator dry-run on ${feed.markets.length} ${profile} candidates...
1347
- `
1348
- )
1349
- );
1350
- }
1351
- const first = await runAllocatorCycle(client, payload);
1352
- const firstResult = first.result ?? {};
1353
- if (o.repeat) {
1354
- const repeatPayload = {
1355
- ...payload,
1356
- allocations: allocationsFromDecisions(firstResult.decisions ?? [])
1357
- };
1358
- const second = await runAllocatorCycle(client, repeatPayload);
1359
- if (globalOpts.json) {
1360
- json({ initial: first, repeat: second });
1361
- } else {
1362
- displayAllocatorCycleResult(firstResult, globalOpts);
1363
- process.stdout.write("\n" + chalk6.bold("Repeated with target allocations") + "\n");
1364
- displayAllocatorCycleResult(second.result ?? {}, globalOpts);
1365
- }
1366
- return;
1367
- }
1368
- if (globalOpts.json) {
1369
- json(first);
1370
- } else {
1371
- displayAllocatorCycleResult(firstResult, globalOpts);
1372
- }
1373
- });
1374
- }
1375
-
1376
- // src/commands/lp.ts
1377
- import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
1378
- import { writeFile } from "fs/promises";
1379
1371
 
1380
1372
  // src/lp-display.ts
1381
- import chalk7 from "chalk";
1382
- function num2(value, fallback = 0) {
1373
+ import chalk6 from "chalk";
1374
+ function num3(value, fallback = 0) {
1383
1375
  const n = Number(value);
1384
1376
  return Number.isFinite(n) ? n : fallback;
1385
1377
  }
1386
1378
  function signedCurrency2(value) {
1387
1379
  const formatted = currency(Math.abs(value));
1388
- if (value > 0) return chalk7.green(`+${formatted}`);
1389
- if (value < 0) return chalk7.yellow(`-${formatted}`);
1390
- return chalk7.dim("$0.00");
1380
+ if (value > 0) return chalk6.green(`+${formatted}`);
1381
+ if (value < 0) return chalk6.yellow(`-${formatted}`);
1382
+ return chalk6.dim("$0.00");
1391
1383
  }
1392
1384
  function actionSummary(actions) {
1393
1385
  if (!actions) return "none";
1394
- return Object.entries(actions).filter(([, count]) => num2(count) > 0).map(([action, count]) => `${action}:${num2(count)}`).join(" ");
1386
+ return Object.entries(actions).filter(([, count]) => num3(count) > 0).map(([action, count]) => `${action}:${num3(count)}`).join(" ");
1395
1387
  }
1396
1388
  function displayLpScanResult(result, globalOpts) {
1397
1389
  if (globalOpts.json) {
@@ -1400,13 +1392,13 @@ function displayLpScanResult(result, globalOpts) {
1400
1392
  }
1401
1393
  heading("LP Scan");
1402
1394
  process.stdout.write(
1403
- chalk7.dim(
1395
+ chalk6.dim(
1404
1396
  ` scan ${result.scanId} \xB7 strategy ${result.strategyId} \xB7 ${result.evidenceSaved} evidence rows saved
1405
1397
  `
1406
1398
  )
1407
1399
  );
1408
1400
  process.stdout.write(
1409
- chalk7.dim(
1401
+ chalk6.dim(
1410
1402
  ` ${result.totalScanned.toLocaleString()} scanned \xB7 ${result.totalAfterFilter.toLocaleString()} after filters \xB7 profile ${result.profile}
1411
1403
 
1412
1404
  `
@@ -1420,10 +1412,10 @@ function displayLpScanResult(result, globalOpts) {
1420
1412
  result.markets.slice(0, 10).map((market) => [
1421
1413
  String(market.rank),
1422
1414
  truncate(market.question, 46),
1423
- String(Math.round(num2(market.score))),
1424
- compactCurrency(num2(market.liquidity)),
1425
- compactCurrency(num2(market.rewardsDailyRate)) + "/day",
1426
- `${num2(market.lpExpectedReturnDailyPct).toFixed(3)}%`
1415
+ String(Math.round(num3(market.score))),
1416
+ compactCurrency(num3(market.liquidity)),
1417
+ compactCurrency(num3(market.rewardsDailyRate)) + "/day",
1418
+ `${num3(market.lpExpectedReturnDailyPct).toFixed(3)}%`
1427
1419
  ]),
1428
1420
  ["#", "Market", "Score", "Liq", "Rewards", "Exp/day"]
1429
1421
  );
@@ -1435,13 +1427,13 @@ function displayLpRecommendResult(result, globalOpts) {
1435
1427
  }
1436
1428
  heading("LP Recommendations");
1437
1429
  process.stdout.write(
1438
- chalk7.dim(
1430
+ chalk6.dim(
1439
1431
  ` cycle ${result.cycleId} \xB7 strategy ${result.strategyId} \xB7 ${result.candidatesSubmitted} markets \xB7 ${result.allocationsSubmitted} current allocations
1440
1432
  `
1441
1433
  )
1442
1434
  );
1443
1435
  process.stdout.write(
1444
- chalk7.dim(
1436
+ chalk6.dim(
1445
1437
  ` PnL context ${result.pnlContextCount} rows${result.pnlSynced ? " \xB7 synced" : ""} \xB7 approvals required
1446
1438
 
1447
1439
  `
@@ -1456,11 +1448,11 @@ function displayLpEvaluateResult(result, globalOpts) {
1456
1448
  }
1457
1449
  heading("LP Evaluation");
1458
1450
  process.stdout.write(
1459
- chalk7.dim(
1451
+ chalk6.dim(
1460
1452
  ` strategy ${result.strategyId} \xB7 ${result.summary.snapshots} snapshots \xB7 ${result.summary.markets} markets`
1461
1453
  )
1462
1454
  );
1463
- if (result.pnlSynced) process.stdout.write(chalk7.dim(" \xB7 synced"));
1455
+ if (result.pnlSynced) process.stdout.write(chalk6.dim(" \xB7 synced"));
1464
1456
  process.stdout.write("\n\n");
1465
1457
  if (result.syncError) warn(result.syncError);
1466
1458
  table(
@@ -1482,64 +1474,64 @@ function displayLpEvaluateResult(result, globalOpts) {
1482
1474
  result.lessons.slice(0, 8).map((lesson) => [
1483
1475
  truncate(String(lesson.market_slug ?? "portfolio"), 26),
1484
1476
  String(lesson.outcome ?? "flat"),
1485
- signedCurrency2(num2(lesson.net_pnl)),
1477
+ signedCurrency2(num3(lesson.net_pnl)),
1486
1478
  truncate(String(lesson.lesson ?? "no lesson"), 64)
1487
1479
  ]),
1488
1480
  ["Market", "Outcome", "Net", "Lesson"]
1489
1481
  );
1490
1482
  }
1491
- function displayLpRunResult(result, globalOpts) {
1492
- if (globalOpts.json) {
1493
- json(result);
1494
- return;
1495
- }
1496
- heading("LP Run");
1497
- process.stdout.write(
1498
- chalk7.dim(
1499
- ` cycle ${result.run.cycleId} \xB7 scan ${result.run.scanId ?? "n/a"} \xB7 strategy ${result.run.strategyId}
1500
- `
1501
- )
1502
- );
1503
- process.stdout.write(
1504
- chalk7.dim(
1505
- ` ${result.run.opportunitiesFound} opportunities \xB7 PnL ${result.run.pnlSynced ? "synced" : "not synced"} \xB7 approvals required
1506
-
1507
- `
1508
- )
1509
- );
1510
- displayAllocatorCycleResult(result.run.result, globalOpts);
1511
- if (result.evaluation) {
1512
- process.stdout.write("\n");
1513
- process.stdout.write(
1514
- chalk7.dim(
1515
- ` Evaluation: ${signedCurrency2(result.evaluation.summary.netPnl)} net PnL across ${result.evaluation.summary.markets} markets
1516
- `
1517
- )
1518
- );
1519
- }
1520
- }
1521
1483
 
1522
1484
  // src/commands/lp.ts
1523
- function requireAuth6(client) {
1485
+ function requireAuth5(client) {
1524
1486
  if (!client.isAuthenticated) {
1525
1487
  error("Not authenticated. Run `hl auth login` first.");
1526
1488
  process.exit(1);
1527
1489
  }
1528
1490
  }
1529
- function parseNonNegative2(value) {
1491
+ function parseNonNegative(value) {
1530
1492
  const n = Number(value);
1531
1493
  if (!Number.isFinite(n) || n < 0) {
1532
1494
  throw new InvalidArgumentError2("Expected a non-negative number");
1533
1495
  }
1534
1496
  return n;
1535
1497
  }
1536
- function parsePositiveInt2(value) {
1498
+ function parsePositiveInt(value) {
1537
1499
  const n = Number(value);
1538
1500
  if (!Number.isInteger(n) || n < 1) {
1539
1501
  throw new InvalidArgumentError2("Expected a positive integer");
1540
1502
  }
1541
1503
  return n;
1542
1504
  }
1505
+ function strategyFromAllocatorOptions(opts) {
1506
+ return {
1507
+ id: "cli-lp-allocator",
1508
+ name: "CLI LP allocator dry run",
1509
+ status: opts.paused ? "paused" : "dry_run",
1510
+ ...opts.totalHoldings !== void 0 && { total_holdings: opts.totalHoldings },
1511
+ ...opts.capitalLimitPct !== void 0 && { capital_limit_pct: opts.capitalLimitPct },
1512
+ ...opts.perMarketLimitPct !== void 0 && { per_market_limit_pct: opts.perMarketLimitPct },
1513
+ capital_limit: opts.capitalLimit,
1514
+ per_market_limit: opts.perMarketLimit,
1515
+ min_expected_return_daily_pct: opts.minExpectedReturnDailyPct,
1516
+ max_inventory_imbalance: opts.maxInventoryImbalance,
1517
+ volatility_fill_spike_threshold: opts.volatilityFillSpikeThreshold,
1518
+ event_no_quote_minutes_before: opts.eventNoQuoteMinutesBefore,
1519
+ event_no_quote_minutes_after: opts.eventNoQuoteMinutesAfter,
1520
+ min_liquidity: opts.allocatorMinLiquidity,
1521
+ max_spread: opts.maxSpread,
1522
+ min_days_to_end: opts.allocatorMinDaysToEnd,
1523
+ max_markets: opts.maxMarkets
1524
+ };
1525
+ }
1526
+ function validateAllocatorPercentageSizing(opts) {
1527
+ const usesPercentageSizing = opts.capitalLimitPct !== void 0 || opts.perMarketLimitPct !== void 0;
1528
+ if (usesPercentageSizing && opts.totalHoldings === void 0) {
1529
+ error(
1530
+ "Percentage sizing requires --total-holdings so the allocator can convert percentages into dollar caps."
1531
+ );
1532
+ process.exit(1);
1533
+ }
1534
+ }
1543
1535
  function compact(payload) {
1544
1536
  return Object.fromEntries(
1545
1537
  Object.entries(payload).filter(([, value]) => value !== void 0 && value !== "")
@@ -1547,7 +1539,7 @@ function compact(payload) {
1547
1539
  }
1548
1540
  async function writeArtifact(path, data, jsonMode) {
1549
1541
  if (!path) return;
1550
- await writeFile(path, JSON.stringify(data, null, 2) + "\n", "utf8");
1542
+ await writeFile2(path, JSON.stringify(data, null, 2) + "\n", "utf8");
1551
1543
  if (!jsonMode) {
1552
1544
  process.stderr.write(dim(` Saved artifact to ${path}
1553
1545
  `));
@@ -1587,19 +1579,49 @@ function buildLpEvaluatePayload(opts) {
1587
1579
  limit: opts.limit
1588
1580
  });
1589
1581
  }
1590
- function buildLpRunPayload(opts) {
1591
- return compact({
1592
- strategyId: opts.strategyId,
1593
- limit: opts.limit,
1594
- syncPnl: opts.syncPnl
1595
- });
1582
+ async function runAllocatorCycle(client, payload) {
1583
+ return client.post("/api/lp/allocator", payload);
1596
1584
  }
1597
1585
  function registerLpCommands(program2) {
1598
1586
  const lp = program2.command("lp").description("Run persisted liquidity-provider scan, recommendation, and evaluation workflows");
1599
- 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) => {
1587
+ 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 or { allocations }; use '-' to read stdin").option("--pnl <file>", "Per-market PnL context JSON array or { pnl_context } (hl-trader pnl --json output); 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) => {
1600
1588
  const globalOpts = program2.opts();
1601
1589
  const client = new ApiClient(globalOpts);
1602
- requireAuth6(client);
1590
+ requireAuth5(client);
1591
+ validateAllocatorPercentageSizing(opts);
1592
+ const markets = await readMarketPayload(opts.markets);
1593
+ if (!markets || markets.length === 0) {
1594
+ error("Markets JSON must include at least one market.");
1595
+ process.exit(1);
1596
+ }
1597
+ if (opts.allocations === "-" && opts.pnl === "-") {
1598
+ error("Only one of --allocations and --pnl can read from stdin.");
1599
+ process.exit(1);
1600
+ }
1601
+ const pnlContext = await readPnlContext(opts.pnl);
1602
+ const payload = {
1603
+ strategy: strategyFromAllocatorOptions(opts),
1604
+ markets,
1605
+ allocations: await readAllocations(opts.allocations),
1606
+ ...pnlContext.length > 0 && { pnl_context: pnlContext }
1607
+ };
1608
+ if (!globalOpts.json) {
1609
+ process.stderr.write(
1610
+ dim(` Running allocator dry-run on ${markets.length} provided candidates...
1611
+ `)
1612
+ );
1613
+ }
1614
+ const response = await runAllocatorCycle(client, payload);
1615
+ if (globalOpts.json) {
1616
+ json(response);
1617
+ } else {
1618
+ displayAllocatorCycleResult(response.result ?? {}, globalOpts);
1619
+ }
1620
+ });
1621
+ 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) => {
1622
+ const globalOpts = program2.opts();
1623
+ const client = new ApiClient(globalOpts);
1624
+ requireAuth5(client);
1603
1625
  const result = await client.post(
1604
1626
  "/api/lp/scan",
1605
1627
  buildLpScanPayload(topic, opts)
@@ -1607,10 +1629,10 @@ function registerLpCommands(program2) {
1607
1629
  await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1608
1630
  displayLpScanResult(result, globalOpts);
1609
1631
  });
1610
- 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) => {
1632
+ 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) => {
1611
1633
  const globalOpts = program2.opts();
1612
1634
  const client = new ApiClient(globalOpts);
1613
- requireAuth6(client);
1635
+ requireAuth5(client);
1614
1636
  const result = await client.post(
1615
1637
  "/api/lp/recommend",
1616
1638
  buildLpRecommendPayload(opts)
@@ -1618,10 +1640,10 @@ function registerLpCommands(program2) {
1618
1640
  await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1619
1641
  displayLpRecommendResult(result, globalOpts);
1620
1642
  });
1621
- 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) => {
1643
+ 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) => {
1622
1644
  const globalOpts = program2.opts();
1623
1645
  const client = new ApiClient(globalOpts);
1624
- requireAuth6(client);
1646
+ requireAuth5(client);
1625
1647
  const result = await client.post(
1626
1648
  "/api/lp/evaluate",
1627
1649
  buildLpEvaluatePayload(opts)
@@ -1629,31 +1651,6 @@ function registerLpCommands(program2) {
1629
1651
  await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1630
1652
  displayLpEvaluateResult(result, globalOpts);
1631
1653
  });
1632
- 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) => {
1633
- const globalOpts = program2.opts();
1634
- const client = new ApiClient(globalOpts);
1635
- requireAuth6(client);
1636
- const run = await client.post(
1637
- "/api/lp/run",
1638
- buildLpRunPayload(opts)
1639
- );
1640
- let evaluation = null;
1641
- try {
1642
- evaluation = await client.post("/api/lp/evaluate", {
1643
- strategyId: run.strategyId,
1644
- syncPnl: false
1645
- });
1646
- } catch (error2) {
1647
- if (!globalOpts.json) {
1648
- warn(
1649
- `Could not load evaluation summary: ${error2 instanceof Error ? error2.message : String(error2)}`
1650
- );
1651
- }
1652
- }
1653
- const combined = { run, evaluation };
1654
- await writeArtifact(opts.output, combined, Boolean(globalOpts.json));
1655
- displayLpRunResult(combined, globalOpts);
1656
- });
1657
1654
  }
1658
1655
 
1659
1656
  // src/commands/wallet.ts
@@ -1664,7 +1661,7 @@ var POLYGON_NATIVE_USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
1664
1661
  var SUPPORTED_ASSETS = ["pUsd", "usdcE", "matic"];
1665
1662
  var POLYGON_NATIVE_ASSET_LABEL = "POL";
1666
1663
  var TERMINAL_WITHDRAWAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "expired"]);
1667
- function requireAuth7(client) {
1664
+ function requireAuth6(client) {
1668
1665
  if (!client.isAuthenticated) {
1669
1666
  error("Not authenticated. Run `hl auth login` first.");
1670
1667
  process.exit(1);
@@ -1849,7 +1846,7 @@ function registerWalletCommands(program2) {
1849
1846
  wallet.command("status").description("Show linked owner and Polymarket deposit wallet status").action(async () => {
1850
1847
  const globalOpts = program2.opts();
1851
1848
  const client = new ApiClient(globalOpts);
1852
- requireAuth7(client);
1849
+ requireAuth6(client);
1853
1850
  const status = await client.get("/api/wallet/status");
1854
1851
  if (globalOpts.json) {
1855
1852
  json(status);
@@ -1860,7 +1857,7 @@ function registerWalletCommands(program2) {
1860
1857
  wallet.command("balances").alias("funds").description("Show available wallet funds").action(async () => {
1861
1858
  const globalOpts = program2.opts();
1862
1859
  const client = new ApiClient(globalOpts);
1863
- requireAuth7(client);
1860
+ requireAuth6(client);
1864
1861
  const balances = await client.get("/api/wallet/balances");
1865
1862
  if (globalOpts.json) {
1866
1863
  json(balances);
@@ -1871,7 +1868,7 @@ function registerWalletCommands(program2) {
1871
1868
  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) => {
1872
1869
  const globalOpts = program2.opts();
1873
1870
  const client = new ApiClient(globalOpts);
1874
- requireAuth7(client);
1871
+ requireAuth6(client);
1875
1872
  const asset = normalizeWalletAsset(opts.asset);
1876
1873
  const result = await client.post("/api/wallet/deposit", {
1877
1874
  bridge: Boolean(opts.bridge)
@@ -1894,7 +1891,7 @@ function registerWalletCommands(program2) {
1894
1891
  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) => {
1895
1892
  const globalOpts = program2.opts();
1896
1893
  const client = new ApiClient(globalOpts);
1897
- requireAuth7(client);
1894
+ requireAuth6(client);
1898
1895
  const created = await client.post(
1899
1896
  "/api/wallet/withdraw/intents",
1900
1897
  buildWithdrawRequestPayload(opts)
@@ -1924,7 +1921,7 @@ import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
1924
1921
  import { readFile as readFile2 } from "fs/promises";
1925
1922
 
1926
1923
  // src/signal-display.ts
1927
- import chalk8 from "chalk";
1924
+ import chalk7 from "chalk";
1928
1925
  function pctFromSignalValue(value) {
1929
1926
  if (value === null || value === void 0 || Number.isNaN(value)) return null;
1930
1927
  return Math.abs(value) <= 1 ? value * 100 : value;
@@ -1937,14 +1934,14 @@ function formatGap(value) {
1937
1934
  if (value === null || value === void 0 || Number.isNaN(value)) return "n/a";
1938
1935
  const points = Math.abs(value) <= 1 ? value * 100 : value;
1939
1936
  const formatted = `${points >= 0 ? "+" : ""}${points.toFixed(1)}pp`;
1940
- if (points > 0) return chalk8.green(formatted);
1941
- if (points < 0) return chalk8.red(formatted);
1942
- return chalk8.dim(formatted);
1937
+ if (points > 0) return chalk7.green(formatted);
1938
+ if (points < 0) return chalk7.red(formatted);
1939
+ return chalk7.dim(formatted);
1943
1940
  }
1944
1941
  function formatStrength(value) {
1945
- if (value === "strong") return chalk8.green("strong");
1946
- if (value === "weak") return chalk8.yellow("weak");
1947
- return value ? chalk8.dim(value) : "n/a";
1942
+ if (value === "strong") return chalk7.green("strong");
1943
+ if (value === "weak") return chalk7.yellow("weak");
1944
+ return value ? chalk7.dim(value) : "n/a";
1948
1945
  }
1949
1946
  function analysisItems(result) {
1950
1947
  if (!result) return [];
@@ -1991,27 +1988,27 @@ function displaySignalAnalysis(response, globalOpts) {
1991
1988
  for (const item of items.slice(0, 3)) {
1992
1989
  const analysis = item.analysis;
1993
1990
  if (!analysis) continue;
1994
- process.stdout.write("\n " + chalk8.bold(truncate(titleFor(analysis), 76)) + "\n");
1991
+ process.stdout.write("\n " + chalk7.bold(truncate(titleFor(analysis), 76)) + "\n");
1995
1992
  if (analysis.market_link) {
1996
- process.stdout.write(" " + chalk8.dim("Polymarket: ") + analysis.market_link + "\n");
1993
+ process.stdout.write(" " + chalk7.dim("Polymarket: ") + analysis.market_link + "\n");
1997
1994
  }
1998
1995
  if (analysis.key_factors && analysis.key_factors.length > 0) {
1999
- process.stdout.write(" " + chalk8.dim("Key factors: ") + analysis.key_factors.slice(0, 4).join("; ") + "\n");
1996
+ process.stdout.write(" " + chalk7.dim("Key factors: ") + analysis.key_factors.slice(0, 4).join("; ") + "\n");
2000
1997
  }
2001
1998
  if (analysis.research_findings) {
2002
1999
  process.stdout.write(
2003
- " " + chalk8.dim("Research: ") + truncate(analysis.research_findings, 180) + "\n"
2000
+ " " + chalk7.dim("Research: ") + truncate(analysis.research_findings, 180) + "\n"
2004
2001
  );
2005
2002
  }
2006
2003
  }
2007
2004
  if (items.length > 3) {
2008
- process.stdout.write(chalk8.dim(`
2005
+ process.stdout.write(chalk7.dim(`
2009
2006
  ... and ${items.length - 3} more
2010
2007
  `));
2011
2008
  }
2012
2009
  if (result?.strong_signal_count !== void 0) {
2013
2010
  process.stdout.write(
2014
- chalk8.dim(`
2011
+ chalk7.dim(`
2015
2012
  Strong signals: ${result.strong_signal_count}
2016
2013
  `)
2017
2014
  );
@@ -2019,7 +2016,7 @@ function displaySignalAnalysis(response, globalOpts) {
2019
2016
  }
2020
2017
 
2021
2018
  // src/commands/signal.ts
2022
- function requireAuth8(client) {
2019
+ function requireAuth7(client) {
2023
2020
  if (!client.isAuthenticated) {
2024
2021
  error("Not authenticated. Run `hl auth login` first.");
2025
2022
  process.exit(1);
@@ -2042,7 +2039,7 @@ async function readStdin2() {
2042
2039
  }
2043
2040
  return Buffer.concat(chunks).toString("utf8");
2044
2041
  }
2045
- async function readMarketPayload(path) {
2042
+ async function readMarketPayload2(path) {
2046
2043
  if (!path) return {};
2047
2044
  const raw = path === "-" ? await readStdin2() : await readFile2(path, "utf8");
2048
2045
  const parsed = JSON.parse(raw);
@@ -2074,7 +2071,7 @@ async function buildSignalPayload(positionalUrl, opts) {
2074
2071
  const urls = [positionalUrl, ...opts.url ?? []].filter(
2075
2072
  (value) => Boolean(value)
2076
2073
  );
2077
- const filePayload = await readMarketPayload(opts.market);
2074
+ const filePayload = await readMarketPayload2(opts.market);
2078
2075
  const inlineMarket = inlineMarketFromOptions(opts);
2079
2076
  const hasMarketInput = Boolean(filePayload.market || filePayload.markets || inlineMarket);
2080
2077
  if (urls.length > 0 && hasMarketInput) {
@@ -2103,7 +2100,7 @@ function registerSignalCommands(program2) {
2103
2100
  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) => {
2104
2101
  const globalOpts = program2.opts();
2105
2102
  const client = new ApiClient(globalOpts);
2106
- requireAuth8(client);
2103
+ requireAuth7(client);
2107
2104
  let payload;
2108
2105
  try {
2109
2106
  payload = await buildSignalPayload(url, o);
@@ -2120,8 +2117,8 @@ function registerSignalCommands(program2) {
2120
2117
  }
2121
2118
 
2122
2119
  // src/index.ts
2123
- var program = new Command5();
2124
- program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("1.8.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");
2120
+ var program = new Command4();
2121
+ program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("2.1.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");
2125
2122
  registerAuthCommands(program);
2126
2123
  registerBriefCommands(program);
2127
2124
  registerProfileCommand(program);
@@ -2129,7 +2126,6 @@ registerResearchCommands(program);
2129
2126
  registerFeedCommand(program);
2130
2127
  registerWalletCommands(program);
2131
2128
  registerLpCommands(program);
2132
- registerAllocatorCommands(program);
2133
2129
  registerSignalCommands(program);
2134
2130
  program.hook("preAction", (_thisCommand, actionCommand) => {
2135
2131
  const opts = program.opts();