@palmyr/cli 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1636,6 +1636,8 @@ async function main() {
1636
1636
  { name: 'brief', description: 'Show thesis + PnL brief for a position', hint: '<CA>' },
1637
1637
  { name: 'doctor', description: 'Health check for the wallet-trading subsystem', hint: '[--wallet <ref>]' },
1638
1638
  { name: 'smoke-test', description: 'End-to-end validation of wallet trading on Solana + Base', hint: '--wallet <ref> [--chain solana|base|all]' },
1639
+ { name: 'readiness', description: 'Go/no-go autonomous-trading readiness — sign, gas, quotes, daemon, open positions', hint: '--wallet <ref>' },
1640
+ { name: 'live-test', description: 'Execute tiny real round trips on Solana + Base, verify no leftover positions', hint: '--wallet <ref> --budget Nusdc [--chain ...]' },
1639
1641
  { name: 'daemon', description: 'Auto-monitor positions for trigger-based exits', hint: 'tick | start [--auto] | stop | status' },
1640
1642
  { name: 'triggers', description: 'List pending trigger fires from the daemon', hint: '[--ca X] [--since ISO] [--clear]' },
1641
1643
  { name: 'trading-keystore', description: 'Encrypted BIP39 keystore for HD-derived trading wallets', hint: 'init | list | status | derive | export' },
@@ -2501,9 +2503,12 @@ async function main() {
2501
2503
  : (readPosition('solana', ca) ?? readPosition('base', ca));
2502
2504
  if (!p)
2503
2505
  err(`Position not found: ${ca}`, EXIT.NOT_FOUND);
2504
- const unit = p.chain === 'solana' ? 'SOL' : 'ETH';
2505
- const realized = p.chain === 'solana' ? p.pnl.realizedSol : p.pnl.realizedEth;
2506
- const unrealized = p.chain === 'solana' ? p.pnl.unrealizedSol : p.pnl.unrealizedEth;
2506
+ // Canonical asset-tagged PnL reflects what the position was
2507
+ // actually funded in (USDC-funded positions report USDC, not the
2508
+ // chain native asset).
2509
+ const unit = p.pnl.realized?.asset ?? (p.chain === 'solana' ? 'SOL' : 'ETH');
2510
+ const realized = p.pnl.realized?.amount ?? 0;
2511
+ const unrealized = p.pnl.unrealized?.amount ?? 0;
2507
2512
  if (!AGENT_MODE) {
2508
2513
  console.log();
2509
2514
  section('Position');
@@ -2548,15 +2553,11 @@ async function main() {
2548
2553
  if (p.sells.length > 0) {
2549
2554
  console.log();
2550
2555
  section(`Sells (${p.sells.length})`);
2551
- if (p.chain === 'solana') {
2552
- for (const s of p.sells) {
2553
- console.log(` ${t.muted}${s.time}${t.reset} ${s.tokensIn} ${s.solOut} (realized ${s.realizedSol >= 0 ? '+' : ''}${s.realizedSol.toFixed(6)} SOL) — ${s.reason}`);
2554
- }
2555
- }
2556
- else {
2557
- for (const s of p.sells) {
2558
- console.log(` ${t.muted}${s.time}${t.reset} ${s.tokensIn} → ${s.ethOut} (realized ${s.realizedEth >= 0 ? '+' : ''}${s.realizedEth.toFixed(6)} ETH) — ${s.reason}`);
2559
- }
2556
+ for (const s of p.sells) {
2557
+ const outDisplay = s.output?.display ?? '?';
2558
+ const r = s.realized?.amount ?? 0;
2559
+ const ru = s.realized?.asset ?? unit;
2560
+ console.log(` ${t.muted}${s.time}${t.reset} ${s.tokensIn} → ${outDisplay} (realized ${r >= 0 ? '+' : ''}${r.toFixed(6)} ${ru}) — ${s.reason}`);
2560
2561
  }
2561
2562
  }
2562
2563
  console.log();
@@ -2623,7 +2624,8 @@ async function main() {
2623
2624
  if (!AGENT_MODE) {
2624
2625
  const tag = baseResult.dryRun ? `${t.warn}[dry-run]${t.reset} ` : '';
2625
2626
  const protTag = baseResult.protectedExec ? ` ${t.accent}[protected]${t.reset}` : '';
2626
- const pnlColor = baseResult.realizedEth >= 0 ? t.success : t.error;
2627
+ const realizedAmt = baseResult.realized.amount;
2628
+ const pnlColor = realizedAmt >= 0 ? t.success : t.error;
2627
2629
  const closedTag = baseResult.positionStatus === 'closed' ? ` ${t.muted}[closed]${t.reset}` : '';
2628
2630
  console.log(`\n ${t.success}${icon.success} ${tag}Base sell executed${protTag}${closedTag}${t.reset}`);
2629
2631
  if (baseResult.approvalTxHash) {
@@ -2631,11 +2633,11 @@ async function main() {
2631
2633
  }
2632
2634
  console.log(` ${t.muted}tx:${t.reset} ${baseResult.txHash}`);
2633
2635
  console.log(` ${t.muted}sold:${t.reset} ${baseResult.tokensIn} tokens (${percent}%)`);
2634
- console.log(` ${t.muted}received:${t.reset} ${baseResult.ethOut}`);
2636
+ console.log(` ${t.muted}received:${t.reset} ${baseResult.output.display}`);
2635
2637
  {
2636
- const unit = baseResult.outputAsset ?? 'ETH';
2638
+ const unit = baseResult.realized.asset;
2637
2639
  const decimals = unit === 'USDC' ? 2 : 6;
2638
- console.log(` ${t.muted}realized:${t.reset} ${pnlColor}${baseResult.realizedEth >= 0 ? '+' : ''}${baseResult.realizedEth.toFixed(decimals)} ${unit}${t.reset}`);
2640
+ console.log(` ${t.muted}realized:${t.reset} ${pnlColor}${realizedAmt >= 0 ? '+' : ''}${realizedAmt.toFixed(decimals)} ${unit}${t.reset}`);
2639
2641
  }
2640
2642
  console.log(` ${t.muted}reason:${t.reset} ${reason}`);
2641
2643
  if (baseResult.protectedExec) {
@@ -2672,21 +2674,22 @@ async function main() {
2672
2674
  if (!AGENT_MODE) {
2673
2675
  const tag = result.dryRun ? `${t.warn}[dry-run]${t.reset} ` : '';
2674
2676
  const protTag = result.protectedExec ? ` ${t.accent}[protected]${t.reset}` : '';
2675
- const pnlColor = result.realizedSol >= 0 ? t.success : t.error;
2677
+ const realizedAmt = result.realized.amount;
2678
+ const pnlColor = realizedAmt >= 0 ? t.success : t.error;
2676
2679
  const closedTag = result.positionStatus === 'closed' ? ` ${t.muted}[closed]${t.reset}` : '';
2677
2680
  console.log(`\n ${t.success}${icon.success} ${tag}Sell executed${protTag}${closedTag}${t.reset}`);
2678
2681
  console.log(` ${t.muted}tx:${t.reset} ${result.txSignature}`);
2679
2682
  console.log(` ${t.muted}sold:${t.reset} ${result.tokensIn} tokens (${percent}%)`);
2680
- console.log(` ${t.muted}received:${t.reset} ${result.solOut}`);
2683
+ console.log(` ${t.muted}received:${t.reset} ${result.output.display}`);
2681
2684
  console.log(` ${t.muted}slippage:${t.reset} ${result.slippageBpsUsed}bps (${result.slippageSource})`);
2682
2685
  if (result.protectedExec) {
2683
2686
  console.log(` ${t.muted}tip:${t.reset} ${result.tipLamports} lamports (Jito)`);
2684
2687
  }
2685
2688
  console.log(` ${t.muted}fee:${t.reset} ${result.feeLamports} lamports`);
2686
2689
  {
2687
- const unit = result.outputAsset ?? 'SOL';
2690
+ const unit = result.realized.asset;
2688
2691
  const decimals = unit === 'USDC' ? 2 : 6;
2689
- console.log(` ${t.muted}realized:${t.reset} ${pnlColor}${result.realizedSol >= 0 ? '+' : ''}${result.realizedSol.toFixed(decimals)} ${unit}${t.reset}`);
2692
+ console.log(` ${t.muted}realized:${t.reset} ${pnlColor}${realizedAmt >= 0 ? '+' : ''}${realizedAmt.toFixed(decimals)} ${unit}${t.reset}`);
2690
2693
  }
2691
2694
  console.log(` ${t.muted}reason:${t.reset} ${reason}`);
2692
2695
  if (result.forensics && result.forensics.flag === 'suspect-mev') {
@@ -3020,14 +3023,19 @@ async function main() {
3020
3023
  const p = readPosition(inferredChain, ca, scopedAddr);
3021
3024
  if (!p)
3022
3025
  err(`Position not found: ${ca}`, EXIT.NOT_FOUND);
3026
+ // `--evaluate` degrades gracefully: a missing ANTHROPIC_API_KEY or
3027
+ // a model-API failure must NOT take down the whole brief. We surface
3028
+ // the LLM error as `llmError` so agents can branch on it, and still
3029
+ // print the deterministic brief fields.
3023
3030
  let llm;
3031
+ let llmError;
3024
3032
  if (evaluate) {
3025
3033
  const { evaluateBriefWithLLM } = await import('./wallet-brief-llm.js');
3026
3034
  try {
3027
3035
  llm = await evaluateBriefWithLLM(p);
3028
3036
  }
3029
3037
  catch (e) {
3030
- err(e.message || 'brief --evaluate failed', EXIT.GENERAL);
3038
+ llmError = e?.message ?? 'brief --evaluate failed';
3031
3039
  }
3032
3040
  }
3033
3041
  if (!AGENT_MODE) {
@@ -3042,11 +3050,11 @@ async function main() {
3042
3050
  console.log();
3043
3051
  section('Current');
3044
3052
  const pnlColor = p.pnl.unrealizedPct >= 0 ? t.success : t.error;
3045
- // Brief is now chain-aware read canonical asset-tagged pnl
3046
- // fields rather than the Solana-specific `realizedSol` / `unrealizedSol`.
3047
- const realizedAmt = p.pnl.realized?.amount ?? (p.chain === 'solana' ? p.pnl.realizedSol : p.pnl.realizedEth);
3053
+ // Canonical asset-tagged pnlnormalizePosition guarantees these
3054
+ // exist on read, including back-fill from legacy on-disk fields.
3055
+ const realizedAmt = p.pnl.realized?.amount ?? 0;
3048
3056
  const realizedAsset = p.pnl.realized?.asset ?? (p.chain === 'solana' ? 'SOL' : 'ETH');
3049
- const unrealizedAmt = p.pnl.unrealized?.amount ?? (p.chain === 'solana' ? p.pnl.unrealizedSol : p.pnl.unrealizedEth);
3057
+ const unrealizedAmt = p.pnl.unrealized?.amount ?? 0;
3050
3058
  const unrealizedAsset = p.pnl.unrealized?.asset ?? realizedAsset;
3051
3059
  kv('Realized', `${realizedAmt >= 0 ? '+' : ''}${realizedAmt.toFixed(6)} ${realizedAsset}`);
3052
3060
  console.log(` ${t.muted}Unrealized:${t.reset} ${pnlColor}${unrealizedAmt >= 0 ? '+' : ''}${unrealizedAmt.toFixed(6)} ${unrealizedAsset} (${p.pnl.unrealizedPct.toFixed(2)}%)${t.reset}`);
@@ -3060,6 +3068,10 @@ async function main() {
3060
3068
  console.log(` ${t.muted}Reasoning:${t.reset} ${llm.reasoning}`);
3061
3069
  console.log(` ${t.muted}Watch for:${t.reset} ${llm.watchFor}`);
3062
3070
  }
3071
+ else if (llmError) {
3072
+ console.log();
3073
+ console.log(` ${t.warn}LLM eval skipped: ${llmError}${t.reset}`);
3074
+ }
3063
3075
  else {
3064
3076
  console.log();
3065
3077
  console.log(` ${t.muted}Add --evaluate for an LLM thesis-health check.${t.reset}`);
@@ -3077,6 +3089,7 @@ async function main() {
3077
3089
  pnl: p.pnl,
3078
3090
  sellsCount: p.sells.length,
3079
3091
  llm,
3092
+ llmError,
3080
3093
  });
3081
3094
  }
3082
3095
  break;
@@ -3175,6 +3188,111 @@ async function main() {
3175
3188
  process.exit(EXIT.GENERAL);
3176
3189
  break;
3177
3190
  }
3191
+ case 'readiness': {
3192
+ const readyWalletRef = flags.wallet || undefined;
3193
+ if (!readyWalletRef)
3194
+ err('--wallet required. Use a vault wallet name/id or `trading:N`.', EXIT.BAD_INPUT);
3195
+ const { runWalletReadiness } = await import('./wallet-readiness.js');
3196
+ const report = await runWalletReadiness({ walletRef: readyWalletRef });
3197
+ if (AGENT_MODE) {
3198
+ print(report);
3199
+ if (!report.safeForAutonomousTrading)
3200
+ process.exit(EXIT.GENERAL);
3201
+ break;
3202
+ }
3203
+ console.log();
3204
+ section('Wallet readiness');
3205
+ kv('Wallet', report.wallet);
3206
+ if (report.solanaAddress)
3207
+ kv('Solana', report.solanaAddress);
3208
+ if (report.evmAddress)
3209
+ kv('EVM', report.evmAddress);
3210
+ kv('Verdict', report.safeForAutonomousTrading
3211
+ ? `${t.success}safe for autonomous trading${t.reset}`
3212
+ : `${t.error}NOT safe — see failing checks${t.reset}`);
3213
+ if (report.balances.solana)
3214
+ kv('SOL balance', `${report.balances.solana.sol.toFixed(6)} SOL`);
3215
+ if (report.balances.base)
3216
+ kv('ETH balance', `${report.balances.base.eth.toFixed(8)} ETH`);
3217
+ kv('Open positions', `solana=${report.openPositions.solana} base=${report.openPositions.base}`);
3218
+ kv('Daemon', report.daemon.running
3219
+ ? `${t.success}running${t.reset} (pid ${report.daemon.pid}${report.daemon.autoExecute ? ', auto-execute' : ''})`
3220
+ : `${t.warn}not running${t.reset}`);
3221
+ console.log();
3222
+ section('Checks');
3223
+ for (const c of report.checks) {
3224
+ const dot = c.status === 'pass' ? `${t.success}✓${t.reset}`
3225
+ : c.status === 'warn' ? `${t.warn}!${t.reset}`
3226
+ : c.status === 'skip' ? `${t.muted}-${t.reset}`
3227
+ : `${t.error}✗${t.reset}`;
3228
+ const tail = [c.value !== undefined ? String(c.value) : null, c.message].filter(Boolean).join(' — ');
3229
+ console.log(` ${dot} ${c.name}${tail ? `: ${tail}` : ''}`);
3230
+ }
3231
+ console.log();
3232
+ if (!report.safeForAutonomousTrading)
3233
+ process.exit(EXIT.GENERAL);
3234
+ break;
3235
+ }
3236
+ case 'live-test': {
3237
+ const liveWalletRef = flags.wallet || undefined;
3238
+ if (!liveWalletRef)
3239
+ err('--wallet required. Use a vault wallet name/id or `trading:N`.', EXIT.BAD_INPUT);
3240
+ const budgetRaw = flags.budget;
3241
+ if (!budgetRaw)
3242
+ err('--budget required, e.g. --budget 1usdc (caps total trade exposure).', EXIT.BAD_INPUT);
3243
+ const budgetMatch = (budgetRaw ?? '').trim().match(/^(\d+(?:\.\d+)?)\s*usdc$/i);
3244
+ if (!budgetMatch)
3245
+ err(`--budget must be in USDC (e.g. "0.5usdc", "1usdc"), got "${budgetRaw}".`, EXIT.BAD_INPUT);
3246
+ const budgetUsdc = Number(budgetMatch[1]);
3247
+ const liveChainFlag = (flags.chain || 'all').toLowerCase();
3248
+ if (liveChainFlag !== 'solana' && liveChainFlag !== 'base' && liveChainFlag !== 'all') {
3249
+ err(`--chain must be solana, base, or all (got ${liveChainFlag})`, EXIT.BAD_INPUT);
3250
+ }
3251
+ const { runWalletLiveTest } = await import('./wallet-live-test.js');
3252
+ let report;
3253
+ try {
3254
+ report = await runWalletLiveTest({
3255
+ walletRef: liveWalletRef,
3256
+ budgetUsdc,
3257
+ chain: liveChainFlag,
3258
+ });
3259
+ }
3260
+ catch (e) {
3261
+ err(e.message || 'live-test failed', EXIT.GENERAL);
3262
+ }
3263
+ if (AGENT_MODE) {
3264
+ print(report);
3265
+ if (!report.safeForAutonomousTrading)
3266
+ process.exit(EXIT.GENERAL);
3267
+ break;
3268
+ }
3269
+ console.log();
3270
+ section('Wallet live-test');
3271
+ kv('Wallet', report.wallet);
3272
+ kv('Budget', `${report.budgetUsdc.toFixed(6)} USDC (per leg ${report.perLegUsdc.toFixed(6)} USDC)`);
3273
+ kv('Verdict', report.safeForAutonomousTrading
3274
+ ? `${t.success}safe for autonomous trading${t.reset}`
3275
+ : `${t.error}NOT safe — see failing legs${t.reset}`);
3276
+ kv('Total realized', `${report.totalRealizedUsdc >= 0 ? '+' : ''}${report.totalRealizedUsdc.toFixed(6)} USDC`);
3277
+ kv('Open positions after', String(report.openPositionsAfter));
3278
+ console.log();
3279
+ section('Legs');
3280
+ for (const leg of report.legs) {
3281
+ const dot = leg.status === 'pass' ? `${t.success}✓${t.reset}`
3282
+ : leg.status === 'skip' ? `${t.muted}-${t.reset}`
3283
+ : `${t.error}✗${t.reset}`;
3284
+ const realizedStr = leg.realized
3285
+ ? `; realized ${leg.realized.amount >= 0 ? '+' : ''}${leg.realized.amount.toFixed(6)} ${leg.realized.asset}`
3286
+ : '';
3287
+ const txStr = leg.txHash ? ` (${leg.txHash.slice(0, 10)}…)` : '';
3288
+ const detail = [leg.message, leg.durationMs !== undefined ? `${leg.durationMs}ms` : null].filter(Boolean).join(' — ');
3289
+ console.log(` ${dot} ${leg.chain}/${leg.name}${txStr}${realizedStr}${detail ? `: ${detail}` : ''}`);
3290
+ }
3291
+ console.log();
3292
+ if (!report.safeForAutonomousTrading)
3293
+ process.exit(EXIT.GENERAL);
3294
+ break;
3295
+ }
3178
3296
  case 'daemon': {
3179
3297
  const sub = positional[0];
3180
3298
  if (!sub)