@hedge-layer/cli 2.2.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1247,7 +1247,6 @@ function registerFeedCommand(program2) {
1247
1247
 
1248
1248
  // src/commands/lp.ts
1249
1249
  import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
1250
- import { writeFile as writeFile2 } from "fs/promises";
1251
1250
 
1252
1251
  // src/allocator-display.ts
1253
1252
  import chalk5 from "chalk";
@@ -1423,118 +1422,6 @@ async function readStdin() {
1423
1422
  return Buffer.concat(chunks).toString("utf8");
1424
1423
  }
1425
1424
 
1426
- // src/lp-display.ts
1427
- import chalk6 from "chalk";
1428
- function num3(value, fallback = 0) {
1429
- const n = Number(value);
1430
- return Number.isFinite(n) ? n : fallback;
1431
- }
1432
- function signedCurrency2(value) {
1433
- const formatted = currency(Math.abs(value));
1434
- if (value > 0) return chalk6.green(`+${formatted}`);
1435
- if (value < 0) return chalk6.yellow(`-${formatted}`);
1436
- return chalk6.dim("$0.00");
1437
- }
1438
- function actionSummary(actions) {
1439
- if (!actions) return "none";
1440
- return Object.entries(actions).filter(([, count]) => num3(count) > 0).map(([action, count]) => `${action}:${num3(count)}`).join(" ");
1441
- }
1442
- function displayLpScanResult(result, globalOpts) {
1443
- if (globalOpts.json) {
1444
- json(result);
1445
- return;
1446
- }
1447
- heading("LP Scan");
1448
- process.stdout.write(
1449
- chalk6.dim(
1450
- ` scan ${result.scanId} \xB7 strategy ${result.strategyId} \xB7 ${result.evidenceSaved} evidence rows saved
1451
- `
1452
- )
1453
- );
1454
- process.stdout.write(
1455
- chalk6.dim(
1456
- ` ${result.totalScanned.toLocaleString()} scanned \xB7 ${result.totalAfterFilter.toLocaleString()} after filters \xB7 profile ${result.profile}
1457
-
1458
- `
1459
- )
1460
- );
1461
- if (result.markets.length === 0) {
1462
- warn("No markets matched the LP scan.");
1463
- return;
1464
- }
1465
- table(
1466
- result.markets.slice(0, 10).map((market) => [
1467
- String(market.rank),
1468
- truncate(market.question, 46),
1469
- String(Math.round(num3(market.score))),
1470
- compactCurrency(num3(market.liquidity)),
1471
- compactCurrency(num3(market.rewardsDailyRate)) + "/day",
1472
- `${num3(market.lpExpectedReturnDailyPct).toFixed(3)}%`
1473
- ]),
1474
- ["#", "Market", "Score", "Liq", "Rewards", "Exp/day"]
1475
- );
1476
- }
1477
- function displayLpRecommendResult(result, globalOpts) {
1478
- if (globalOpts.json) {
1479
- json(result);
1480
- return;
1481
- }
1482
- heading("LP Recommendations");
1483
- process.stdout.write(
1484
- chalk6.dim(
1485
- ` cycle ${result.cycleId} \xB7 strategy ${result.strategyId} \xB7 ${result.candidatesSubmitted} markets \xB7 ${result.allocationsSubmitted} current allocations
1486
- `
1487
- )
1488
- );
1489
- process.stdout.write(
1490
- chalk6.dim(
1491
- ` PnL context ${result.pnlContextCount} rows${result.pnlSynced ? " \xB7 synced" : ""} \xB7 approvals required
1492
-
1493
- `
1494
- )
1495
- );
1496
- displayAllocatorCycleResult(result.result ?? { decisions: result.decisions }, globalOpts);
1497
- }
1498
- function displayLpEvaluateResult(result, globalOpts) {
1499
- if (globalOpts.json) {
1500
- json(result);
1501
- return;
1502
- }
1503
- heading("LP Evaluation");
1504
- process.stdout.write(
1505
- chalk6.dim(
1506
- ` strategy ${result.strategyId} \xB7 ${result.summary.snapshots} snapshots \xB7 ${result.summary.markets} markets`
1507
- )
1508
- );
1509
- if (result.pnlSynced) process.stdout.write(chalk6.dim(" \xB7 synced"));
1510
- process.stdout.write("\n\n");
1511
- if (result.syncError) warn(result.syncError);
1512
- table(
1513
- [
1514
- ["Realized PnL", signedCurrency2(result.summary.realizedPnl)],
1515
- ["Unrealized PnL", signedCurrency2(result.summary.unrealizedPnl)],
1516
- ["Net PnL", signedCurrency2(result.summary.netPnl)],
1517
- ["Capital locked", currency(result.summary.capitalLocked)],
1518
- ["Current value", currency(result.summary.currentValue)],
1519
- ["Outcomes", actionSummary(result.summary.outcomes)]
1520
- ]
1521
- );
1522
- if (result.lessons.length === 0) {
1523
- warn("No PnL lessons available yet.");
1524
- return;
1525
- }
1526
- process.stdout.write("\n");
1527
- table(
1528
- result.lessons.slice(0, 8).map((lesson) => [
1529
- truncate(String(lesson.market_slug ?? "portfolio"), 26),
1530
- String(lesson.outcome ?? "flat"),
1531
- signedCurrency2(num3(lesson.net_pnl)),
1532
- truncate(String(lesson.lesson ?? "no lesson"), 64)
1533
- ]),
1534
- ["Market", "Outcome", "Net", "Lesson"]
1535
- );
1536
- }
1537
-
1538
1425
  // src/commands/lp.ts
1539
1426
  function requireAuth5(client) {
1540
1427
  if (!client.isAuthenticated) {
@@ -1586,59 +1473,12 @@ function validateAllocatorPercentageSizing(opts) {
1586
1473
  process.exit(1);
1587
1474
  }
1588
1475
  }
1589
- function compact(payload) {
1590
- return Object.fromEntries(
1591
- Object.entries(payload).filter(([, value]) => value !== void 0 && value !== "")
1592
- );
1593
- }
1594
- async function writeArtifact(path, data, jsonMode) {
1595
- if (!path) return;
1596
- await writeFile2(path, JSON.stringify(data, null, 2) + "\n", "utf8");
1597
- if (!jsonMode) {
1598
- process.stderr.write(dim(` Saved artifact to ${path}
1599
- `));
1600
- }
1601
- }
1602
- function buildLpScanPayload(topic, opts) {
1603
- return compact({
1604
- topic: topic?.trim() || void 0,
1605
- strategyId: opts.strategyId,
1606
- profile: opts.profile,
1607
- sortBy: opts.sortBy,
1608
- tag: opts.tag,
1609
- minVolume: opts.minVolume,
1610
- minLiquidity: opts.minLiquidity,
1611
- maxLiquidity: opts.maxLiquidity,
1612
- minRewardsDailyRate: opts.minRewardsDailyRate,
1613
- minDaysToEnd: opts.minDaysToEnd,
1614
- maxDaysToEnd: opts.maxDaysToEnd,
1615
- maxMarketAgeHours: opts.maxMarketAgeHours,
1616
- liquidProfile: opts.liquidProfile,
1617
- limit: opts.limit
1618
- });
1619
- }
1620
- function buildLpRecommendPayload(opts) {
1621
- return compact({
1622
- strategyId: opts.strategyId,
1623
- scanId: opts.scanId,
1624
- limit: opts.limit,
1625
- syncPnl: opts.syncPnl
1626
- });
1627
- }
1628
- function buildLpEvaluatePayload(opts) {
1629
- return compact({
1630
- strategyId: opts.strategyId,
1631
- walletAddress: opts.walletAddress,
1632
- syncPnl: opts.syncPnl,
1633
- limit: opts.limit
1634
- });
1635
- }
1636
1476
  async function runAllocatorCycle(client, payload) {
1637
1477
  return client.post("/api/lp/allocator", payload);
1638
1478
  }
1639
1479
  function registerLpCommands(program2) {
1640
- const lp = program2.command("lp").description("Run persisted liquidity-provider scan, recommendation, and evaluation workflows");
1641
- 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) => {
1480
+ const lp = program2.command("lp").description("Generate dry-run liquidity-provider allocation plans");
1481
+ 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 } (external wallet/inventory export); 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) => {
1642
1482
  const globalOpts = program2.opts();
1643
1483
  const client = new ApiClient(globalOpts);
1644
1484
  requireAuth5(client);
@@ -1672,310 +1512,14 @@ function registerLpCommands(program2) {
1672
1512
  displayAllocatorCycleResult(response.result ?? {}, globalOpts);
1673
1513
  }
1674
1514
  });
1675
- 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) => {
1676
- const globalOpts = program2.opts();
1677
- const client = new ApiClient(globalOpts);
1678
- requireAuth5(client);
1679
- const result = await client.post(
1680
- "/api/lp/scan",
1681
- buildLpScanPayload(topic, opts)
1682
- );
1683
- await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1684
- displayLpScanResult(result, globalOpts);
1685
- });
1686
- 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) => {
1687
- const globalOpts = program2.opts();
1688
- const client = new ApiClient(globalOpts);
1689
- requireAuth5(client);
1690
- const result = await client.post(
1691
- "/api/lp/recommend",
1692
- buildLpRecommendPayload(opts)
1693
- );
1694
- await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1695
- displayLpRecommendResult(result, globalOpts);
1696
- });
1697
- 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) => {
1698
- const globalOpts = program2.opts();
1699
- const client = new ApiClient(globalOpts);
1700
- requireAuth5(client);
1701
- const result = await client.post(
1702
- "/api/lp/evaluate",
1703
- buildLpEvaluatePayload(opts)
1704
- );
1705
- await writeArtifact(opts.output, result, Boolean(globalOpts.json));
1706
- displayLpEvaluateResult(result, globalOpts);
1707
- });
1708
- }
1709
-
1710
- // src/commands/wallet.ts
1711
- import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
1712
- import { spawn } from "child_process";
1713
- var NETWORK_NAME = "Polygon";
1714
- var POLYGON_NATIVE_USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
1715
- var SUPPORTED_ASSETS = ["pUsd", "usdcE", "matic"];
1716
- var POLYGON_NATIVE_ASSET_LABEL = "POL";
1717
- var TERMINAL_WITHDRAWAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "expired"]);
1718
- function requireAuth6(client) {
1719
- if (!client.isAuthenticated) {
1720
- error("Not authenticated. Run `hl auth login` first.");
1721
- process.exit(1);
1722
- }
1723
- }
1724
- function parsePositiveNumber2(value) {
1725
- const n = Number(value);
1726
- if (!Number.isFinite(n) || n <= 0) {
1727
- throw new InvalidArgumentError3("Expected a positive number");
1728
- }
1729
- return n;
1730
- }
1731
- function parseWalletAddress(value) {
1732
- const trimmed = value.trim();
1733
- if (!/^0x[a-fA-F0-9]{40}$/.test(trimmed)) {
1734
- throw new InvalidArgumentError3("Expected a valid EVM wallet address");
1735
- }
1736
- return trimmed;
1737
- }
1738
- function normalizeWalletAsset(value) {
1739
- if (!value) return void 0;
1740
- const normalized = value.trim().toLowerCase().replace(/[._\s-]/g, "");
1741
- if (["pusd", "polymarketusd"].includes(normalized)) return "pUsd";
1742
- if (["usdce", "usdcebridged", "bridgedusdc", "usdc"].includes(normalized)) return "usdcE";
1743
- if (["matic", "pol"].includes(normalized)) return "matic";
1744
- throw new InvalidArgumentError3("Unknown wallet asset. Use pUSD, USDC.e, or POL.");
1745
- }
1746
- function assetByKey(balances, key) {
1747
- return balances.assets[key];
1748
- }
1749
- function tokenAddress(asset) {
1750
- return asset.address ?? "native token";
1751
- }
1752
- function displayAmount(asset) {
1753
- if (!asset.ok) {
1754
- return `unavailable${asset.error ? ` (${asset.error})` : ""}`;
1755
- }
1756
- const numeric = Number(asset.balance);
1757
- if (asset.address === null && asset.decimals === 18) {
1758
- return Number.isFinite(numeric) ? numeric.toLocaleString("en-US", { maximumFractionDigits: 6 }) : asset.balance;
1759
- }
1760
- return Number.isFinite(numeric) ? currency(numeric) : asset.balance;
1761
- }
1762
- function displayAssetSymbol(asset) {
1763
- return asset.address === null && asset.decimals === 18 ? POLYGON_NATIVE_ASSET_LABEL : asset.symbol;
1764
- }
1765
- function buildDepositInstructions(balances, assetKey) {
1766
- const assets = assetKey ? [assetByKey(balances, assetKey)] : SUPPORTED_ASSETS.map((key) => assetByKey(balances, key));
1767
- return {
1768
- action: "deposit",
1769
- publicDepositAddress: balances.walletAddress,
1770
- walletAddress: balances.walletAddress,
1771
- chainId: balances.chainId,
1772
- network: NETWORK_NAME,
1773
- asset: assetKey ? assetByKey(balances, assetKey) : null,
1774
- assets,
1775
- warning: "Send only Polygon assets to this address. Hedge Layer cannot reverse external transfers."
1776
- };
1777
- }
1778
- function buildWithdrawRequestPayload(opts) {
1779
- const assetKey = normalizeWalletAsset(opts.asset) ?? "pUsd";
1780
- if (assetKey !== "pUsd") {
1781
- throw new InvalidArgumentError3("Bridge withdrawals currently send pUSD. Use --asset pUSD.");
1782
- }
1783
- return {
1784
- amount: opts.amount,
1785
- recipientAddress: opts.to,
1786
- toChainId: opts.toChainId ?? "137",
1787
- toTokenAddress: opts.toTokenAddress ?? POLYGON_NATIVE_USDC
1788
- };
1789
- }
1790
- function displayWalletStatus(status) {
1791
- const ownerWallet = status.ownerWallet ?? status.wallet;
1792
- const depositWallet = status.depositWallet ?? status.tradingWallet ?? null;
1793
- heading("Wallet");
1794
- table([
1795
- ["Owner linked", status.linked ? "yes" : "no"],
1796
- ["Provider", status.provider],
1797
- ["Owner wallet", ownerWallet?.wallet_address ?? "(none)"],
1798
- ["Owner chain", ownerWallet ? `${NETWORK_NAME} (${ownerWallet.chain_id})` : "(none)"],
1799
- ["Owner linked at", ownerWallet?.linked_at ? new Date(ownerWallet.linked_at).toLocaleString() : "(none)"],
1800
- ["Deposit wallet", depositWallet?.wallet_address ?? "(none)"],
1801
- ["Deposit deployed", status.depositWalletDeployed ? "yes" : "no"],
1802
- ["Deposit approved", status.depositWalletApproved ? "yes" : "no"],
1803
- ["Deposit ready", status.depositWalletReady ? "yes" : "no"],
1804
- ["Relayer configured", status.relayerConfigured ? "yes" : "no"]
1805
- ]);
1806
- }
1807
- function displayWalletBalances(balances) {
1808
- heading("Wallet Funds");
1809
- table([
1810
- ["Wallet", balances.walletAddress],
1811
- ["Chain", `${NETWORK_NAME} (${balances.chainId})`],
1812
- ["Available pUSD", displayAmount(balances.available)],
1813
- ["USDC.e", displayAmount(balances.assets.usdcE)],
1814
- [displayAssetSymbol(balances.assets.matic), displayAmount(balances.assets.matic)],
1815
- ["Updated", new Date(balances.updatedAt).toLocaleString()]
1816
- ]);
1817
- }
1818
- function displayDepositInstructions(instructions) {
1819
- heading("Deposit");
1820
- table([
1821
- ["Public deposit address", instructions.publicDepositAddress],
1822
- ["Network", `${instructions.network} (${instructions.chainId})`]
1823
- ]);
1824
- process.stdout.write("\n");
1825
- table(
1826
- instructions.assets.map((asset) => [
1827
- displayAssetSymbol(asset),
1828
- tokenAddress(asset),
1829
- displayAmount(asset)
1830
- ]),
1831
- ["Asset", "Token contract", "Current balance"]
1832
- );
1833
- process.stdout.write("\n");
1834
- warn(instructions.warning);
1835
- }
1836
- function displayBridgeDeposit(result) {
1837
- const bridgeAddresses = result.bridge?.address ?? {};
1838
- const rows = Object.entries(bridgeAddresses).filter(([, value]) => typeof value === "string" && value.length > 0).map(([network, value]) => [network.toUpperCase(), value]);
1839
- if (rows.length === 0) return;
1840
- process.stdout.write("\n");
1841
- table(rows, ["Bridge network", "Bridge deposit address"]);
1842
- if (result.bridge?.note) {
1843
- process.stdout.write("\n" + dim(` ${result.bridge.note}
1844
- `));
1845
- }
1846
- for (const warning of result.bridge?.warnings ?? []) {
1847
- if (warning.message) warn(warning.message);
1848
- }
1849
- }
1850
- function displayWithdrawIntent(intent) {
1851
- heading("Withdraw");
1852
- table([
1853
- ["Intent", intent.id],
1854
- ["Status", intent.status],
1855
- ["Wallet", intent.walletAddress],
1856
- ["Amount", `${intent.amount} pUSD`],
1857
- ["Recipient", intent.recipientAddress],
1858
- ["Destination", `chain ${intent.toChainId} \xB7 ${intent.toTokenAddress}`],
1859
- ["Bridge address", intent.bridgeAddresses.evm ?? "(none)"],
1860
- ["Signing URL", intent.signingUrl]
1861
- ]);
1862
- const quote = intent.quote ?? {};
1863
- if (quote.estOutputUsd !== void 0 || quote.estCheckoutTimeMs !== void 0) {
1864
- process.stdout.write("\n");
1865
- table([
1866
- ["Estimated output", quote.estOutputUsd !== void 0 ? currency(Number(quote.estOutputUsd)) : "-"],
1867
- ["Estimated checkout", quote.estCheckoutTimeMs !== void 0 ? `${Math.round(Number(quote.estCheckoutTimeMs) / 1e3)}s` : "-"]
1868
- ]);
1869
- }
1870
- }
1871
- function openBrowser(url) {
1872
- const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
1873
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
1874
- const child = spawn(command, args, { detached: true, stdio: "ignore" });
1875
- child.unref();
1876
- }
1877
- function sleep(ms) {
1878
- return new Promise((resolve) => setTimeout(resolve, ms));
1879
- }
1880
- async function pollWithdrawalIntent(client, id, opts) {
1881
- const started = Date.now();
1882
- let lastStatus = "";
1883
- while (Date.now() - started <= opts.timeoutSeconds * 1e3) {
1884
- const result = await client.get(
1885
- `/api/wallet/withdraw/intents/${encodeURIComponent(id)}`
1886
- );
1887
- const intent = result.intent;
1888
- if (!opts.jsonMode && intent.status !== lastStatus) {
1889
- process.stderr.write(dim(` Withdrawal status: ${intent.status}
1890
- `));
1891
- lastStatus = intent.status;
1892
- }
1893
- if (TERMINAL_WITHDRAWAL_STATUSES.has(intent.status)) return intent;
1894
- await sleep(opts.intervalSeconds * 1e3);
1895
- }
1896
- throw new Error(`Timed out waiting for withdrawal intent ${id}`);
1897
- }
1898
- function registerWalletCommands(program2) {
1899
- const wallet = program2.command("wallet").description("Inspect linked wallet funds and funding instructions");
1900
- wallet.command("status").description("Show linked owner and Polymarket deposit wallet status").action(async () => {
1901
- const globalOpts = program2.opts();
1902
- const client = new ApiClient(globalOpts);
1903
- requireAuth6(client);
1904
- const status = await client.get("/api/wallet/status");
1905
- if (globalOpts.json) {
1906
- json(status);
1907
- return;
1908
- }
1909
- displayWalletStatus(status);
1910
- });
1911
- wallet.command("balances").alias("funds").description("Show available wallet funds").action(async () => {
1912
- const globalOpts = program2.opts();
1913
- const client = new ApiClient(globalOpts);
1914
- requireAuth6(client);
1915
- const balances = await client.get("/api/wallet/balances");
1916
- if (globalOpts.json) {
1917
- json(balances);
1918
- return;
1919
- }
1920
- displayWalletBalances(balances);
1921
- });
1922
- 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) => {
1923
- const globalOpts = program2.opts();
1924
- const client = new ApiClient(globalOpts);
1925
- requireAuth6(client);
1926
- const asset = normalizeWalletAsset(opts.asset);
1927
- const result = await client.post("/api/wallet/deposit", {
1928
- bridge: Boolean(opts.bridge)
1929
- });
1930
- const balances = {
1931
- walletAddress: result.direct.walletAddress,
1932
- chainId: result.direct.chainId,
1933
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1934
- available: result.direct.assets.pUsd,
1935
- assets: result.direct.assets
1936
- };
1937
- const instructions = buildDepositInstructions(balances, asset);
1938
- if (globalOpts.json) {
1939
- json({ ...result, instructions });
1940
- return;
1941
- }
1942
- displayDepositInstructions(instructions);
1943
- displayBridgeDeposit(result);
1944
- });
1945
- 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) => {
1946
- const globalOpts = program2.opts();
1947
- const client = new ApiClient(globalOpts);
1948
- requireAuth6(client);
1949
- const created = await client.post(
1950
- "/api/wallet/withdraw/intents",
1951
- buildWithdrawRequestPayload(opts)
1952
- );
1953
- let intent = created.intent;
1954
- if (opts.open !== false) {
1955
- openBrowser(intent.signingUrl);
1956
- }
1957
- if (opts.wait !== false) {
1958
- intent = await pollWithdrawalIntent(client, intent.id, {
1959
- timeoutSeconds: opts.timeout ?? 600,
1960
- intervalSeconds: opts.pollInterval ?? 5,
1961
- jsonMode: Boolean(globalOpts.json)
1962
- });
1963
- }
1964
- if (globalOpts.json) {
1965
- json(intent);
1966
- return;
1967
- } else {
1968
- displayWithdrawIntent(intent);
1969
- }
1970
- });
1971
1515
  }
1972
1516
 
1973
1517
  // src/commands/signal.ts
1974
- import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
1518
+ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
1975
1519
  import { readFile as readFile2 } from "fs/promises";
1976
1520
 
1977
1521
  // src/signal-display.ts
1978
- import chalk7 from "chalk";
1522
+ import chalk6 from "chalk";
1979
1523
  function pctFromSignalValue(value) {
1980
1524
  if (value === null || value === void 0 || Number.isNaN(value)) return null;
1981
1525
  return Math.abs(value) <= 1 ? value * 100 : value;
@@ -1988,14 +1532,14 @@ function formatGap(value) {
1988
1532
  if (value === null || value === void 0 || Number.isNaN(value)) return "n/a";
1989
1533
  const points = Math.abs(value) <= 1 ? value * 100 : value;
1990
1534
  const formatted = `${points >= 0 ? "+" : ""}${points.toFixed(1)}pp`;
1991
- if (points > 0) return chalk7.green(formatted);
1992
- if (points < 0) return chalk7.red(formatted);
1993
- return chalk7.dim(formatted);
1535
+ if (points > 0) return chalk6.green(formatted);
1536
+ if (points < 0) return chalk6.red(formatted);
1537
+ return chalk6.dim(formatted);
1994
1538
  }
1995
1539
  function formatStrength(value) {
1996
- if (value === "strong") return chalk7.green("strong");
1997
- if (value === "weak") return chalk7.yellow("weak");
1998
- return value ? chalk7.dim(value) : "n/a";
1540
+ if (value === "strong") return chalk6.green("strong");
1541
+ if (value === "weak") return chalk6.yellow("weak");
1542
+ return value ? chalk6.dim(value) : "n/a";
1999
1543
  }
2000
1544
  function analysisItems(result) {
2001
1545
  if (!result) return [];
@@ -2042,27 +1586,27 @@ function displaySignalAnalysis(response, globalOpts) {
2042
1586
  for (const item of items.slice(0, 3)) {
2043
1587
  const analysis = item.analysis;
2044
1588
  if (!analysis) continue;
2045
- process.stdout.write("\n " + chalk7.bold(truncate(titleFor(analysis), 76)) + "\n");
1589
+ process.stdout.write("\n " + chalk6.bold(truncate(titleFor(analysis), 76)) + "\n");
2046
1590
  if (analysis.market_link) {
2047
- process.stdout.write(" " + chalk7.dim("Polymarket: ") + analysis.market_link + "\n");
1591
+ process.stdout.write(" " + chalk6.dim("Polymarket: ") + analysis.market_link + "\n");
2048
1592
  }
2049
1593
  if (analysis.key_factors && analysis.key_factors.length > 0) {
2050
- process.stdout.write(" " + chalk7.dim("Key factors: ") + analysis.key_factors.slice(0, 4).join("; ") + "\n");
1594
+ process.stdout.write(" " + chalk6.dim("Key factors: ") + analysis.key_factors.slice(0, 4).join("; ") + "\n");
2051
1595
  }
2052
1596
  if (analysis.research_findings) {
2053
1597
  process.stdout.write(
2054
- " " + chalk7.dim("Research: ") + truncate(analysis.research_findings, 180) + "\n"
1598
+ " " + chalk6.dim("Research: ") + truncate(analysis.research_findings, 180) + "\n"
2055
1599
  );
2056
1600
  }
2057
1601
  }
2058
1602
  if (items.length > 3) {
2059
- process.stdout.write(chalk7.dim(`
1603
+ process.stdout.write(chalk6.dim(`
2060
1604
  ... and ${items.length - 3} more
2061
1605
  `));
2062
1606
  }
2063
1607
  if (result?.strong_signal_count !== void 0) {
2064
1608
  process.stdout.write(
2065
- chalk7.dim(`
1609
+ chalk6.dim(`
2066
1610
  Strong signals: ${result.strong_signal_count}
2067
1611
  `)
2068
1612
  );
@@ -2070,7 +1614,7 @@ function displaySignalAnalysis(response, globalOpts) {
2070
1614
  }
2071
1615
 
2072
1616
  // src/commands/signal.ts
2073
- function requireAuth7(client) {
1617
+ function requireAuth6(client) {
2074
1618
  if (!client.isAuthenticated) {
2075
1619
  error("Not authenticated. Run `hl auth login` first.");
2076
1620
  process.exit(1);
@@ -2082,7 +1626,7 @@ function collect(value, previous = []) {
2082
1626
  function parseProbability(value) {
2083
1627
  const n = Number(value);
2084
1628
  if (!Number.isFinite(n) || n < 0 || n > 100) {
2085
- throw new InvalidArgumentError4("Expected a probability between 0 and 100");
1629
+ throw new InvalidArgumentError3("Expected a probability between 0 and 100");
2086
1630
  }
2087
1631
  return n;
2088
1632
  }
@@ -2154,7 +1698,7 @@ function registerSignalCommands(program2) {
2154
1698
  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) => {
2155
1699
  const globalOpts = program2.opts();
2156
1700
  const client = new ApiClient(globalOpts);
2157
- requireAuth7(client);
1701
+ requireAuth6(client);
2158
1702
  let payload;
2159
1703
  try {
2160
1704
  payload = await buildSignalPayload(url, o);
@@ -2170,17 +1714,196 @@ function registerSignalCommands(program2) {
2170
1714
  });
2171
1715
  }
2172
1716
 
1717
+ // src/commands/quote.ts
1718
+ import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
1719
+ function requireAuth7(client) {
1720
+ if (!client.isAuthenticated) {
1721
+ error("Not authenticated. Run `hl auth login` first.");
1722
+ process.exit(1);
1723
+ }
1724
+ }
1725
+ function parsePositiveNumber2(value) {
1726
+ const parsed = Number(value);
1727
+ if (!Number.isFinite(parsed) || parsed <= 0) {
1728
+ throw new InvalidArgumentError4("Expected a positive number");
1729
+ }
1730
+ return parsed;
1731
+ }
1732
+ function parseQuoteAction(value) {
1733
+ const normalized = value.trim().toUpperCase();
1734
+ if (normalized !== "BUY" && normalized !== "SELL") {
1735
+ throw new InvalidArgumentError4("Expected buy or sell");
1736
+ }
1737
+ return normalized;
1738
+ }
1739
+ function parseQuoteOutcome(value) {
1740
+ const normalized = value.trim().toUpperCase();
1741
+ if (normalized !== "YES" && normalized !== "NO") {
1742
+ throw new InvalidArgumentError4("Expected yes or no");
1743
+ }
1744
+ return normalized;
1745
+ }
1746
+ function parseQuoteRoute(value) {
1747
+ const normalized = value.trim().toLowerCase();
1748
+ if (normalized !== "auto" && normalized !== "aggressive" && normalized !== "passive") {
1749
+ throw new InvalidArgumentError4("Expected auto, aggressive, or passive");
1750
+ }
1751
+ return normalized;
1752
+ }
1753
+ function buildQuotePayload(instrument, opts) {
1754
+ const normalizedInstrument = instrument.trim();
1755
+ if (!normalizedInstrument) {
1756
+ throw new Error("Provide a Polymarket slug or URL.");
1757
+ }
1758
+ const hasCash = opts.cash !== void 0;
1759
+ const hasShares = opts.shares !== void 0;
1760
+ if (hasCash === hasShares) {
1761
+ throw new Error("Provide exactly one of --cash or --shares.");
1762
+ }
1763
+ if (opts.action === "SELL" && hasCash) {
1764
+ throw new Error("SELL quotes require --shares; --cash is BUY-only.");
1765
+ }
1766
+ return {
1767
+ instrument: normalizedInstrument,
1768
+ action: opts.action,
1769
+ outcome: opts.outcome,
1770
+ size: hasCash ? { type: "cash", amount_usd: opts.cash } : { type: "shares", shares: opts.shares },
1771
+ ...opts.signalId && { signal_forecast_id: opts.signalId },
1772
+ ...opts.capital !== void 0 && { portfolio_capital_usd: opts.capital },
1773
+ route: opts.route,
1774
+ persist: Boolean(opts.save)
1775
+ };
1776
+ }
1777
+ function formatNumber(value, digits = 2) {
1778
+ return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US", { maximumFractionDigits: digits }) : "n/a";
1779
+ }
1780
+ function formatUsd(value) {
1781
+ return typeof value === "number" && Number.isFinite(value) ? currency(value) : "n/a";
1782
+ }
1783
+ function formatPrice(value) {
1784
+ return typeof value === "number" && Number.isFinite(value) ? `$${value.toFixed(4)}` : "n/a";
1785
+ }
1786
+ function formatPercent(value) {
1787
+ return typeof value === "number" && Number.isFinite(value) ? percent(value) : "n/a";
1788
+ }
1789
+ function displayQuotePreview(preview, globalOpts) {
1790
+ if (globalOpts.json) {
1791
+ json(preview);
1792
+ return;
1793
+ }
1794
+ if (preview.error) {
1795
+ error(preview.error);
1796
+ return;
1797
+ }
1798
+ const request = preview.request;
1799
+ const instrument = preview.instrument;
1800
+ const market = preview.market ?? {};
1801
+ const fill = preview.fill ?? {};
1802
+ const economics = preview.economics ?? {};
1803
+ const signal = preview.signal ?? {};
1804
+ const sizing = preview.sizing_suggestion ?? {};
1805
+ heading(`Quote Preview \u2014 ${preview.status ?? "UNAVAILABLE"}`);
1806
+ table([
1807
+ ["Market", instrument.question ?? instrument.slug ?? "n/a"],
1808
+ ["Venue", preview.venue ?? "polymarket"],
1809
+ ["Action", `${request.action ?? "n/a"} ${request.outcome ?? "n/a"}`],
1810
+ ["Route", `${request.route_selected ?? "n/a"} (requested ${request.route_requested ?? "auto"})`],
1811
+ ["Observed", preview.observed_at ? new Date(preview.observed_at).toLocaleString() : "n/a"],
1812
+ ["Expires", preview.expires_at ? new Date(preview.expires_at).toLocaleString() : "n/a"]
1813
+ ]);
1814
+ process.stdout.write("\n");
1815
+ table([
1816
+ ["Best bid", formatPrice(market.best_bid)],
1817
+ ["Best ask", formatPrice(market.best_ask)],
1818
+ ["Spread", formatPrice(market.spread)],
1819
+ ["Bid depth", `${formatNumber(market.bid_depth_shares, 4)} shares`],
1820
+ ["Ask depth", `${formatNumber(market.ask_depth_shares, 4)} shares`],
1821
+ ["Requested cash", formatUsd(fill.requested_cash_usd)],
1822
+ ["Requested shares", formatNumber(fill.requested_shares, 4)],
1823
+ ["Fillable shares", formatNumber(fill.fillable_shares, 4)],
1824
+ ["Safety-capped shares", formatNumber(fill.safety_capped_shares, 4)],
1825
+ ["Fill ratio", formatPercent(fill.fill_ratio)],
1826
+ ["Average price", formatPrice(fill.average_price)],
1827
+ ["Worst price", formatPrice(fill.worst_price)],
1828
+ ["Passive limit", formatPrice(fill.passive_limit_price)],
1829
+ ["Slippage", typeof fill.slippage_bps === "number" ? `${formatNumber(fill.slippage_bps)} bps` : "n/a"]
1830
+ ], ["Quote", "Value"]);
1831
+ process.stdout.write("\n");
1832
+ table([
1833
+ ["Gross notional", formatUsd(economics.gross_notional_usd)],
1834
+ ["Venue fee", formatUsd(economics.venue_fee_usd)],
1835
+ ["Fee source", economics.fee_source ?? "unavailable"],
1836
+ [request.action === "SELL" ? "Net proceeds" : "All-in cost", formatUsd(
1837
+ request.action === "SELL" ? economics.net_proceeds_usd : economics.all_in_cost_usd
1838
+ )],
1839
+ ["Max loss", formatUsd(economics.max_loss_usd)],
1840
+ ["Max payout", formatUsd(economics.max_payout_usd)],
1841
+ ["Profit at payout", formatUsd(economics.max_profit_usd)],
1842
+ ["Foregone payout", request.action === "SELL" ? formatUsd(economics.foregone_payout_usd) : "n/a"],
1843
+ ["Break-even probability", formatPercent(economics.break_even_probability)]
1844
+ ], ["Economics", "Value"]);
1845
+ if (preview.signal) {
1846
+ process.stdout.write("\n");
1847
+ table([
1848
+ ["Forecast YES", formatPercent(signal.forecast_yes)],
1849
+ ["Forecast interval", `${formatPercent(signal.lower_bound)} \u2013 ${formatPercent(signal.upper_bound)}`],
1850
+ ["Midpoint edge", formatPercent(signal.midpoint_edge)],
1851
+ ["Conservative edge", formatPercent(signal.conservative_edge)]
1852
+ ], ["Signal", "Value"]);
1853
+ }
1854
+ if (preview.sizing_suggestion) {
1855
+ process.stdout.write("\n");
1856
+ table([
1857
+ ["Suggested cash", formatUsd(sizing.suggested_max_spend_usd)],
1858
+ ["Suggested shares", formatNumber(sizing.suggested_shares, 4)],
1859
+ ["Capital fraction", formatPercent(sizing.allocation_fraction)]
1860
+ ], ["Non-binding sizing", "Value"]);
1861
+ }
1862
+ if (preview.risks?.length) {
1863
+ process.stdout.write("\n");
1864
+ for (const risk of preview.risks) warn(risk);
1865
+ }
1866
+ if (preview.id) {
1867
+ process.stdout.write("\n" + dim(` Saved preview: ${preview.id}
1868
+ `));
1869
+ }
1870
+ process.stdout.write("\n");
1871
+ warn("Preview only \u2014 no order was signed or submitted.");
1872
+ const marketUrl = instrument.market_url;
1873
+ if (marketUrl) process.stdout.write(dim(` Market: ${marketUrl}
1874
+ `));
1875
+ }
1876
+ function registerQuoteCommand(program2) {
1877
+ program2.command("quote").description("Preview the cost, liquidity, and risk of a Polymarket trade").argument("<slug-or-url>", "Polymarket market slug or URL").requiredOption("--action <action>", "buy | sell", parseQuoteAction).requiredOption("--outcome <outcome>", "yes | no", parseQuoteOutcome).option("--cash <usd>", "Maximum cash to spend (BUY only)", parsePositiveNumber2).option("--shares <shares>", "Number of outcome shares", parsePositiveNumber2).option("--signal-id <uuid>", "Saved Signal forecast to include in edge calculations").option("--capital <usd>", "Manual portfolio capital for non-binding BUY sizing", parsePositiveNumber2).option("--route <route>", "auto | aggressive | passive", parseQuoteRoute, "auto").option("--save", "Save a freshly generated preview to quote history").action(async (instrument, opts) => {
1878
+ const globalOpts = program2.opts();
1879
+ const client = new ApiClient(globalOpts);
1880
+ requireAuth7(client);
1881
+ let payload;
1882
+ try {
1883
+ payload = buildQuotePayload(instrument, opts);
1884
+ } catch (error2) {
1885
+ error(error2 instanceof Error ? error2.message : String(error2));
1886
+ process.exit(1);
1887
+ }
1888
+ if (!globalOpts.json) {
1889
+ process.stderr.write(dim(" Refreshing public market data and order book...\n"));
1890
+ }
1891
+ const preview = await client.post("/api/quote", payload);
1892
+ displayQuotePreview(preview, globalOpts);
1893
+ });
1894
+ }
1895
+
2173
1896
  // src/index.ts
2174
1897
  var program = new Command4();
2175
- program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("2.2.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");
1898
+ program.name("hl").description("Hedge Layer CLI \u2014 prediction market intelligence from the terminal").version("3.0.1").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");
2176
1899
  registerAuthCommands(program);
2177
1900
  registerBriefCommands(program);
2178
1901
  registerProfileCommand(program);
2179
1902
  registerResearchCommands(program);
2180
1903
  registerFeedCommand(program);
2181
- registerWalletCommands(program);
2182
1904
  registerLpCommands(program);
2183
1905
  registerSignalCommands(program);
1906
+ registerQuoteCommand(program);
2184
1907
  program.hook("preAction", (_thisCommand, actionCommand) => {
2185
1908
  const opts = program.opts();
2186
1909
  if (opts.color === false) {