nothumanallowed 15.1.68 → 15.1.70
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "15.1.
|
|
3
|
+
"version": "15.1.70",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '15.1.
|
|
8
|
+
export const VERSION = '15.1.70';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -97,7 +97,7 @@ export const DESTRUCTIVE_ACTIONS = new Set([
|
|
|
97
97
|
// Cron / scheduling
|
|
98
98
|
'cron_create', 'cron_delete',
|
|
99
99
|
// Portfolio (local file)
|
|
100
|
-
'portfolio_add', 'portfolio_remove',
|
|
100
|
+
'portfolio_add', 'portfolio_remove', 'portfolio_tx_add',
|
|
101
101
|
]);
|
|
102
102
|
|
|
103
103
|
// ── Tool Definitions (for system prompt) ─────────────────────────────────────
|
|
@@ -720,6 +720,60 @@ TOOLS:
|
|
|
720
720
|
101. italian_market(what?: string)
|
|
721
721
|
Italian market snapshot. what: "all" | "mib" (FTSE MIB index) | "constituents" (top 15 blue chips with live prices) | "spread" (BTP-Bund 10y).
|
|
722
722
|
|
|
723
|
+
102. portfolio_correlation(period?: string)
|
|
724
|
+
Pearson correlation matrix between all holdings + SPY benchmark over a period (default 1y).
|
|
725
|
+
Surfaces concentration risk (>0.7 correlation = redundant) and diversification opportunities (<0.3 = good).
|
|
726
|
+
|
|
727
|
+
103. portfolio_sector_breakdown()
|
|
728
|
+
Breakdown of portfolio exposure by sector, country, and currency. Computed from live prices × position size.
|
|
729
|
+
Use to spot over-concentration (e.g. 60% in tech) or unhedged currency exposure.
|
|
730
|
+
|
|
731
|
+
104. portfolio_var(period?: string, confidence?: number)
|
|
732
|
+
Historical-simulation Value at Risk and Expected Shortfall (CVaR). Default: 1y, 95% confidence.
|
|
733
|
+
Output: 1-day VaR % and $, 10-day VaR (sqrt-time scaled), Expected Shortfall (avg tail loss).
|
|
734
|
+
For stress testing pass confidence=0.99.
|
|
735
|
+
|
|
736
|
+
105. portfolio_rebalance(targets?: object)
|
|
737
|
+
Compare current weights vs target weights and suggest the exact share trades to rebalance.
|
|
738
|
+
targets: optional {AAPL: 0.20, MSFT: 0.15, ...} (sum to 1.0). If not provided, falls back to ~/.nha/portfolio.json
|
|
739
|
+
"targets" field, otherwise assumes equal-weight. Drift threshold for action: 1%.
|
|
740
|
+
|
|
741
|
+
106. insider_trading(ticker: string, limit?: number)
|
|
742
|
+
SEC Form 4 (insider/officer transactions) for a US-listed company. Returns last N filings with direct URLs.
|
|
743
|
+
Open each URL to see whether the insider BOUGHT or SOLD shares — strong signal when clustered.
|
|
744
|
+
|
|
745
|
+
107. option_strategy_builder(ticker: string, direction?: string, maxRisk?: number, daysToExpiry?: number)
|
|
746
|
+
Suggests concrete option strategies with strikes, expiry, net debit/credit, max profit/loss, breakeven, ROI.
|
|
747
|
+
direction: "bullish" | "bearish" | "neutral". Default: neutral. maxRisk: $ budget (default 1000). daysToExpiry: default 30.
|
|
748
|
+
Output adapts to ATM IV percentile vs 30d realized vol (cheap < 30th = buy premium; rich > 70th = sell premium):
|
|
749
|
+
- bullish+cheap → Bull Call Debit Spread - bullish+rich → Bull Put Credit Spread
|
|
750
|
+
- bearish+cheap → Bear Put Debit Spread - bearish+rich → Bear Call Credit Spread
|
|
751
|
+
- neutral+rich → Iron Condor - neutral+cheap → Long Straddle
|
|
752
|
+
Each suggestion includes exact contracts × ticker × expiry × strike × side, with sizing scaled to maxRisk.
|
|
753
|
+
|
|
754
|
+
108. crypto_onchain_metrics(coin: string)
|
|
755
|
+
Multi-source on-chain & derivatives view of a crypto asset. coin = CoinGecko ID (bitcoin, ethereum, solana, …).
|
|
756
|
+
Combines:
|
|
757
|
+
- CoinGecko market data: price, mcap, supply, ATH distance, social signal
|
|
758
|
+
- DeFi Llama TVL (for L1s): 30-day TVL trend
|
|
759
|
+
- Binance perpetual futures: 8h funding rate (last + avg of 8), open interest, positioning signal 🟢/🔴/🟡
|
|
760
|
+
- Alternative.me Fear & Greed Index (0-100)
|
|
761
|
+
- CoinGecko global: BTC/ETH/stablecoin market share
|
|
762
|
+
All-free APIs. No API key required.
|
|
763
|
+
|
|
764
|
+
109. portfolio_tax_lots(method?: string)
|
|
765
|
+
Tax-lot accounting on the transaction history. method: "FIFO" | "LIFO" | "HIFO". Default: FIFO.
|
|
766
|
+
Computes realized short-term (taxed at income rate) vs long-term (15%/20% US) gains per ticker.
|
|
767
|
+
Lists open lots with cost basis, age (long-term threshold 365 days), unrealized P/L per lot.
|
|
768
|
+
Surfaces:
|
|
769
|
+
- ⚠ Wash-sale warnings: buys within 30 days of a loss sale (IRS §1091)
|
|
770
|
+
- 💡 Tax-loss harvest candidates: open lots with unrealized loss > $100, flags if hold < 31d (wash-sale risk)
|
|
771
|
+
Requires transactions recorded via portfolio_tx_add (legacy portfolio_add still works for current state).
|
|
772
|
+
|
|
773
|
+
110. portfolio_tx_add(ticker: string, type: "buy"|"sell", qty: number, price: number, date?: string)
|
|
774
|
+
Record a dated transaction. Feeds portfolio_tax_lots. Also updates current positions for portfolio_summary parity.
|
|
775
|
+
date format: "YYYY-MM-DD" (defaults to today).
|
|
776
|
+
|
|
723
777
|
--- CODE EXECUTION ---
|
|
724
778
|
|
|
725
779
|
81. execute_code(language: "python"|"javascript"|"typescript", code: string, files?: [{path: string, content: string}], packages?: string[], stdin?: string, timeout?: number)
|
|
@@ -4460,6 +4514,730 @@ print(f" Max drawdown: {max_dd*100:.2f}%")
|
|
|
4460
4514
|
return executeTool('execute_code', { language: 'python', code, timeout: 60 }, config);
|
|
4461
4515
|
}
|
|
4462
4516
|
|
|
4517
|
+
case 'portfolio_correlation': {
|
|
4518
|
+
// Pearson correlation matrix of all portfolio holdings + SPY benchmark.
|
|
4519
|
+
const period = params.period || '1y';
|
|
4520
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4521
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4522
|
+
if (!fs.default.existsSync(file)) return 'Portfolio is empty.';
|
|
4523
|
+
const pf = JSON.parse(fs.default.readFileSync(file, 'utf-8'));
|
|
4524
|
+
const positions = pf.positions || [];
|
|
4525
|
+
if (positions.length < 2) return 'Need at least 2 positions for a correlation matrix.';
|
|
4526
|
+
const symbols = [...positions.map(p => p.ticker), 'SPY'];
|
|
4527
|
+
const series = {};
|
|
4528
|
+
for (const s of symbols) {
|
|
4529
|
+
try {
|
|
4530
|
+
const r = await fetch(`https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(s)}?range=${period}&interval=1d`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4531
|
+
if (!r.ok) continue;
|
|
4532
|
+
const d = await r.json();
|
|
4533
|
+
series[s] = d?.chart?.result?.[0]?.indicators?.quote?.[0]?.close?.filter(Number.isFinite) || [];
|
|
4534
|
+
} catch {}
|
|
4535
|
+
}
|
|
4536
|
+
const valid = Object.entries(series).filter(([_, a]) => a.length > 30);
|
|
4537
|
+
if (valid.length < 2) return 'portfolio_correlation: not enough historical data.';
|
|
4538
|
+
const N = Math.min(...valid.map(([_, a]) => a.length));
|
|
4539
|
+
const rets = Object.fromEntries(valid.map(([s, a]) => {
|
|
4540
|
+
const arr = a.slice(-N);
|
|
4541
|
+
return [s, arr.slice(1).map((v, i) => (v - arr[i]) / arr[i])];
|
|
4542
|
+
}));
|
|
4543
|
+
const mean = (a) => a.reduce((x, y) => x + y, 0) / a.length;
|
|
4544
|
+
const corr = (a, b) => {
|
|
4545
|
+
const ma = mean(a), mb = mean(b);
|
|
4546
|
+
let num = 0, dA = 0, dB = 0;
|
|
4547
|
+
for (let i = 0; i < a.length; i++) { const xa = a[i] - ma, xb = b[i] - mb; num += xa * xb; dA += xa * xa; dB += xb * xb; }
|
|
4548
|
+
return num / Math.sqrt(dA * dB || 1);
|
|
4549
|
+
};
|
|
4550
|
+
const syms = valid.map(([s]) => s);
|
|
4551
|
+
const matrix = syms.map(a => syms.map(b => corr(rets[a], rets[b])));
|
|
4552
|
+
const lines = [`Correlation matrix (${period}, ${N} days)`];
|
|
4553
|
+
lines.push(' ' + syms.map(s => s.padStart(8)).join(''));
|
|
4554
|
+
matrix.forEach((row, i) => {
|
|
4555
|
+
lines.push(syms[i].padEnd(9) + row.map(v => v.toFixed(2).padStart(8)).join(''));
|
|
4556
|
+
});
|
|
4557
|
+
// Find pairs with highest correlation (excluding self/SPY)
|
|
4558
|
+
const pairs = [];
|
|
4559
|
+
for (let i = 0; i < syms.length; i++) for (let j = i + 1; j < syms.length; j++) {
|
|
4560
|
+
if (syms[i] === 'SPY' || syms[j] === 'SPY') continue;
|
|
4561
|
+
pairs.push({ a: syms[i], b: syms[j], r: matrix[i][j] });
|
|
4562
|
+
}
|
|
4563
|
+
pairs.sort((a, b) => b.r - a.r);
|
|
4564
|
+
if (pairs.length) {
|
|
4565
|
+
lines.push('\nMost correlated pairs (concentration risk):');
|
|
4566
|
+
pairs.slice(0, 3).forEach(p => lines.push(` ${p.a} ↔ ${p.b}: ${p.r.toFixed(2)}${p.r > 0.7 ? ' ⚠ high' : ''}`));
|
|
4567
|
+
lines.push('\nLeast correlated (diversification value):');
|
|
4568
|
+
pairs.slice(-3).reverse().forEach(p => lines.push(` ${p.a} ↔ ${p.b}: ${p.r.toFixed(2)}`));
|
|
4569
|
+
}
|
|
4570
|
+
return lines.join('\n');
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4573
|
+
case 'portfolio_sector_breakdown': {
|
|
4574
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4575
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4576
|
+
if (!fs.default.existsSync(file)) return 'Portfolio is empty.';
|
|
4577
|
+
const pf = JSON.parse(fs.default.readFileSync(file, 'utf-8'));
|
|
4578
|
+
const positions = pf.positions || [];
|
|
4579
|
+
if (positions.length === 0) return 'Portfolio is empty.';
|
|
4580
|
+
const symbols = positions.map(p => p.ticker);
|
|
4581
|
+
// Batch-fetch assetProfile + live price
|
|
4582
|
+
const summaries = {};
|
|
4583
|
+
for (const s of symbols) {
|
|
4584
|
+
try {
|
|
4585
|
+
const r = await fetch(`https://query2.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(s)}?modules=assetProfile,summaryDetail,price`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4586
|
+
if (r.ok) {
|
|
4587
|
+
const d = await r.json();
|
|
4588
|
+
summaries[s] = d?.quoteSummary?.result?.[0] || {};
|
|
4589
|
+
}
|
|
4590
|
+
} catch {}
|
|
4591
|
+
}
|
|
4592
|
+
const rows = positions.map(p => {
|
|
4593
|
+
const sx = summaries[p.ticker] || {};
|
|
4594
|
+
const sector = sx.assetProfile?.sector || sx.assetProfile?.industry || 'Unknown';
|
|
4595
|
+
const industry = sx.assetProfile?.industry || '';
|
|
4596
|
+
const country = sx.assetProfile?.country || 'Unknown';
|
|
4597
|
+
const currency = sx.price?.currency || sx.summaryDetail?.currency || 'USD';
|
|
4598
|
+
const price = sx.price?.regularMarketPrice?.raw || 0;
|
|
4599
|
+
const value = price * p.qty;
|
|
4600
|
+
return { ticker: p.ticker, sector, industry, country, currency, value };
|
|
4601
|
+
});
|
|
4602
|
+
const total = rows.reduce((a, r) => a + r.value, 0) || 1;
|
|
4603
|
+
const bySector = {};
|
|
4604
|
+
const byCountry = {};
|
|
4605
|
+
const byCurrency = {};
|
|
4606
|
+
rows.forEach(r => {
|
|
4607
|
+
bySector[r.sector] = (bySector[r.sector] || 0) + r.value;
|
|
4608
|
+
byCountry[r.country] = (byCountry[r.country] || 0) + r.value;
|
|
4609
|
+
byCurrency[r.currency] = (byCurrency[r.currency] || 0) + r.value;
|
|
4610
|
+
});
|
|
4611
|
+
const fmt = (obj) => Object.entries(obj).sort((a, b) => b[1] - a[1])
|
|
4612
|
+
.map(([k, v]) => ` ${k.padEnd(28)} ${((v / total) * 100).toFixed(1).padStart(6)}% $${v.toFixed(0).padStart(10)}`).join('\n');
|
|
4613
|
+
return [
|
|
4614
|
+
`Portfolio breakdown — total value $${total.toFixed(0)}`,
|
|
4615
|
+
'',
|
|
4616
|
+
`By sector:\n${fmt(bySector)}`,
|
|
4617
|
+
'',
|
|
4618
|
+
`By country:\n${fmt(byCountry)}`,
|
|
4619
|
+
'',
|
|
4620
|
+
`By currency:\n${fmt(byCurrency)}`,
|
|
4621
|
+
].join('\n');
|
|
4622
|
+
}
|
|
4623
|
+
|
|
4624
|
+
case 'portfolio_var': {
|
|
4625
|
+
// Historical-simulation Value at Risk and Expected Shortfall.
|
|
4626
|
+
const period = params.period || '1y';
|
|
4627
|
+
const conf = parseFloat(params.confidence || '0.95'); // 95% default; pass 0.99 for stress
|
|
4628
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4629
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4630
|
+
if (!fs.default.existsSync(file)) return 'Portfolio is empty.';
|
|
4631
|
+
const pf = JSON.parse(fs.default.readFileSync(file, 'utf-8'));
|
|
4632
|
+
const positions = pf.positions || [];
|
|
4633
|
+
if (positions.length === 0) return 'Portfolio is empty.';
|
|
4634
|
+
const symbols = positions.map(p => p.ticker);
|
|
4635
|
+
const series = {};
|
|
4636
|
+
for (const s of symbols) {
|
|
4637
|
+
try {
|
|
4638
|
+
const r = await fetch(`https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(s)}?range=${period}&interval=1d`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4639
|
+
if (!r.ok) continue;
|
|
4640
|
+
const d = await r.json();
|
|
4641
|
+
series[s] = d?.chart?.result?.[0]?.indicators?.quote?.[0]?.close?.filter(Number.isFinite) || [];
|
|
4642
|
+
} catch {}
|
|
4643
|
+
}
|
|
4644
|
+
const valid = positions.filter(p => (series[p.ticker] || []).length > 30);
|
|
4645
|
+
if (valid.length === 0) return 'portfolio_var: not enough data.';
|
|
4646
|
+
const N = Math.min(...valid.map(p => series[p.ticker].length));
|
|
4647
|
+
const lastPrice = (p) => series[p.ticker][series[p.ticker].length - 1];
|
|
4648
|
+
const values = valid.map(p => p.qty * lastPrice(p));
|
|
4649
|
+
const totalValue = values.reduce((a, b) => a + b, 0);
|
|
4650
|
+
const weights = values.map(v => v / totalValue);
|
|
4651
|
+
const dailyRet = (a) => a.slice(1).map((v, i) => (v - a[i]) / a[i]);
|
|
4652
|
+
const tickerRets = valid.map(p => dailyRet(series[p.ticker]).slice(-N + 1));
|
|
4653
|
+
const minLen = Math.min(...tickerRets.map(r => r.length));
|
|
4654
|
+
const portfRets = [];
|
|
4655
|
+
for (let i = 0; i < minLen; i++) {
|
|
4656
|
+
let r = 0;
|
|
4657
|
+
for (let j = 0; j < tickerRets.length; j++) r += (tickerRets[j][i] || 0) * weights[j];
|
|
4658
|
+
portfRets.push(r);
|
|
4659
|
+
}
|
|
4660
|
+
// Sort returns ascending; VaR is the loss at the (1-conf) quantile.
|
|
4661
|
+
const sorted = [...portfRets].sort((a, b) => a - b);
|
|
4662
|
+
const idx = Math.floor((1 - conf) * sorted.length);
|
|
4663
|
+
const varRet = sorted[idx];
|
|
4664
|
+
const tailLosses = sorted.slice(0, idx + 1);
|
|
4665
|
+
const esRet = tailLosses.reduce((a, b) => a + b, 0) / (tailLosses.length || 1);
|
|
4666
|
+
const portValue = positions.reduce((a, p) => a + (lastPrice(p) || 0) * p.qty, 0);
|
|
4667
|
+
return [
|
|
4668
|
+
`Portfolio VaR (Historical Simulation, ${period}, ${minLen} days)`,
|
|
4669
|
+
`Confidence: ${(conf * 100).toFixed(0)}%`,
|
|
4670
|
+
`Portfolio value: $${portValue.toFixed(0)}`,
|
|
4671
|
+
`Daily VaR: ${(varRet * 100).toFixed(2)}% = $${(varRet * portValue).toFixed(0)} loss`,
|
|
4672
|
+
`Expected Shortfall (CVaR): ${(esRet * 100).toFixed(2)}% = $${(esRet * portValue).toFixed(0)} avg tail loss`,
|
|
4673
|
+
`10-day VaR (sqrt-time scaled): ${(varRet * Math.sqrt(10) * 100).toFixed(2)}% = $${(varRet * Math.sqrt(10) * portValue).toFixed(0)}`,
|
|
4674
|
+
'',
|
|
4675
|
+
`Interpretation: with ${(conf * 100).toFixed(0)}% confidence, daily losses won't exceed the VaR figure.`,
|
|
4676
|
+
`When they do (the worst ${((1 - conf) * 100).toFixed(0)}% of days), the average loss is the ES/CVaR.`,
|
|
4677
|
+
].join('\n');
|
|
4678
|
+
}
|
|
4679
|
+
|
|
4680
|
+
case 'portfolio_rebalance': {
|
|
4681
|
+
// Compare current weights vs target weights. Targets can be passed as
|
|
4682
|
+
// `params.targets = {AAPL: 0.20, MSFT: 0.15, ...}` or read from
|
|
4683
|
+
// `~/.nha/portfolio.json` field `targets`. Without targets we assume
|
|
4684
|
+
// equal-weight.
|
|
4685
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4686
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4687
|
+
if (!fs.default.existsSync(file)) return 'Portfolio is empty.';
|
|
4688
|
+
const pf = JSON.parse(fs.default.readFileSync(file, 'utf-8'));
|
|
4689
|
+
const positions = pf.positions || [];
|
|
4690
|
+
if (positions.length === 0) return 'Portfolio is empty.';
|
|
4691
|
+
let targets = params.targets || pf.targets || null;
|
|
4692
|
+
if (!targets) {
|
|
4693
|
+
const eq = 1 / positions.length;
|
|
4694
|
+
targets = Object.fromEntries(positions.map(p => [p.ticker, eq]));
|
|
4695
|
+
}
|
|
4696
|
+
const symbols = positions.map(p => p.ticker);
|
|
4697
|
+
const r = await fetch(`https://query1.finance.yahoo.com/v7/finance/quote?symbols=${symbols.join(',')}`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4698
|
+
if (!r.ok) return `portfolio_rebalance: HTTP ${r.status}`;
|
|
4699
|
+
const d = await r.json();
|
|
4700
|
+
const priceMap = Object.fromEntries((d?.quoteResponse?.result || []).map(q => [q.symbol, q.regularMarketPrice]));
|
|
4701
|
+
const rows = positions.map(p => {
|
|
4702
|
+
const px = priceMap[p.ticker] || 0;
|
|
4703
|
+
const value = px * p.qty;
|
|
4704
|
+
return { ticker: p.ticker, qty: p.qty, price: px, value };
|
|
4705
|
+
});
|
|
4706
|
+
const total = rows.reduce((a, r) => a + r.value, 0) || 1;
|
|
4707
|
+
const lines = [`Portfolio rebalance — total $${total.toFixed(0)}`];
|
|
4708
|
+
lines.push(`${'Ticker'.padEnd(10)}${'Current %'.padStart(12)}${'Target %'.padStart(12)}${'Drift'.padStart(10)}${'Action'.padStart(30)}`);
|
|
4709
|
+
let totalTrade = 0;
|
|
4710
|
+
rows.forEach(p => {
|
|
4711
|
+
const current = (p.value / total) * 100;
|
|
4712
|
+
const target = (targets[p.ticker] || 0) * 100;
|
|
4713
|
+
const drift = current - target;
|
|
4714
|
+
const tradeUsd = (target - current) / 100 * total;
|
|
4715
|
+
const shares = p.price > 0 ? tradeUsd / p.price : 0;
|
|
4716
|
+
const action = Math.abs(drift) < 1 ? '✓ ok'
|
|
4717
|
+
: drift > 0 ? `SELL ${Math.abs(shares).toFixed(2)} @ $${p.price.toFixed(2)}`
|
|
4718
|
+
: `BUY ${Math.abs(shares).toFixed(2)} @ $${p.price.toFixed(2)}`;
|
|
4719
|
+
totalTrade += Math.abs(tradeUsd);
|
|
4720
|
+
lines.push(`${p.ticker.padEnd(10)}${current.toFixed(2).padStart(11)}%${target.toFixed(2).padStart(11)}%${(drift >= 0 ? '+' : '') + drift.toFixed(2).padStart(8)}%${action.padStart(30)}`);
|
|
4721
|
+
});
|
|
4722
|
+
lines.push(`\nTotal trade volume to rebalance: $${totalTrade.toFixed(0)}`);
|
|
4723
|
+
lines.push(`Drift threshold ≥ 1% → trades suggested above. Below threshold → leave as is.`);
|
|
4724
|
+
return lines.join('\n');
|
|
4725
|
+
}
|
|
4726
|
+
|
|
4727
|
+
case 'option_strategy_builder': {
|
|
4728
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
4729
|
+
const direction = String(params.direction || 'neutral').toLowerCase();
|
|
4730
|
+
const maxRisk = parseFloat(params.maxRisk || params.max_risk || '1000');
|
|
4731
|
+
const targetDays = parseInt(params.daysToExpiry || params.dte || '30', 10);
|
|
4732
|
+
if (!ticker) return 'option_strategy_builder: ticker required.';
|
|
4733
|
+
try {
|
|
4734
|
+
// 1. Fetch options chain (Yahoo Finance, free)
|
|
4735
|
+
const chainRes = await fetch(`https://query2.finance.yahoo.com/v7/finance/options/${encodeURIComponent(ticker)}`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4736
|
+
if (!chainRes.ok) return `option_strategy_builder: HTTP ${chainRes.status}`;
|
|
4737
|
+
const cd = await chainRes.json();
|
|
4738
|
+
const result = cd?.optionChain?.result?.[0];
|
|
4739
|
+
if (!result) return `option_strategy_builder: no options data for ${ticker}.`;
|
|
4740
|
+
const spot = result.quote?.regularMarketPrice;
|
|
4741
|
+
// 2. Pick the expiration closest to targetDays
|
|
4742
|
+
const today = Date.now() / 1000;
|
|
4743
|
+
const exps = result.expirationDates || [];
|
|
4744
|
+
const expPicked = exps.reduce((best, e) => {
|
|
4745
|
+
const dte = (e - today) / 86400;
|
|
4746
|
+
if (dte < 5) return best;
|
|
4747
|
+
const bestDte = best ? (best - today) / 86400 : Infinity;
|
|
4748
|
+
return Math.abs(dte - targetDays) < Math.abs(bestDte - targetDays) ? e : best;
|
|
4749
|
+
}, 0);
|
|
4750
|
+
if (!expPicked) return `option_strategy_builder: no suitable expiry near ${targetDays} days.`;
|
|
4751
|
+
const dtePicked = Math.round((expPicked - today) / 86400);
|
|
4752
|
+
// Fetch the picked chain
|
|
4753
|
+
const pickedRes = await fetch(`https://query2.finance.yahoo.com/v7/finance/options/${encodeURIComponent(ticker)}?date=${expPicked}`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4754
|
+
const pickedData = await pickedRes.json();
|
|
4755
|
+
const opts = pickedData?.optionChain?.result?.[0]?.options?.[0] || {};
|
|
4756
|
+
const calls = opts.calls || [];
|
|
4757
|
+
const puts = opts.puts || [];
|
|
4758
|
+
if (calls.length === 0 || puts.length === 0) return `option_strategy_builder: empty chain for chosen expiry.`;
|
|
4759
|
+
// 3. ATM IV (average call+put closest to spot)
|
|
4760
|
+
const closest = (arr) => arr.reduce((a, b) => Math.abs(a.strike - spot) < Math.abs(b.strike - spot) ? a : b);
|
|
4761
|
+
const atmCall = closest(calls);
|
|
4762
|
+
const atmPut = closest(puts);
|
|
4763
|
+
const atmIV = ((atmCall.impliedVolatility || 0) + (atmPut.impliedVolatility || 0)) / 2;
|
|
4764
|
+
// 4. IV rank from market_chart historical realized vol as proxy (no API for historical IV free)
|
|
4765
|
+
const chartRes = await fetch(`https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(ticker)}?range=1y&interval=1d`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4766
|
+
let ivPercentile = null;
|
|
4767
|
+
try {
|
|
4768
|
+
const ch = await chartRes.json();
|
|
4769
|
+
const closes = ch?.chart?.result?.[0]?.indicators?.quote?.[0]?.close?.filter(Number.isFinite) || [];
|
|
4770
|
+
// Rolling 30-day realized vol annualized
|
|
4771
|
+
const rollingVols = [];
|
|
4772
|
+
for (let i = 30; i < closes.length; i++) {
|
|
4773
|
+
const window = closes.slice(i - 30, i);
|
|
4774
|
+
const rets = window.slice(1).map((v, j) => Math.log(v / window[j]));
|
|
4775
|
+
const mean = rets.reduce((a, b) => a + b, 0) / rets.length;
|
|
4776
|
+
const variance = rets.reduce((a, r) => a + (r - mean) ** 2, 0) / (rets.length - 1);
|
|
4777
|
+
rollingVols.push(Math.sqrt(variance * 252));
|
|
4778
|
+
}
|
|
4779
|
+
if (rollingVols.length > 20) {
|
|
4780
|
+
const sorted = [...rollingVols].sort((a, b) => a - b);
|
|
4781
|
+
const rank = sorted.findIndex(v => v >= atmIV);
|
|
4782
|
+
ivPercentile = rank < 0 ? 100 : (rank / sorted.length) * 100;
|
|
4783
|
+
}
|
|
4784
|
+
} catch {}
|
|
4785
|
+
const ivCheap = ivPercentile != null && ivPercentile < 30;
|
|
4786
|
+
const ivRich = ivPercentile != null && ivPercentile > 70;
|
|
4787
|
+
// 5. Build strategy recommendations
|
|
4788
|
+
const expStr = new Date(expPicked * 1000).toISOString().slice(0, 10);
|
|
4789
|
+
const findStrike = (arr, targetDelta) => {
|
|
4790
|
+
// Find option whose abs(delta) is closest to target. Yahoo doesn't always provide delta;
|
|
4791
|
+
// approximate by inMoneyness: for calls, delta≈0.5 at ATM; +0.05 per 1% ITM.
|
|
4792
|
+
let best = arr[0], bestDist = Infinity;
|
|
4793
|
+
for (const o of arr) {
|
|
4794
|
+
const moneyness = (o.strike - spot) / spot;
|
|
4795
|
+
const approxDelta = arr === calls ? Math.max(0.05, Math.min(0.95, 0.5 - moneyness * 5)) : Math.max(-0.95, Math.min(-0.05, -0.5 - moneyness * 5));
|
|
4796
|
+
const dist = Math.abs(Math.abs(approxDelta) - targetDelta);
|
|
4797
|
+
if (dist < bestDist) { bestDist = dist; best = o; }
|
|
4798
|
+
}
|
|
4799
|
+
return best;
|
|
4800
|
+
};
|
|
4801
|
+
const mid = (o) => ((o.bid || 0) + (o.ask || 0)) / 2 || o.lastPrice || 0;
|
|
4802
|
+
const strategies = [];
|
|
4803
|
+
|
|
4804
|
+
if (direction === 'bullish' || direction === 'long') {
|
|
4805
|
+
if (ivCheap || ivPercentile == null) {
|
|
4806
|
+
// Bull Call Debit Spread (buy ATM, sell OTM)
|
|
4807
|
+
const buy = atmCall;
|
|
4808
|
+
const sell = calls.find(c => c.strike > spot * 1.05) || calls[calls.length - 1];
|
|
4809
|
+
const debit = (mid(buy) - mid(sell)) * 100;
|
|
4810
|
+
const width = (sell.strike - buy.strike) * 100;
|
|
4811
|
+
const maxProfit = width - debit;
|
|
4812
|
+
const maxLoss = debit;
|
|
4813
|
+
const be = buy.strike + (debit / 100);
|
|
4814
|
+
const contracts = Math.max(1, Math.floor(maxRisk / debit));
|
|
4815
|
+
strategies.push({
|
|
4816
|
+
name: 'Bull Call Debit Spread',
|
|
4817
|
+
rationale: 'Bullish, IV cheap → buy premium, defined risk',
|
|
4818
|
+
legs: [`BUY ${contracts}x ${ticker} ${expStr} ${buy.strike} CALL @ ~$${mid(buy).toFixed(2)}`,
|
|
4819
|
+
`SELL ${contracts}x ${ticker} ${expStr} ${sell.strike} CALL @ ~$${mid(sell).toFixed(2)}`],
|
|
4820
|
+
netDebit: debit * contracts,
|
|
4821
|
+
maxProfit: maxProfit * contracts,
|
|
4822
|
+
maxLoss: maxLoss * contracts,
|
|
4823
|
+
breakeven: be.toFixed(2),
|
|
4824
|
+
roi: ((maxProfit / Math.max(1, maxLoss)) * 100).toFixed(0) + '%',
|
|
4825
|
+
});
|
|
4826
|
+
}
|
|
4827
|
+
if (ivRich) {
|
|
4828
|
+
// Bull Put Credit Spread (sell OTM put, buy further OTM put)
|
|
4829
|
+
const sell = puts.find(p => p.strike < spot * 0.97 && p.strike > spot * 0.93) || puts[Math.floor(puts.length / 2)];
|
|
4830
|
+
const buy = puts.find(p => p.strike < sell.strike - spot * 0.03) || puts[0];
|
|
4831
|
+
const credit = (mid(sell) - mid(buy)) * 100;
|
|
4832
|
+
const width = (sell.strike - buy.strike) * 100;
|
|
4833
|
+
const maxLoss = width - credit;
|
|
4834
|
+
const be = sell.strike - (credit / 100);
|
|
4835
|
+
const contracts = Math.max(1, Math.floor(maxRisk / maxLoss));
|
|
4836
|
+
strategies.push({
|
|
4837
|
+
name: 'Bull Put Credit Spread',
|
|
4838
|
+
rationale: 'Bullish, IV rich → sell premium, defined risk',
|
|
4839
|
+
legs: [`SELL ${contracts}x ${ticker} ${expStr} ${sell.strike} PUT @ ~$${mid(sell).toFixed(2)}`,
|
|
4840
|
+
`BUY ${contracts}x ${ticker} ${expStr} ${buy.strike} PUT @ ~$${mid(buy).toFixed(2)}`],
|
|
4841
|
+
netCredit: credit * contracts,
|
|
4842
|
+
maxProfit: credit * contracts,
|
|
4843
|
+
maxLoss: maxLoss * contracts,
|
|
4844
|
+
breakeven: be.toFixed(2),
|
|
4845
|
+
roi: ((credit / Math.max(1, maxLoss)) * 100).toFixed(0) + '%',
|
|
4846
|
+
});
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
|
|
4850
|
+
if (direction === 'bearish' || direction === 'short') {
|
|
4851
|
+
if (ivCheap || ivPercentile == null) {
|
|
4852
|
+
const buy = atmPut;
|
|
4853
|
+
const sell = puts.find(p => p.strike < spot * 0.95) || puts[0];
|
|
4854
|
+
const debit = (mid(buy) - mid(sell)) * 100;
|
|
4855
|
+
const width = (buy.strike - sell.strike) * 100;
|
|
4856
|
+
const maxProfit = width - debit;
|
|
4857
|
+
const be = buy.strike - (debit / 100);
|
|
4858
|
+
const contracts = Math.max(1, Math.floor(maxRisk / debit));
|
|
4859
|
+
strategies.push({
|
|
4860
|
+
name: 'Bear Put Debit Spread',
|
|
4861
|
+
rationale: 'Bearish, IV cheap → buy premium, defined risk',
|
|
4862
|
+
legs: [`BUY ${contracts}x ${ticker} ${expStr} ${buy.strike} PUT @ ~$${mid(buy).toFixed(2)}`,
|
|
4863
|
+
`SELL ${contracts}x ${ticker} ${expStr} ${sell.strike} PUT @ ~$${mid(sell).toFixed(2)}`],
|
|
4864
|
+
netDebit: debit * contracts, maxProfit: maxProfit * contracts, maxLoss: debit * contracts,
|
|
4865
|
+
breakeven: be.toFixed(2), roi: ((maxProfit / Math.max(1, debit)) * 100).toFixed(0) + '%',
|
|
4866
|
+
});
|
|
4867
|
+
}
|
|
4868
|
+
if (ivRich) {
|
|
4869
|
+
const sell = calls.find(c => c.strike > spot * 1.03 && c.strike < spot * 1.07) || calls[Math.floor(calls.length / 2)];
|
|
4870
|
+
const buy = calls.find(c => c.strike > sell.strike + spot * 0.03) || calls[calls.length - 1];
|
|
4871
|
+
const credit = (mid(sell) - mid(buy)) * 100;
|
|
4872
|
+
const width = (buy.strike - sell.strike) * 100;
|
|
4873
|
+
const maxLoss = width - credit;
|
|
4874
|
+
const be = sell.strike + (credit / 100);
|
|
4875
|
+
const contracts = Math.max(1, Math.floor(maxRisk / maxLoss));
|
|
4876
|
+
strategies.push({
|
|
4877
|
+
name: 'Bear Call Credit Spread',
|
|
4878
|
+
rationale: 'Bearish, IV rich → sell premium, defined risk',
|
|
4879
|
+
legs: [`SELL ${contracts}x ${ticker} ${expStr} ${sell.strike} CALL @ ~$${mid(sell).toFixed(2)}`,
|
|
4880
|
+
`BUY ${contracts}x ${ticker} ${expStr} ${buy.strike} CALL @ ~$${mid(buy).toFixed(2)}`],
|
|
4881
|
+
netCredit: credit * contracts, maxProfit: credit * contracts, maxLoss: maxLoss * contracts,
|
|
4882
|
+
breakeven: be.toFixed(2), roi: ((credit / Math.max(1, maxLoss)) * 100).toFixed(0) + '%',
|
|
4883
|
+
});
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
if (direction === 'neutral' || direction === 'range') {
|
|
4888
|
+
if (ivRich) {
|
|
4889
|
+
// Iron Condor — sell OTM call + OTM put, buy further OTM wings
|
|
4890
|
+
const sellPut = puts.find(p => p.strike < spot * 0.95 && p.strike > spot * 0.90) || puts[Math.floor(puts.length * 0.3)];
|
|
4891
|
+
const buyPut = puts.find(p => p.strike < sellPut.strike - spot * 0.05) || puts[0];
|
|
4892
|
+
const sellCall = calls.find(c => c.strike > spot * 1.05 && c.strike < spot * 1.10) || calls[Math.floor(calls.length * 0.7)];
|
|
4893
|
+
const buyCall = calls.find(c => c.strike > sellCall.strike + spot * 0.05) || calls[calls.length - 1];
|
|
4894
|
+
const credit = (mid(sellPut) + mid(sellCall) - mid(buyPut) - mid(buyCall)) * 100;
|
|
4895
|
+
const widthPut = (sellPut.strike - buyPut.strike) * 100;
|
|
4896
|
+
const widthCall = (buyCall.strike - sellCall.strike) * 100;
|
|
4897
|
+
const maxLoss = Math.max(widthPut, widthCall) - credit;
|
|
4898
|
+
const contracts = Math.max(1, Math.floor(maxRisk / maxLoss));
|
|
4899
|
+
strategies.push({
|
|
4900
|
+
name: 'Iron Condor',
|
|
4901
|
+
rationale: 'Neutral, IV rich → defined-risk short premium, profits if price stays in range',
|
|
4902
|
+
legs: [
|
|
4903
|
+
`SELL ${contracts}x ${expStr} ${sellPut.strike} PUT @ ~$${mid(sellPut).toFixed(2)}`,
|
|
4904
|
+
`BUY ${contracts}x ${expStr} ${buyPut.strike} PUT @ ~$${mid(buyPut).toFixed(2)}`,
|
|
4905
|
+
`SELL ${contracts}x ${expStr} ${sellCall.strike} CALL @ ~$${mid(sellCall).toFixed(2)}`,
|
|
4906
|
+
`BUY ${contracts}x ${expStr} ${buyCall.strike} CALL @ ~$${mid(buyCall).toFixed(2)}`,
|
|
4907
|
+
],
|
|
4908
|
+
netCredit: credit * contracts, maxProfit: credit * contracts, maxLoss: maxLoss * contracts,
|
|
4909
|
+
breakeven: `${(sellPut.strike - credit / 100).toFixed(2)} to ${(sellCall.strike + credit / 100).toFixed(2)}`,
|
|
4910
|
+
roi: ((credit / Math.max(1, maxLoss)) * 100).toFixed(0) + '%',
|
|
4911
|
+
});
|
|
4912
|
+
}
|
|
4913
|
+
if (ivCheap) {
|
|
4914
|
+
// Long Straddle — direction-agnostic, profits from large move
|
|
4915
|
+
const debit = (mid(atmCall) + mid(atmPut)) * 100;
|
|
4916
|
+
const contracts = Math.max(1, Math.floor(maxRisk / debit));
|
|
4917
|
+
strategies.push({
|
|
4918
|
+
name: 'Long Straddle',
|
|
4919
|
+
rationale: 'Neutral, IV cheap → expecting a large move either direction (often pre-earnings)',
|
|
4920
|
+
legs: [
|
|
4921
|
+
`BUY ${contracts}x ${expStr} ${atmCall.strike} CALL @ ~$${mid(atmCall).toFixed(2)}`,
|
|
4922
|
+
`BUY ${contracts}x ${expStr} ${atmPut.strike} PUT @ ~$${mid(atmPut).toFixed(2)}`,
|
|
4923
|
+
],
|
|
4924
|
+
netDebit: debit * contracts, maxProfit: 'unlimited (call) / large (put)', maxLoss: debit * contracts,
|
|
4925
|
+
breakeven: `${(atmCall.strike - debit / 100).toFixed(2)} or ${(atmCall.strike + debit / 100).toFixed(2)}`,
|
|
4926
|
+
roi: 'depends on move magnitude',
|
|
4927
|
+
});
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
|
|
4931
|
+
if (strategies.length === 0) {
|
|
4932
|
+
strategies.push({ name: '(no recommendation)', rationale: `direction=${direction} + IV=${ivPercentile?.toFixed(0)}% — no matching template. Try direction=bullish/bearish/neutral.` });
|
|
4933
|
+
}
|
|
4934
|
+
|
|
4935
|
+
const ivLabel = ivPercentile == null ? 'n/a' : `${ivPercentile.toFixed(0)}th percentile (${ivCheap ? 'CHEAP' : ivRich ? 'RICH' : 'normal'})`;
|
|
4936
|
+
const lines = [
|
|
4937
|
+
`Option strategy builder — ${ticker} @ $${spot}`,
|
|
4938
|
+
`Direction: ${direction} | ATM IV: ${(atmIV * 100).toFixed(1)}% | IV rank: ${ivLabel}`,
|
|
4939
|
+
`Expiry chosen: ${expStr} (${dtePicked} DTE) | Max risk budget: $${maxRisk}`,
|
|
4940
|
+
'',
|
|
4941
|
+
];
|
|
4942
|
+
strategies.forEach((s, i) => {
|
|
4943
|
+
lines.push(`${i + 1}. ${s.name}`);
|
|
4944
|
+
lines.push(` ${s.rationale}`);
|
|
4945
|
+
(s.legs || []).forEach(l => lines.push(` ${l}`));
|
|
4946
|
+
if (s.netDebit) lines.push(` Net debit: $${(s.netDebit).toFixed(0)}`);
|
|
4947
|
+
if (s.netCredit) lines.push(` Net credit: $${(s.netCredit).toFixed(0)}`);
|
|
4948
|
+
if (s.maxProfit != null) lines.push(` Max profit: ${typeof s.maxProfit === 'number' ? '$' + s.maxProfit.toFixed(0) : s.maxProfit}`);
|
|
4949
|
+
if (s.maxLoss != null) lines.push(` Max loss: $${typeof s.maxLoss === 'number' ? s.maxLoss.toFixed(0) : s.maxLoss}`);
|
|
4950
|
+
if (s.breakeven) lines.push(` Breakeven: ${s.breakeven}`);
|
|
4951
|
+
if (s.roi) lines.push(` ROI: ${s.roi}`);
|
|
4952
|
+
lines.push('');
|
|
4953
|
+
});
|
|
4954
|
+
return lines.join('\n');
|
|
4955
|
+
} catch (e) { return `option_strategy_builder error: ${e.message}`; }
|
|
4956
|
+
}
|
|
4957
|
+
|
|
4958
|
+
case 'crypto_onchain_metrics': {
|
|
4959
|
+
const coin = String(params.coin || 'bitcoin').toLowerCase();
|
|
4960
|
+
const lines = [`On-chain & derivatives — ${coin}`];
|
|
4961
|
+
try {
|
|
4962
|
+
// 1. CoinGecko: price + market cap + supply
|
|
4963
|
+
const cgRes = await fetch(`https://api.coingecko.com/api/v3/coins/${encodeURIComponent(coin)}?localization=false&tickers=false&community_data=true&developer_data=false`, { headers: { 'Accept': 'application/json', 'User-Agent': 'NHA/1.0' } });
|
|
4964
|
+
if (cgRes.ok) {
|
|
4965
|
+
const cg = await cgRes.json();
|
|
4966
|
+
const md = cg.market_data || {};
|
|
4967
|
+
const price = md.current_price?.usd;
|
|
4968
|
+
const mcap = md.market_cap?.usd;
|
|
4969
|
+
const supply = md.circulating_supply;
|
|
4970
|
+
const ath = md.ath?.usd;
|
|
4971
|
+
const athDate = md.ath_date?.usd?.slice(0, 10);
|
|
4972
|
+
const fromAth = md.ath_change_percentage?.usd?.toFixed(1);
|
|
4973
|
+
lines.push(`Price: $${price?.toLocaleString()} | Market cap: $${(mcap / 1e9)?.toFixed(2)}B | Circulating: ${(supply / 1e6)?.toFixed(2)}M`);
|
|
4974
|
+
lines.push(`ATH: $${ath?.toLocaleString()} on ${athDate} (${fromAth}% from ATH)`);
|
|
4975
|
+
if (cg.community_data?.twitter_followers) lines.push(`Twitter followers: ${cg.community_data.twitter_followers.toLocaleString()}`);
|
|
4976
|
+
// MVRV approximation using market cap / realized cap (CoinGecko doesn't expose realized cap directly,
|
|
4977
|
+
// but we approximate via mcap / (supply × avg_30d_price) — quick proxy).
|
|
4978
|
+
if (md.market_cap_change_percentage_24h_in_currency?.usd != null) {
|
|
4979
|
+
lines.push(`24h mcap change: ${md.market_cap_change_percentage_24h_in_currency.usd.toFixed(2)}%`);
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
|
|
4983
|
+
// 2. DeFi Llama TVL (free, no key) — for L1s and DeFi protocols
|
|
4984
|
+
try {
|
|
4985
|
+
const llSlugMap = { bitcoin: null, ethereum: 'ethereum', solana: 'solana', avalanche: 'avalanche', polygon: 'polygon', arbitrum: 'arbitrum', optimism: 'optimism', polkadot: 'polkadot', cosmos: 'cosmos' };
|
|
4986
|
+
const slug = llSlugMap[coin];
|
|
4987
|
+
if (slug) {
|
|
4988
|
+
const llRes = await fetch(`https://api.llama.fi/v2/historicalChainTvl/${slug}`);
|
|
4989
|
+
if (llRes.ok) {
|
|
4990
|
+
const tvl = await llRes.json();
|
|
4991
|
+
const latest = tvl[tvl.length - 1];
|
|
4992
|
+
const month = tvl[tvl.length - 30] || tvl[0];
|
|
4993
|
+
const chgPct = month?.tvl ? ((latest.tvl - month.tvl) / month.tvl * 100) : 0;
|
|
4994
|
+
lines.push(`\nDeFi TVL on ${slug}: $${(latest.tvl / 1e9).toFixed(2)}B (30d ${chgPct >= 0 ? '+' : ''}${chgPct.toFixed(1)}%)`);
|
|
4995
|
+
}
|
|
4996
|
+
} else if (coin === 'bitcoin') {
|
|
4997
|
+
// BTC: pull total DeFi TVL on bitcoin sidechains
|
|
4998
|
+
const llRes = await fetch('https://api.llama.fi/v2/historicalChainTvl/Bitcoin');
|
|
4999
|
+
if (llRes.ok) {
|
|
5000
|
+
const tvl = await llRes.json();
|
|
5001
|
+
const latest = tvl[tvl.length - 1];
|
|
5002
|
+
lines.push(`\nDeFi TVL on Bitcoin (sidechains/L2): $${(latest.tvl / 1e6).toFixed(0)}M`);
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
} catch {}
|
|
5006
|
+
|
|
5007
|
+
// 3. Binance perpetual futures: funding rate + open interest (free, no key)
|
|
5008
|
+
try {
|
|
5009
|
+
const sym = coin === 'bitcoin' ? 'BTCUSDT' : coin === 'ethereum' ? 'ETHUSDT' : coin === 'solana' ? 'SOLUSDT' : null;
|
|
5010
|
+
if (sym) {
|
|
5011
|
+
const [fr, oi] = await Promise.all([
|
|
5012
|
+
fetch(`https://fapi.binance.com/fapi/v1/fundingRate?symbol=${sym}&limit=8`).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
5013
|
+
fetch(`https://fapi.binance.com/fapi/v1/openInterest?symbol=${sym}`).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
5014
|
+
]);
|
|
5015
|
+
if (Array.isArray(fr) && fr.length) {
|
|
5016
|
+
const lastFr = parseFloat(fr[fr.length - 1].fundingRate) * 100;
|
|
5017
|
+
const avg8 = fr.reduce((a, x) => a + parseFloat(x.fundingRate), 0) / fr.length * 100;
|
|
5018
|
+
lines.push(`\nBinance perp funding (8h × 8 readings): last ${lastFr.toFixed(4)}% | avg ${avg8.toFixed(4)}%`);
|
|
5019
|
+
lines.push(` → ${avg8 > 0.02 ? '🟢 longs paying shorts (bullish positioning)' : avg8 < -0.02 ? '🔴 shorts paying longs (bearish positioning)' : '🟡 neutral'}`);
|
|
5020
|
+
}
|
|
5021
|
+
if (oi?.openInterest) lines.push(`Open Interest: ${parseFloat(oi.openInterest).toLocaleString()} ${sym.replace('USDT', '')}`);
|
|
5022
|
+
}
|
|
5023
|
+
} catch {}
|
|
5024
|
+
|
|
5025
|
+
// 4. Fear & Greed Index (alternative.me, free)
|
|
5026
|
+
try {
|
|
5027
|
+
const fgRes = await fetch('https://api.alternative.me/fng/?limit=1', { headers: { 'Accept': 'application/json' } });
|
|
5028
|
+
if (fgRes.ok) {
|
|
5029
|
+
const fg = await fgRes.json();
|
|
5030
|
+
const f = fg.data?.[0];
|
|
5031
|
+
if (f) lines.push(`\nFear & Greed Index: ${f.value}/100 — ${f.value_classification}`);
|
|
5032
|
+
}
|
|
5033
|
+
} catch {}
|
|
5034
|
+
|
|
5035
|
+
// 5. BTC dominance + Stablecoin supply proxy
|
|
5036
|
+
try {
|
|
5037
|
+
const gRes = await fetch('https://api.coingecko.com/api/v3/global', { headers: { 'Accept': 'application/json' } });
|
|
5038
|
+
if (gRes.ok) {
|
|
5039
|
+
const g = await gRes.json();
|
|
5040
|
+
const dom = g.data?.market_cap_percentage || {};
|
|
5041
|
+
lines.push(`\nMarket structure: BTC dominance ${dom.btc?.toFixed(1)}% | ETH ${dom.eth?.toFixed(1)}% | Stablecoins (USDT+USDC) ${((dom.usdt || 0) + (dom.usdc || 0)).toFixed(1)}%`);
|
|
5042
|
+
}
|
|
5043
|
+
} catch {}
|
|
5044
|
+
|
|
5045
|
+
return lines.join('\n');
|
|
5046
|
+
} catch (e) { return `crypto_onchain_metrics error: ${e.message}`; }
|
|
5047
|
+
}
|
|
5048
|
+
|
|
5049
|
+
case 'portfolio_tax_lots': {
|
|
5050
|
+
// Tax-lot accounting. Reads ~/.nha/portfolio.json's `transactions` array
|
|
5051
|
+
// (buy/sell events with date + qty + price), produces realized + unrealized
|
|
5052
|
+
// gain breakdown using the chosen accounting method.
|
|
5053
|
+
const method = String(params.method || 'FIFO').toUpperCase(); // FIFO | LIFO | HIFO
|
|
5054
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
5055
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
5056
|
+
if (!fs.default.existsSync(file)) return 'Portfolio empty (no ~/.nha/portfolio.json).';
|
|
5057
|
+
const pf = JSON.parse(fs.default.readFileSync(file, 'utf-8'));
|
|
5058
|
+
const txs = (pf.transactions || []).slice().sort((a, b) => a.date.localeCompare(b.date));
|
|
5059
|
+
if (txs.length === 0) return 'No transactions recorded. Use portfolio_tx_add to record buys/sells with date.';
|
|
5060
|
+
|
|
5061
|
+
// Build open lots per ticker by applying buys/sells with the chosen method.
|
|
5062
|
+
const lotsByTicker = {};
|
|
5063
|
+
const realizedByTicker = {};
|
|
5064
|
+
const washSaleWarnings = [];
|
|
5065
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
5066
|
+
const yearAgoMs = Date.now() - 365 * 86400000;
|
|
5067
|
+
|
|
5068
|
+
for (const tx of txs) {
|
|
5069
|
+
const t = (tx.ticker || '').toUpperCase();
|
|
5070
|
+
if (!t) continue;
|
|
5071
|
+
lotsByTicker[t] = lotsByTicker[t] || [];
|
|
5072
|
+
realizedByTicker[t] = realizedByTicker[t] || { shortTerm: 0, longTerm: 0, totalSold: 0 };
|
|
5073
|
+
if (tx.type === 'buy' || tx.type === 'BUY') {
|
|
5074
|
+
lotsByTicker[t].push({ date: tx.date, qty: tx.qty, cost: tx.price, remaining: tx.qty });
|
|
5075
|
+
// Wash sale check: any sale at a LOSS within 30 days before this buy?
|
|
5076
|
+
const recentLosses = (realizedByTicker[t].events || []).filter(ev => ev.loss < 0 && (new Date(tx.date) - new Date(ev.date)) <= 30 * 86400000 && (new Date(tx.date) - new Date(ev.date)) >= 0);
|
|
5077
|
+
if (recentLosses.length) washSaleWarnings.push(`${t}: buy on ${tx.date} may trigger wash-sale rule (loss sale on ${recentLosses[0].date}, $${(-recentLosses[0].loss).toFixed(2)} disallowed).`);
|
|
5078
|
+
} else if (tx.type === 'sell' || tx.type === 'SELL') {
|
|
5079
|
+
let qtyToClose = tx.qty;
|
|
5080
|
+
const sortLots = (lots) => {
|
|
5081
|
+
if (method === 'LIFO') return [...lots].sort((a, b) => b.date.localeCompare(a.date));
|
|
5082
|
+
if (method === 'HIFO') return [...lots].sort((a, b) => b.cost - a.cost);
|
|
5083
|
+
return [...lots].sort((a, b) => a.date.localeCompare(b.date)); // FIFO
|
|
5084
|
+
};
|
|
5085
|
+
const order = sortLots(lotsByTicker[t]);
|
|
5086
|
+
for (const lot of order) {
|
|
5087
|
+
if (qtyToClose <= 0) break;
|
|
5088
|
+
if (lot.remaining <= 0) continue;
|
|
5089
|
+
const take = Math.min(lot.remaining, qtyToClose);
|
|
5090
|
+
const proceeds = take * tx.price;
|
|
5091
|
+
const costBasis = take * lot.cost;
|
|
5092
|
+
const gain = proceeds - costBasis;
|
|
5093
|
+
const holdMs = new Date(tx.date) - new Date(lot.date);
|
|
5094
|
+
const isLongTerm = holdMs > 365 * 86400000;
|
|
5095
|
+
if (isLongTerm) realizedByTicker[t].longTerm += gain;
|
|
5096
|
+
else realizedByTicker[t].shortTerm += gain;
|
|
5097
|
+
realizedByTicker[t].totalSold += take;
|
|
5098
|
+
realizedByTicker[t].events = realizedByTicker[t].events || [];
|
|
5099
|
+
realizedByTicker[t].events.push({ date: tx.date, qty: take, gain, loss: gain < 0 ? gain : 0, lotDate: lot.date, isLongTerm });
|
|
5100
|
+
lot.remaining -= take;
|
|
5101
|
+
qtyToClose -= take;
|
|
5102
|
+
}
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
|
|
5106
|
+
// Fetch live prices for unrealized gain.
|
|
5107
|
+
const tickersOpen = Object.keys(lotsByTicker).filter(t => lotsByTicker[t].some(l => l.remaining > 0));
|
|
5108
|
+
let priceMap = {};
|
|
5109
|
+
if (tickersOpen.length) {
|
|
5110
|
+
try {
|
|
5111
|
+
const r = await fetch(`https://query1.finance.yahoo.com/v7/finance/quote?symbols=${tickersOpen.join(',')}`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
5112
|
+
if (r.ok) {
|
|
5113
|
+
const d = await r.json();
|
|
5114
|
+
priceMap = Object.fromEntries((d?.quoteResponse?.result || []).map(q => [q.symbol, q.regularMarketPrice]));
|
|
5115
|
+
}
|
|
5116
|
+
} catch {}
|
|
5117
|
+
}
|
|
5118
|
+
|
|
5119
|
+
const lines = [`Tax lot report — method: ${method}`, ''];
|
|
5120
|
+
let totalRealizedShort = 0, totalRealizedLong = 0, totalUnrealized = 0;
|
|
5121
|
+
let lossHarvestCandidates = [];
|
|
5122
|
+
for (const t of Object.keys(lotsByTicker)) {
|
|
5123
|
+
const open = lotsByTicker[t].filter(l => l.remaining > 0);
|
|
5124
|
+
const realized = realizedByTicker[t] || { shortTerm: 0, longTerm: 0 };
|
|
5125
|
+
const price = priceMap[t];
|
|
5126
|
+
let unrealized = 0;
|
|
5127
|
+
open.forEach(l => { if (price) unrealized += (price - l.cost) * l.remaining; });
|
|
5128
|
+
totalRealizedShort += realized.shortTerm;
|
|
5129
|
+
totalRealizedLong += realized.longTerm;
|
|
5130
|
+
totalUnrealized += unrealized;
|
|
5131
|
+
// Tax-loss harvesting: lot with unrealized loss + holding > some threshold
|
|
5132
|
+
open.forEach(l => {
|
|
5133
|
+
if (price && (price - l.cost) * l.remaining < -100) {
|
|
5134
|
+
const holdDays = (Date.now() - new Date(l.date).getTime()) / 86400000;
|
|
5135
|
+
lossHarvestCandidates.push({ ticker: t, lotDate: l.date, qty: l.remaining, cost: l.cost, currentPrice: price, loss: (price - l.cost) * l.remaining, holdDays });
|
|
5136
|
+
}
|
|
5137
|
+
});
|
|
5138
|
+
lines.push(`${t}:`);
|
|
5139
|
+
lines.push(` Realized: short-term $${realized.shortTerm.toFixed(2)} | long-term $${realized.longTerm.toFixed(2)} | total $${(realized.shortTerm + realized.longTerm).toFixed(2)}`);
|
|
5140
|
+
if (open.length) {
|
|
5141
|
+
lines.push(` Open lots (${open.length}, live $${price?.toFixed(2) || '?'}):`);
|
|
5142
|
+
open.forEach(l => {
|
|
5143
|
+
const u = price ? (price - l.cost) * l.remaining : 0;
|
|
5144
|
+
const ageDays = ((Date.now() - new Date(l.date).getTime()) / 86400000).toFixed(0);
|
|
5145
|
+
lines.push(` ${l.date} qty ${l.remaining} cost $${l.cost.toFixed(2)} unrealized $${u.toFixed(2)} (${ageDays}d, ${ageDays > 365 ? 'long-term' : 'SHORT-TERM'})`);
|
|
5146
|
+
});
|
|
5147
|
+
}
|
|
5148
|
+
lines.push('');
|
|
5149
|
+
}
|
|
5150
|
+
lines.push(`TOTALS:`);
|
|
5151
|
+
lines.push(` Realized short-term gains: $${totalRealizedShort.toFixed(2)} (taxed at ordinary income rate)`);
|
|
5152
|
+
lines.push(` Realized long-term gains: $${totalRealizedLong.toFixed(2)} (taxed at 0/15/20% in US)`);
|
|
5153
|
+
lines.push(` Unrealized gains: $${totalUnrealized.toFixed(2)}`);
|
|
5154
|
+
if (washSaleWarnings.length) {
|
|
5155
|
+
lines.push(`\n⚠ Wash-sale warnings:\n ${washSaleWarnings.join('\n ')}`);
|
|
5156
|
+
}
|
|
5157
|
+
if (lossHarvestCandidates.length) {
|
|
5158
|
+
lines.push(`\n💡 Tax-loss harvest candidates (unrealized loss > $100):`);
|
|
5159
|
+
lossHarvestCandidates.sort((a, b) => a.loss - b.loss).slice(0, 5).forEach(c => {
|
|
5160
|
+
lines.push(` ${c.ticker} lot ${c.lotDate} qty ${c.qty}: realize $${c.loss.toFixed(2)} loss${c.holdDays < 31 ? ' ⚠ <31 days, wash-sale risk' : ''}`);
|
|
5161
|
+
});
|
|
5162
|
+
}
|
|
5163
|
+
return lines.join('\n');
|
|
5164
|
+
}
|
|
5165
|
+
|
|
5166
|
+
case 'portfolio_tx_add': {
|
|
5167
|
+
// Record a buy/sell transaction. Used to feed portfolio_tax_lots.
|
|
5168
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
5169
|
+
const type = String(params.type || 'buy').toLowerCase();
|
|
5170
|
+
const qty = parseFloat(params.qty || params.quantity || '0');
|
|
5171
|
+
const price = parseFloat(params.price || '0');
|
|
5172
|
+
const date = String(params.date || new Date().toISOString().slice(0, 10));
|
|
5173
|
+
if (!ticker || qty <= 0 || !['buy', 'sell'].includes(type)) return 'portfolio_tx_add: ticker, type (buy|sell), qty (>0), price required.';
|
|
5174
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
5175
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
5176
|
+
let pf = { positions: [], transactions: [] };
|
|
5177
|
+
try { if (fs.default.existsSync(file)) pf = JSON.parse(fs.default.readFileSync(file, 'utf-8')); } catch {}
|
|
5178
|
+
pf.transactions = pf.transactions || [];
|
|
5179
|
+
pf.transactions.push({ ticker, type, qty, price, date, recordedAt: new Date().toISOString() });
|
|
5180
|
+
// Also update current `positions` for parity with portfolio_summary.
|
|
5181
|
+
pf.positions = pf.positions || [];
|
|
5182
|
+
const idx = pf.positions.findIndex(p => p.ticker === ticker);
|
|
5183
|
+
if (type === 'buy') {
|
|
5184
|
+
if (idx >= 0) {
|
|
5185
|
+
const existing = pf.positions[idx];
|
|
5186
|
+
const totalQty = existing.qty + qty;
|
|
5187
|
+
existing.cost = ((existing.qty * existing.cost) + (qty * price)) / totalQty;
|
|
5188
|
+
existing.qty = totalQty;
|
|
5189
|
+
} else {
|
|
5190
|
+
pf.positions.push({ ticker, qty, cost: price, addedAt: new Date().toISOString() });
|
|
5191
|
+
}
|
|
5192
|
+
} else {
|
|
5193
|
+
if (idx >= 0) {
|
|
5194
|
+
pf.positions[idx].qty -= qty;
|
|
5195
|
+
if (pf.positions[idx].qty <= 0) pf.positions.splice(idx, 1);
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
fs.default.mkdirSync(path.default.dirname(file), { recursive: true });
|
|
5199
|
+
fs.default.writeFileSync(file, JSON.stringify(pf, null, 2));
|
|
5200
|
+
return `Transaction recorded: ${type.toUpperCase()} ${qty} ${ticker} @ $${price} on ${date}. Total transactions: ${pf.transactions.length}.`;
|
|
5201
|
+
}
|
|
5202
|
+
|
|
5203
|
+
case 'insider_trading': {
|
|
5204
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
5205
|
+
const limit = Math.min(parseInt(params.limit || '15', 10), 30);
|
|
5206
|
+
if (!ticker) return 'insider_trading: ticker required.';
|
|
5207
|
+
try {
|
|
5208
|
+
const ua = 'NotHumanAllowed CLI hello@nothumanallowed.com';
|
|
5209
|
+
const tres = await fetch('https://www.sec.gov/files/company_tickers.json', { headers: { 'User-Agent': ua } });
|
|
5210
|
+
if (!tres.ok) return `insider_trading: ticker map HTTP ${tres.status}`;
|
|
5211
|
+
const map = await tres.json();
|
|
5212
|
+
const entry = Object.values(map).find(e => e.ticker?.toUpperCase() === ticker);
|
|
5213
|
+
if (!entry) return `insider_trading: ${ticker} not in SEC EDGAR (US-listed only).`;
|
|
5214
|
+
const cik = String(entry.cik_str).padStart(10, '0');
|
|
5215
|
+
const fres = await fetch(`https://data.sec.gov/submissions/CIK${cik}.json`, { headers: { 'User-Agent': ua } });
|
|
5216
|
+
if (!fres.ok) return `insider_trading: HTTP ${fres.status}`;
|
|
5217
|
+
const fd = await fres.json();
|
|
5218
|
+
const recent = fd.filings?.recent || {};
|
|
5219
|
+
const rows = [];
|
|
5220
|
+
for (let i = 0; i < (recent.form?.length || 0); i++) {
|
|
5221
|
+
if (recent.form[i] !== '4') continue;
|
|
5222
|
+
rows.push({
|
|
5223
|
+
date: recent.filingDate[i],
|
|
5224
|
+
accession: recent.accessionNumber[i],
|
|
5225
|
+
primary: recent.primaryDocument[i],
|
|
5226
|
+
});
|
|
5227
|
+
if (rows.length >= limit) break;
|
|
5228
|
+
}
|
|
5229
|
+
if (rows.length === 0) return `insider_trading: no Form 4 (insider) filings for ${ticker}.`;
|
|
5230
|
+
const lines = [`Insider trading (Form 4) — ${entry.title} (${ticker}) — last ${rows.length}`];
|
|
5231
|
+
rows.forEach(r => {
|
|
5232
|
+
const accNoDash = r.accession.replace(/-/g, '');
|
|
5233
|
+
const url = `https://www.sec.gov/Archives/edgar/data/${parseInt(cik, 10)}/${accNoDash}/${r.primary}`;
|
|
5234
|
+
lines.push(` ${r.date} Form 4 → ${url}`);
|
|
5235
|
+
});
|
|
5236
|
+
lines.push(`\nNote: Form 4 filings report officer/director/10%-owner transactions. Open URLs to see whether BUY (acquired) or SELL (disposed).`);
|
|
5237
|
+
return lines.join('\n');
|
|
5238
|
+
} catch (e) { return `insider_trading error: ${e.message}`; }
|
|
5239
|
+
}
|
|
5240
|
+
|
|
4463
5241
|
case 'italian_market': {
|
|
4464
5242
|
// FTSE MIB constituents and BTP-Bund spread (10y Italy minus 10y Germany).
|
|
4465
5243
|
const which = String(params.what || 'all').toLowerCase();
|
|
@@ -274,7 +274,57 @@ Provide specific, actionable levels. Use technical analysis (EMA, RSI, MACD, Fib
|
|
|
274
274
|
- Maximum drawdown and recovery expectations
|
|
275
275
|
- Strategy capacity (AUM limits before alpha decay)
|
|
276
276
|
- Implementation costs: slippage, market impact, transaction costs
|
|
277
|
-
- Key risks: overfitting, regime change, crowding`,agents:[{icon:`📊`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`📈`,agent:`edi`,label:`Edi`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``},{icon:`🔥`,agent:`prometheus`,label:`Prometheus`,status:`waiting`,output:``}]}],De=`trading strategy.fundamental analysis.portfolio risk.sector deep dive.crypto analysis.quant factor.macro regime.cross-asset.hedge.derivative.yield curve.market_price.market_chart.market_indicators.macro_data.crypto_data.valuation.equity.earnings.dividend.short interest.volatility.options.futures.bitcoin.ethereum`.split(`.`),Oe=[`Analyze my unread emails and create a priority action plan`,`Search the web for AI news today and summarize it in a canvas report`,`Check my calendar for this week and suggest how to optimize my schedule`,`Review my GitHub notifications and draft responses to open issues`,`Search for information about a topic, fact-check it, and write a report`],I={bg:`#0a0d14`,wall:`#0d1117`,wallStripe:`#0f1520`,baseboard:`#1e293b`,floorA:`#111827`,floorB:`#141c2b`,floorLine:`#1e293b`,ceiling:`#0b0f18`,lampBody:`#854d0e`,lampGlow:`#fef08a`,partition:`#1a2436`,partitionH:`#253347`,partitionT:`#2d4a6e`,deskTop:`#2d4263`,deskFace:`#1e3352`,deskSide:`#192b43`,deskLeg:`#0f1e30`,monBody:`#0f172a`,monFace:`#162032`,monScrIdle:`#060b12`,monScrOn:`#001208`,monScrDone:`#001a08`,monStand:`#0d1625`,keyboard:`#162032`,chairBack:`#1a3a5c`,chairSeat:`#1e4068`,chairLeg:`#0f1e30`,plant1:`#14532d`,plant2:`#166534`,pot:`#78350f`,cup:`#7c2d12`,paper:`#e2e8f0`,paperLine:`#94a3b8`,skin:[`#f5c97a`,`#fbbf24`,`#fca5a5`,`#86efac`,`#a5f3fc`,`#c4b5fd`,`#fde68a`,`#f9a8d4`],shirt:[`#1e40af`,`#065f46`,`#312e81`,`#be123c`,`#164e63`,`#7c3aed`,`#7f1d1d`,`#0e7490`],hair:[`#7c3aed`,`#92400e`,`#1e293b`,`#6b21a8`,`#78350f`,`#292524`,`#422006`,`#0c4a6e`],pants:`#1e293b`,shoes:`#0f172a`,cSkin:`#fbbf24`,cShirt:`#4f46e5`,cHair:`#111827`,cTie:`#dc2626`,cPants:`#1e293b`,cShoes:`#0f172a`,cBag:`#78350f`,green:`#22c55e`,greenDim:`#14532d`,cyan:`#67e8f9`,purple:`#a78bfa`,red:`#ef4444`,amber:`#fbbf24`,dim:`#334155`,white:`#f1f5f9`,bubbleBg:`#0f172a`,bubbleBdr:`#334155`},L=2,ke=1200,Ae=140,je=ke*L,Me=Ae*L,Ne=Math.round(Ae*.78),Pe=130,Fe=14,Ie=Ne-Fe-2,Le=8,Re=36;function R(e,t,n,r,i=1,a=1){e.fillStyle=t,e.fillRect(n*L,r*L,i*L,a*L)}function ze(e){R(e,I.ceiling,0,0,ke,7);for(let t=0;t<ke;t++)R(e,t%20<10?I.wall:I.wallStripe,t,7,1,Ne-7);R(e,I.baseboard,0,Ne-3,ke,3);for(let t=0;t<ke;t+=14)for(let n=Ne;n<Ae;n+=7)R(e,(Math.floor(t/14)+Math.floor((n-Ne)/7))%2==0?I.floorA:I.floorB,t,n,14,7),R(e,I.floorLine,t,n,14,1),R(e,I.floorLine,t,n,1,7);for(let t=40;t<ke;t+=120){R(e,I.lampBody,t,1,40,4),R(e,I.lampGlow,t+2,2,36,2);for(let n=0;n<18;n++)e.fillStyle=`rgba(254,240,138,${(.06-n*.003).toFixed(4)})`,e.fillRect((t+20-n*1.1)*L,(5+n)*L,n*2.2*L,L)}}function Be(e,t,n,r){let i=t.x,a=t.status===`running`,o=t.status===`done`,s=t.status===`error`,c=Math.abs(n-(i+Pe/2))<30;a&&(e.fillStyle=`rgba(99,102,241,0.09)`,e.fillRect(i*L,10*L,Pe*L,(Ae-10)*L)),o&&(e.fillStyle=`rgba(34,197,94,0.05)`,e.fillRect(i*L,10*L,Pe*L,(Ae-10)*L));let l=Ne-8;R(e,I.partition,i,8,3,l),R(e,I.partitionH,i+1,8,2,l),R(e,I.partition,i+Pe,8,3,l),R(e,a?I.partitionT:o?`#1a3a2e`:s?`#3a1a1a`:I.partitionH,i,8,Pe+3,4),R(e,`#0f172a`,i+5,14,16,10),R(e,`#162032`,i+6,15,14,3),a?(R(e,I.green,i+7,19,11,1),R(e,I.greenDim,i+7,21,12,1)):o?(R(e,I.green,i+7,18,12,1),R(e,I.green,i+7,20,9,1),R(e,I.green,i+7,22,11,1)):(R(e,I.dim,i+7,19,10,1),R(e,I.dim,i+7,21,7,1));let u=i+Pe-14;R(e,I.pot,u+2,Ie+Fe-5,7,5),R(e,I.plant1,u,Ie+Fe-13,10,8),R(e,I.plant2,u+2,Ie+Fe-17,6,5),R(e,I.plant2,u-2,Ie+Fe-12,5,4),R(e,I.plant2,u+7,Ie+Fe-13,5,4),R(e,I.deskTop,i+4,Ie,Pe-6,3),R(e,I.deskFace,i+4,Ie+3,Pe-6,Fe-3),R(e,I.deskSide,i+4,Ie+Fe,Pe-6,3);let d=Ne-Ie-Fe-3;R(e,I.deskLeg,i+8,Ie+Fe+3,4,d),R(e,I.deskLeg,i+Pe-10,Ie+Fe+3,4,d);let f=i+14,p=Ie-18;if(R(e,I.monBody,f,p,22,14),R(e,I.monFace,f+1,p+1,20,12),R(e,a?I.monScrOn:o?I.monScrDone:I.monScrIdle,f+2,p+2,18,8),a)for(let t=0;t<3;t++){let n=3+Math.floor((r/90+t*5)%12);R(e,I.green,f+3,p+3+t*2,n,1),R(e,I.greenDim,f+3+n,p+3+t*2,12-n,1)}else o?(R(e,I.green,f+3,p+3,10,1),R(e,I.green,f+3,p+5,8,1),R(e,I.green,f+3,p+7,9,1),R(e,I.green,f+13,p+3,2,4)):s?(R(e,I.red,f+8,p+2,4,6),R(e,I.red,f+8,p+9,4,2)):(R(e,I.dim,f+3,p+3,12,1),R(e,I.dim,f+3,p+5,8,1),R(e,I.dim,f+3,p+7,10,1));R(e,I.monStand,f+8,p+14,4,3),R(e,I.monStand,f+5,p+17,10,1),R(e,I.keyboard,f-1,Ie+1,16,4);for(let t=0;t<5;t++)R(e,I.monFace,f+t*3,Ie+2,2,2);R(e,I.cup,i+7,Ie+2,5,5),R(e,`#92400e`,i+12,Ie+4,2,3),R(e,I.paper,i+52,Ie+2,12,7),R(e,I.paper,i+54,Ie+1,12,7),R(e,I.paperLine,i+56,Ie+3,7,1),R(e,I.paperLine,i+56,Ie+5,5,1),R(e,I.chairBack,i+Pe/2-12,Ne-20,24,3),R(e,I.chairSeat,i+Pe/2-13,Ne-14,26,8),R(e,I.chairLeg,i+Pe/2-8,Ne-6,4,6),R(e,I.chairLeg,i+Pe/2+5,Ne-6,4,6),Ve(e,t,i+Pe/2-8,Ne-Re,a,c,r);let m=a?I.cyan:o?I.green:s?I.red:I.dim;e.font=`bold ${4*L}px monospace`,e.fillStyle=m,e.textBaseline=`alphabetic`;let h=t.label.length>14?t.label.slice(0,13)+`…`:t.label;e.fillText(h,(i+4)*L,8*L),R(e,m,i+Pe-4,9,4,4),a&&Math.floor(r/250)%2==0&&R(e,I.white,i+Pe-3,10,2,2)}function Ve(e,t,n,r,i,a,o){let s=I.skin[t.skinIdx],c=I.shirt[t.shirtIdx],l=I.hair[t.hairIdx],u=i&&!a?Math.floor(o/700)%2:0;R(e,l,n+1,r+u,12,3),R(e,l,n,r+1+u,14,2),R(e,l,n,r+3+u,2,5),R(e,l,n+12,r+3+u,2,5),R(e,s,n+1,r+3+u,12,9);let d=l;R(e,d,n+2,r+4+u,3,1),R(e,d,n+9,r+4+u,3,1),R(e,`#e2e8f0`,n+2,r+5+u,3,3),R(e,`#e2e8f0`,n+9,r+5+u,3,3);let f=t.skinIdx%2==0?`#1d4ed8`:`#065f46`;if(R(e,f,n+3,r+6+u,2,2),R(e,f,n+10,r+6+u,2,2),R(e,`#0f172a`,n+3,r+7+u,1,1),R(e,`#0f172a`,n+10,r+7+u,1,1),R(e,`#c97a4a`,n+7,r+9+u,1,2),t.status===`done`)R(e,`#991b1b`,n+4,r+11+u,6,1),R(e,`#991b1b`,n+3,r+10+u,1,1),R(e,`#991b1b`,n+10,r+10+u,1,1);else if(t.status===`error`)R(e,`#991b1b`,n+4,r+10+u,6,1),R(e,`#991b1b`,n+3,r+11+u,1,1),R(e,`#991b1b`,n+10,r+11+u,1,1);else if(t.status===`running`){let t=Math.floor(o/250)%2;R(e,`#991b1b`,n+5,r+10+u,4,t?2:1)}else R(e,`#7f1d1d`,n+5,r+11+u,4,1);if(R(e,s,n+4,r+12,6,2),R(e,c,n+1,r+14,14,11),R(e,`#f1f5f9`,n+4,r+14,3,4),R(e,`#f1f5f9`,n+9,r+14,3,4),e.font=`${4*L}px serif`,e.textBaseline=`middle`,e.textAlign=`center`,e.fillText(t.icon,(n+8)*L,(r+19)*L),e.textAlign=`left`,i&&!a){let t=Math.floor(o/120)%2;R(e,c,n-2,r+14,3,9),R(e,s,n-2,r+22-t,3,3),R(e,c,n+15,r+14,3,9),R(e,s,n+15,r+22-(1-t),3,3)}else if(a&&i){let t=Math.floor(o/180)%3;R(e,c,n-2,r+14,3,8),R(e,s,n-2,r+22,3,3),R(e,c,n+15,r+8+t,3,9),R(e,s,n+15,r+5+t,3,4)}else R(e,c,n-2,r+14,3,9),R(e,s,n-2,r+23,3,2),R(e,c,n+15,r+14,3,9),R(e,s,n+15,r+23,3,2);R(e,I.pants,n+2,r+25,5,7),R(e,I.pants,n+9,r+25,5,7),R(e,`#2d3f5f`,n+3,r+29,3,1),R(e,`#2d3f5f`,n+10,r+29,3,1),R(e,I.shoes,n,r+32,7,4),R(e,I.shoes,n+9,r+32,7,4),R(e,`#1e2a3a`,n+1,r+32,5,1),R(e,`#1e2a3a`,n+10,r+32,5,1),e.fillStyle=`rgba(0,0,0,0.35)`,e.beginPath(),e.ellipse((n+8)*L,(Ne-1)*L,9*L,2*L,0,0,Math.PI*2),e.fill()}function He(e){e.papers.length>=6||e.papers.push({x:e.x+20+Math.random()*(Pe-40),y:Ie-4,vx:(Math.random()-.5)*1.5,vy:-1.5-Math.random()*1,rot:Math.random()*360,vrot:(Math.random()-.5)*10,life:0,maxLife:70+Math.random()*40})}function Ue(e){e.papers=e.papers.filter(e=>e.life<e.maxLife);for(let t of e.papers)t.x+=t.vx,t.y+=t.vy,t.vy+=.06,t.rot+=t.vrot,t.life++}function We(e,t){for(let n of t.papers){let t=1-n.life/n.maxLife;e.save(),e.globalAlpha=t,e.translate(n.x*L,n.y*L),e.rotate(n.rot*Math.PI/180),e.fillStyle=I.paper,e.fillRect(-7*L,-5*L,14*L,10*L),e.fillStyle=I.paperLine,e.fillRect(-5*L,-2*L,8*L,L),e.fillRect(-5*L,0,6*L,L),e.restore()}e.globalAlpha=1}var Ge=`ABCDEFGHIJKLMNOPRSTUVWXYZ0123456789!?-_.:`;function Ke(e,t){return t<=0?e:Ge[Math.floor(Math.random()*41)]??e}function qe(e,t,n,r,i,a=110,o=`down`){if(t.length===0)return;e.fillStyle=I.bubbleBg,e.fillRect(r*L,i*L,a*L,36*L),e.strokeStyle=I.bubbleBdr,e.lineWidth=L,e.strokeRect(r*L,i*L,a*L,36*L),e.fillStyle=I.cyan,e.fillRect(r*L,i*L,a*L,L),e.fillStyle=I.bubbleBdr,e.fillRect(r*L,(i+13+5)*L,a*L,L);for(let o=0;o<2;o++){let s=t[(Math.floor(n)+o)%t.length];if(!s)continue;let c=i+5+o*14;e.fillStyle=o%2==0?`#0f172a`:`#111827`,e.fillRect(r*L,c*L,a*L,13*L);let l=n-Math.floor(n),u=s.text.split(``);e.font=`bold ${5*L}px monospace`,e.textBaseline=`middle`;let d=Math.floor(a/7)-1;for(let t=0;t<Math.min(u.length,d);t++){let n=l>.3&&t>u.length*(1-l)?Ke(u[t],3):u[t];e.globalAlpha=l>0&&t>u.length*(1-l)?.5+l*.5:1,e.fillStyle=s.color,e.fillText(n,(r+4+t*7)*L,(c+6)*L)}e.globalAlpha=1}let s=r+a/2;if(e.fillStyle=I.bubbleBdr,o===`down`){let t=i+36;e.fillRect((s-2)*L,t*L,4*L,4*L),e.fillRect((s-3)*L,(t+4)*L,6*L,3*L)}else e.fillRect((s-2)*L,(i-4)*L,4*L,4*L),e.fillRect((s-3)*L,(i-7)*L,6*L,3*L)}function Je(e,t,n,r){let i=Ne-Re-8,a=n%2,o=a===0?2:0,s=a===0?0:2;e.fillStyle=`rgba(0,0,0,0.4)`,e.beginPath(),e.ellipse(t*L,(Ne-1)*L,9*L,2*L,0,0,Math.PI*2),e.fill(),e.save(),e.translate(t*L,0),e.scale(r?1:-1,1),R(e,I.cHair,-4,i,9,3),R(e,I.cHair,-5,i+1,11,2),R(e,I.cHair,-5,i+3,2,3),R(e,I.cHair,5,i+3,2,3),R(e,I.cSkin,-4,i+3,8,7),R(e,`#0f172a`,-2,i+5,2,2),R(e,`#0f172a`,2,i+5,2,2),R(e,`#1d4ed8`,-1,i+6,1,1),R(e,`#1d4ed8`,3,i+6,1,1),R(e,`#7f1d1d`,-2,i+9,5,1),R(e,I.cSkin,-1,i+10,3,2),R(e,I.cShirt,-5,i+12,11,9),R(e,`#3730a3`,-5,i+12,3,5),R(e,`#3730a3`,3,i+12,3,5),R(e,I.cTie,-1,i+12,3,7),R(e,`#f1f5f9`,-2,i+12,1,3),R(e,`#f1f5f9`,2,i+12,1,3),R(e,I.cShirt,-7,i+13,3,9),R(e,I.cSkin,-7,i+21,3,3),R(e,I.cShirt,5,i+13,3,9),R(e,I.cSkin,5,i+21,3,3),R(e,I.cBag,6,i+24,6,4),R(e,`#92400e`,7,i+23,4,1),R(e,I.cPants,-3,i+21+o,5,10),R(e,I.cPants,1,i+21+s,5,10),R(e,I.cShoes,-5,i+29+o,6,3),R(e,I.cShoes,-1,i+29+s,6,3),e.restore()}var Ye=[[{text:`RUNNING WORKFLOW`,color:I.cyan},{text:`DISPATCHING...`,color:I.amber}],[{text:`ORCHESTRATING`,color:I.cyan},{text:`ALL SYSTEMS GO`,color:I.green}],[{text:`AGENT PIPELINE`,color:I.purple},{text:`EXECUTING...`,color:I.cyan}],[{text:`MONITORING ALL`,color:I.amber},{text:`ON SCHEDULE`,color:I.green}]];function Xe(e,t){let n=e.toUpperCase().slice(0,14);return t===`running`?[{text:n,color:I.cyan},{text:`WORKING...`,color:I.green}]:t===`done`?[{text:n,color:I.green},{text:`TASK DONE`,color:I.green}]:t===`error`?[{text:n,color:I.red},{text:`ERR`,color:I.red}]:[{text:n,color:I.dim},{text:`STANDBY`,color:I.dim}]}function Ze({nodes:e,running:t}){let n=(0,_.useRef)(null),r=(0,_.useRef)(null);return(0,_.useEffect)(()=>{if(e.length===0)return;let t=Math.min(e.length,8)*(Pe+Le)-Le,n=Math.max(6,Math.round((ke-t)/2));if(!r.current)r.current={agents:e.slice(0,8).map((e,t)=>({x:n+t*(Pe+Le),icon:e.icon,label:e.label,status:e.status,skinIdx:t%I.skin.length,shirtIdx:t%I.shirt.length,hairIdx:t%I.hair.length,papers:[],bubbleLines:Xe(e.label,e.status),bubbleScroll:0,bubbleTimer:0})),condX:n+Pe/2,condTargetX:n+Pe/2,condFacing:!0,condFrame:0,condFrameTimer:0,condIdleTimer:0,condBubble:Ye[0],condBubbleScroll:0,condBubbleTimer:0,rafId:0};else{let t=r.current;for(let r=0;r<Math.min(e.length,8);r++){let i=e[r],a=t.agents[r];a?(a.status!==i.status&&(a.status=i.status,a.bubbleLines=Xe(i.label,i.status),a.bubbleScroll=0),a.icon=i.icon,a.label=i.label):t.agents.push({x:n+r*(Pe+Le),icon:i.icon,label:i.label,status:i.status,skinIdx:r%I.skin.length,shirtIdx:r%I.shirt.length,hairIdx:r%I.hair.length,papers:[],bubbleLines:Xe(i.label,i.status),bubbleScroll:0,bubbleTimer:0})}}},[e]),(0,_.useEffect)(()=>{let e=n.current;if(!e)return;let i=e.getContext(`2d`);if(!i||(i.imageSmoothingEnabled=!1,!r.current))return;let a=0,o=0;function s(e){let n=r.current,c=Math.min(e-a,50);a=e;let l=e,u=n.agents.find(e=>e.status===`running`);if(u){let e=u.x+Pe+12,t=u.x-18;n.condTargetX=e<ke-30?e:t,n.condBubbleTimer+=c,n.condBubbleTimer>2e3&&(n.condBubbleTimer=0,n.condBubble=Ye[Math.floor(Math.random()*Ye.length)],n.condBubbleScroll=(n.condBubbleScroll+1)%4)}else if(n.condIdleTimer-=c,n.condIdleTimer<=0){let e=n.agents[Math.floor(Math.random()*n.agents.length)];e&&(n.condTargetX=e.x+Pe/2),n.condIdleTimer=2e3+Math.random()*3e3}let d=n.condTargetX-n.condX;if(Math.abs(d)>1&&(n.condX+=d*(t?.12:.04)*(c/16),n.condFacing=d>0,n.condFrameTimer+=c,n.condFrameTimer>100&&(n.condFrame++,n.condFrameTimer=0)),o+=c,o>200){o=0;for(let e of n.agents)e.status===`running`&&He(e)}for(let e of n.agents)Ue(e);for(let e of n.agents)e.bubbleTimer+=c,e.bubbleTimer>1800&&(e.bubbleTimer=0,e.bubbleScroll=(e.bubbleScroll+1)%4);i.clearRect(0,0,je,Me),ze(i);for(let e of n.agents)Be(i,e,n.condX,l),We(i,e);Je(i,n.condX,n.condFrame,n.condFacing);for(let e of n.agents){if(e.status===`waiting`)continue;let t=Math.min(e.label.length*6+16,90),n=Math.max(e.x+1,Math.min(e.x+(Pe-t)/2,ke-t-2)),r=Ne-Re-36;qe(i,e.bubbleLines,e.bubbleScroll,n,r,t,`down`)}let f=Ne-Re-8-38,p=Math.max(4,Math.min(n.condX-50,ke-106));qe(i,n.condBubble,n.condBubbleScroll,p,f,100,`down`);for(let e=0;e<Me;e+=L*3)i.fillStyle=`rgba(0,0,0,0.04)`,i.fillRect(0,e,je,L);n.rafId=requestAnimationFrame(s)}return r.current.rafId=requestAnimationFrame(s),()=>{r.current&&cancelAnimationFrame(r.current.rafId)}},[t]),e.length===0?null:(0,A.jsx)(`div`,{style:{width:`100%`,lineHeight:0},children:(0,A.jsx)(`canvas`,{ref:n,width:je,height:Me,style:{width:`100%`,height:`auto`,imageRendering:`pixelated`,display:`block`,background:I.bg,boxShadow:`0 0 40px rgba(99,102,241,0.15), inset 0 0 80px rgba(0,0,0,0.4)`}})})}var Qe=[`#f4d4b8`,`#e0a878`,`#c98a5b`,`#9c6b48`,`#7a5238`,`#5a3a26`,`#3d2818`,`#deba90`],$e=[`#dc2626`,`#2563eb`,`#16a34a`,`#7c3aed`,`#f59e0b`,`#0891b2`,`#db2777`,`#475569`],et=[`#1a1a1a`,`#3d2817`,`#6b4423`,`#8b6f47`,`#c9a55c`,`#e4d4a8`,`#6b7280`,`#000000`],tt=[{x:50,y:16},{x:78,y:28},{x:86,y:52},{x:78,y:76},{x:50,y:84},{x:22,y:76},{x:14,y:52},{x:22,y:28}];function nt({agents:e,council:t}){let[n,r]=(0,_.useState)(0);(0,_.useEffect)(()=>{if(!t||t.phase===`idle`||t.phase===`skipped`){r(0);return}let n=Math.min(e.length,tt.length),i=0;r(0);let a=()=>{i++,r(i),i<n&&setTimeout(a,250)},o=setTimeout(a,200);return()=>{clearTimeout(o)}},[t?.phase,e.length]);let[i,a]=(0,_.useState)(0);(0,_.useEffect)(()=>{if(!t||t.phase!==`r2`&&t.phase!==`r3`)return;let n=setInterval(()=>{a(t=>(t+1)%Math.max(1,e.length))},2e3);return()=>clearInterval(n)},[t?.phase,e.length]);let o=t?.phase===`r2`?`Round 2 — Cross-reading (${t.r2Nodes.length}/${e.length})`:t?.phase===`r3`?`Round 3 — HERALD mediazione`:t?.phase===`done`?`Sintesi completata · convergenza ${Math.round((t.r2Convergence||0)*100)}%`:`In attesa…`;return(0,A.jsxs)(`div`,{style:{width:`100%`,height:`100%`,background:`linear-gradient(180deg, #1a1530 0%, #0f0d22 100%)`,position:`relative`,overflow:`hidden`,color:`#fff`,fontFamily:`inherit`},children:[(0,A.jsxs)(`div`,{style:{position:`absolute`,top:8,left:12,right:12,display:`flex`,justifyContent:`space-between`,alignItems:`center`,fontSize:11,letterSpacing:`0.8px`,textTransform:`uppercase`,color:`#a78bfa`,zIndex:10},children:[(0,A.jsx)(`span`,{style:{fontWeight:800},children:`🏛️ Council Room`}),(0,A.jsx)(`span`,{style:{color:`#c4b5fd`},children:o})]}),(0,A.jsxs)(`svg`,{viewBox:`0 0 100 100`,preserveAspectRatio:`none`,style:{position:`absolute`,inset:0,width:`100%`,height:`100%`},children:[(0,A.jsxs)(`defs`,{children:[(0,A.jsxs)(`radialGradient`,{id:`tableGrad`,cx:`50%`,cy:`50%`,r:`50%`,children:[(0,A.jsx)(`stop`,{offset:`0%`,stopColor:`#5b3a8b`}),(0,A.jsx)(`stop`,{offset:`100%`,stopColor:`#2d1d54`})]}),(0,A.jsxs)(`radialGradient`,{id:`floorGrad`,cx:`50%`,cy:`100%`,r:`80%`,children:[(0,A.jsx)(`stop`,{offset:`0%`,stopColor:`#241a3a`}),(0,A.jsx)(`stop`,{offset:`100%`,stopColor:`#15102a`})]})]}),(0,A.jsx)(`rect`,{x:`0`,y:`0`,width:`100`,height:`100`,fill:`url(#floorGrad)`}),(0,A.jsx)(`ellipse`,{cx:`50`,cy:`52`,rx:`32`,ry:`20`,fill:`url(#tableGrad)`,stroke:`#7c3aed`,strokeWidth:`0.4`}),(0,A.jsx)(`ellipse`,{cx:`50`,cy:`49`,rx:`28`,ry:`17`,fill:`rgba(0,0,0,0.18)`}),(0,A.jsx)(`circle`,{cx:`50`,cy:`50`,r:`38`,fill:`none`,stroke:`rgba(167,139,250,0.12)`,strokeWidth:`0.3`})]}),e.slice(0,tt.length).map((e,r)=>{let a=tt[r],o=r<n,s=o&&r===i&&(t?.phase===`r2`||t?.phase===`r3`),c=$e[r%$e.length],l=Qe[r%Qe.length],u=et[r%et.length];return(0,A.jsxs)(`div`,{style:{position:`absolute`,left:`${a.x}%`,top:`${a.y}%`,transform:`translate(-50%, -50%) translateX(${o?0:a.x<50?`-80px`:`80px`})`,opacity:+!!o,transition:`transform 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.4s ease`,display:`flex`,flexDirection:`column`,alignItems:`center`,gap:2,pointerEvents:`none`,zIndex:5},children:[(0,A.jsxs)(`div`,{style:{fontSize:9,padding:`2px 6px`,background:`rgba(255,255,255,0.95)`,color:`#1e1b4b`,borderRadius:8,marginBottom:2,fontWeight:700,opacity:+!!s,transform:s?`translateY(0) scale(1)`:`translateY(4px) scale(0.85)`,transition:`opacity 0.25s, transform 0.25s`,whiteSpace:`nowrap`,maxWidth:110,overflow:`hidden`,textOverflow:`ellipsis`,boxShadow:`0 2px 6px rgba(0,0,0,0.4)`},children:[e.icon,` `,e.label.slice(0,12)]}),(0,A.jsxs)(`svg`,{width:`20`,height:`26`,viewBox:`0 0 20 26`,style:{filter:s?`drop-shadow(0 0 4px rgba(167,139,250,0.8))`:`none`,transition:`filter 0.25s`},children:[(0,A.jsx)(`rect`,{x:`6`,y:`2`,width:`8`,height:`3`,fill:u}),(0,A.jsx)(`rect`,{x:`6`,y:`4`,width:`8`,height:`6`,fill:l}),(0,A.jsx)(`rect`,{x:`8`,y:`7`,width:`1`,height:`1`,fill:`#000`}),(0,A.jsx)(`rect`,{x:`11`,y:`7`,width:`1`,height:`1`,fill:`#000`}),(0,A.jsx)(`rect`,{x:`5`,y:`10`,width:`10`,height:`9`,fill:c}),(0,A.jsx)(`rect`,{x:`9`,y:`10`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`3`,y:`11`,width:`2`,height:`7`,fill:c}),(0,A.jsx)(`rect`,{x:`15`,y:`11`,width:`2`,height:`7`,fill:c}),(0,A.jsx)(`rect`,{x:`3`,y:`18`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`15`,y:`18`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`6`,y:`19`,width:`3`,height:`4`,fill:`#1e293b`}),(0,A.jsx)(`rect`,{x:`11`,y:`19`,width:`3`,height:`4`,fill:`#1e293b`})]}),(0,A.jsx)(`div`,{style:{fontSize:9,color:`#c4b5fd`,fontWeight:600,maxWidth:80,textAlign:`center`,textShadow:`0 1px 2px rgba(0,0,0,0.6)`},children:e.label.slice(0,14)})]},e.agent+r)}),t?.phase===`done`&&(0,A.jsx)(`div`,{style:{position:`absolute`,bottom:10,left:0,right:0,textAlign:`center`,fontSize:10,color:`#c4b5fd`,letterSpacing:`0.5px`},children:t.converged?`✓ Consenso raggiunto`:`⚖ Mediazione completata`})]})}function rt(e){let t=e.toLowerCase();return De.some(e=>t.includes(e.toLowerCase()))}function it(e,t){let n=e=>e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`),r=t.map(e=>e.output).join(`
|
|
277
|
+
- Key risks: overfitting, regime change, crowding`,agents:[{icon:`📊`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`📈`,agent:`edi`,label:`Edi`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``},{icon:`🔥`,agent:`prometheus`,label:`Prometheus`,status:`waiting`,output:``}]},{label:`💼 Portfolio Health Check`,task:`You are a multi-asset risk manager and portfolio strategist. Produce a comprehensive weekly health check of the user's portfolio (the actual positions persisted in ~/.nha/portfolio.json). Use the dedicated tools:
|
|
278
|
+
- portfolio_summary: current positions, live P/L, cost basis, totals
|
|
279
|
+
- portfolio_metrics(period="1y"): annualized return, vol, Sharpe, Sortino, max drawdown, beta vs SPY
|
|
280
|
+
- portfolio_var(period="1y", confidence=0.95): daily VaR + Expected Shortfall + 10-day VaR
|
|
281
|
+
- portfolio_correlation(period="1y"): full correlation matrix + concentration pairs
|
|
282
|
+
- portfolio_sector_breakdown: % allocation by sector, country, currency
|
|
283
|
+
- portfolio_rebalance: current vs target weights drift + exact trades to bring back in line
|
|
284
|
+
- peer_comparison for the TOP 3 positions: side-by-side P/E, P/B, ROE, D/E, dividend yield
|
|
285
|
+
- news_sentiment for each position with weight > 10%: aggregate verdict + key headlines
|
|
286
|
+
- economic_calendar(country="US", days=7): macro events that could move the book
|
|
287
|
+
- market_news(query="Federal Reserve" OR "ECB"): central bank chatter
|
|
288
|
+
|
|
289
|
+
Then produce a structured report:
|
|
290
|
+
|
|
291
|
+
1. EXECUTIVE SUMMARY (3 bullets max) — overall health (green/yellow/red), biggest concentration risk, one concrete action this week.
|
|
292
|
+
2. PERFORMANCE & RISK — YTD vs SPY, Sharpe, max drawdown, beta. Flag Sharpe < 0.5 or beta > 1.3. VaR 95% in $ and %.
|
|
293
|
+
3. DIVERSIFICATION SCORECARD — sector concentration > 35%? unhedged FX > 20%? correlated pairs r > 0.7? effectively independent bets (1/sum(weight²)).
|
|
294
|
+
4. POSITION-BY-POSITION REVIEW — for each holding > 5%: fundamentals snapshot, technical signal, news sentiment, catalyst calendar, verdict HOLD/TRIM/ADD/EXIT with rationale.
|
|
295
|
+
5. REBALANCING PLAN — use portfolio_rebalance output. Sort by absolute drift. Flag tax implications if gain > 30%.
|
|
296
|
+
6. WEEK AHEAD WATCHLIST — macro events, earnings dates, technical levels to monitor.
|
|
297
|
+
7. ONE-LINE BOTTOM LINE — the sentence the user should remember in 30 seconds.
|
|
298
|
+
|
|
299
|
+
Quantitative. Numbers. No vague platitudes.`,agents:[{icon:`💼`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``},{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`📰`,agent:`herald`,label:`Herald`,status:`waiting`,output:``},{icon:`🔥`,agent:`prometheus`,label:`Prometheus`,status:`waiting`,output:``}]},{label:`📊 Earnings Preview — Week Ahead`,task:`You are an earnings strategist. For each ticker the user names (or the most-watched names of the week if none): pull earnings_calendar (date + EPS estimate + last 4 surprises), market_indicators (current valuation), peer_comparison (vs industry), options_chain (implied move = straddle / spot × 100), news_sentiment (pre-earnings tone), market_chart period="3mo" (technical setup, RSI, key levels).
|
|
300
|
+
|
|
301
|
+
Then produce:
|
|
302
|
+
1. CONSENSUS vs OPTIONS-IMPLIED MOVE — disagreement is the trade.
|
|
303
|
+
2. SETUP CATEGORIZATION — 🟢 HIGH-CONVICTION LONG (beats + cheap IV + bullish chart) | 🔴 SHORT INTO PRINT (deteriorating + expensive IV) | 🟡 STRADDLE (binary, no directional edge) | ⚪ SKIP.
|
|
304
|
+
3. SIZING & RISK — % of book, stop loss $ and %, hedging idea (peer offset, sector ETF, VIX call).
|
|
305
|
+
4. POST-EARNINGS PLAYBOOK — beats+raises: trail stop to BE at +5%. Misses+solid guidance: fade. Misses+lowers: short reversal. Beats+weak guidance: tactical short.
|
|
306
|
+
|
|
307
|
+
Specific prices. Specific levels.`,agents:[{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`💼`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`📰`,agent:`herald`,label:`Herald`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``}]},{label:`🔄 Sector Rotation Scan`,task:`You are a top-down sector strategist. Run a 4-week rotation scan.
|
|
308
|
+
|
|
309
|
+
Data: macro_data(indicator="all") for regime, stock_screener(day_gainers/day_losers, 20 each) for breadth, for each SPDR sector ETF (XLK, XLF, XLE, XLV, XLI, XLP, XLY, XLU, XLB, XLRE, XLC): market_chart period="3mo" + market_indicators. Plus market_news(query="sector rotation") and economic_calendar(country="US", days=14).
|
|
310
|
+
|
|
311
|
+
Report:
|
|
312
|
+
1. MACRO REGIME — cycle phase, risk-on/off, dollar regime, inflation surprise direction.
|
|
313
|
+
2. SECTOR LEADERSHIP TABLE — 11 sectors ranked by 1mo / 3mo / YTD performance, RSI, distance from 50d MA.
|
|
314
|
+
3. CROSS-SECTOR SIGNALS — cyclicals vs defensives (XLI+XLY+XLB vs XLP+XLV+XLU), banks vs gold (XLF vs GLD), energy vs tech (XLE vs XLK), discretionary vs staples (XLY vs XLP).
|
|
315
|
+
4. TACTICAL CALLS — 2 OVERWEIGHT + 2 UNDERWEIGHT with specific ETF + 1-3 leading names + entry zone + invalidation.
|
|
316
|
+
5. KEY RISK — the macro event that breaks the thesis + stop-loss conditions at the ETF level.`,agents:[{icon:`🌍`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`📊`,agent:`cartographer`,label:`Cartographer`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``}]},{label:`🇮🇹 Italian Market Daily Brief`,task:`Sei uno strategist del mercato italiano. Produci il brief giornaliero in italiano. Usa: italian_market(what="all"), macro_data(indicator="indices"), macro_data(indicator="commodities"), market_news(query="Borsa Italiana"), news_sentiment(query="MIB", limit=15), economic_calendar(country="IT", days=5), economic_calendar(country="EU", days=5), earnings_calendar per i top 5 MIB con earnings nei prossimi 14 giorni, peer_comparison per i 3 titoli più mossi.
|
|
317
|
+
|
|
318
|
+
Struttura:
|
|
319
|
+
1. APERTURA in 3 righe — FTSE MIB livello + delta %, tono, notizia dominante.
|
|
320
|
+
2. SETTORI MOTORI E FRENI — top 3 su / top 3 giù + ragione, performance vs DAX e CAC.
|
|
321
|
+
3. TITOLI DA SEGUIRE — 3 movimenti significativi (% + volume + news), 2 breakout/breakdown tecnici da 50d MA.
|
|
322
|
+
4. CONTESTO MACRO ITALIA — spread BTP-Bund, EUR/USD impatto su esportatrici (ENI, STM), BCE/Fed.
|
|
323
|
+
5. EVENTI SETTIMANA — earnings (UCG, ISP, ENI…), dati macro, decisioni politiche.
|
|
324
|
+
6. SENTIMENT — mood stampa, flussi, idea direzionale.
|
|
325
|
+
7. TRADE DEL GIORNO — 1 long + 1 short con entry, stop, target precisi. Sizing in % portafoglio.
|
|
326
|
+
|
|
327
|
+
Tutto quantitativo. Niente "il mercato è cauto" senza numeri.`,agents:[{icon:`🇮🇹`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`📰`,agent:`herald`,label:`Herald`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``}]}],De=`trading strategy.fundamental analysis.portfolio risk.sector deep dive.crypto analysis.quant factor.macro regime.cross-asset.hedge.derivative.yield curve.market_price.market_chart.market_indicators.macro_data.crypto_data.valuation.equity.earnings.dividend.short interest.volatility.options.futures.bitcoin.ethereum`.split(`.`),Oe=[`Analyze my unread emails and create a priority action plan`,`Search the web for AI news today and summarize it in a canvas report`,`Check my calendar for this week and suggest how to optimize my schedule`,`Review my GitHub notifications and draft responses to open issues`,`Search for information about a topic, fact-check it, and write a report`],I={bg:`#0a0d14`,wall:`#0d1117`,wallStripe:`#0f1520`,baseboard:`#1e293b`,floorA:`#111827`,floorB:`#141c2b`,floorLine:`#1e293b`,ceiling:`#0b0f18`,lampBody:`#854d0e`,lampGlow:`#fef08a`,partition:`#1a2436`,partitionH:`#253347`,partitionT:`#2d4a6e`,deskTop:`#2d4263`,deskFace:`#1e3352`,deskSide:`#192b43`,deskLeg:`#0f1e30`,monBody:`#0f172a`,monFace:`#162032`,monScrIdle:`#060b12`,monScrOn:`#001208`,monScrDone:`#001a08`,monStand:`#0d1625`,keyboard:`#162032`,chairBack:`#1a3a5c`,chairSeat:`#1e4068`,chairLeg:`#0f1e30`,plant1:`#14532d`,plant2:`#166534`,pot:`#78350f`,cup:`#7c2d12`,paper:`#e2e8f0`,paperLine:`#94a3b8`,skin:[`#f5c97a`,`#fbbf24`,`#fca5a5`,`#86efac`,`#a5f3fc`,`#c4b5fd`,`#fde68a`,`#f9a8d4`],shirt:[`#1e40af`,`#065f46`,`#312e81`,`#be123c`,`#164e63`,`#7c3aed`,`#7f1d1d`,`#0e7490`],hair:[`#7c3aed`,`#92400e`,`#1e293b`,`#6b21a8`,`#78350f`,`#292524`,`#422006`,`#0c4a6e`],pants:`#1e293b`,shoes:`#0f172a`,cSkin:`#fbbf24`,cShirt:`#4f46e5`,cHair:`#111827`,cTie:`#dc2626`,cPants:`#1e293b`,cShoes:`#0f172a`,cBag:`#78350f`,green:`#22c55e`,greenDim:`#14532d`,cyan:`#67e8f9`,purple:`#a78bfa`,red:`#ef4444`,amber:`#fbbf24`,dim:`#334155`,white:`#f1f5f9`,bubbleBg:`#0f172a`,bubbleBdr:`#334155`},L=2,ke=1200,Ae=140,je=ke*L,Me=Ae*L,Ne=Math.round(Ae*.78),Pe=130,Fe=14,Ie=Ne-Fe-2,Le=8,Re=36;function R(e,t,n,r,i=1,a=1){e.fillStyle=t,e.fillRect(n*L,r*L,i*L,a*L)}function ze(e){R(e,I.ceiling,0,0,ke,7);for(let t=0;t<ke;t++)R(e,t%20<10?I.wall:I.wallStripe,t,7,1,Ne-7);R(e,I.baseboard,0,Ne-3,ke,3);for(let t=0;t<ke;t+=14)for(let n=Ne;n<Ae;n+=7)R(e,(Math.floor(t/14)+Math.floor((n-Ne)/7))%2==0?I.floorA:I.floorB,t,n,14,7),R(e,I.floorLine,t,n,14,1),R(e,I.floorLine,t,n,1,7);for(let t=40;t<ke;t+=120){R(e,I.lampBody,t,1,40,4),R(e,I.lampGlow,t+2,2,36,2);for(let n=0;n<18;n++)e.fillStyle=`rgba(254,240,138,${(.06-n*.003).toFixed(4)})`,e.fillRect((t+20-n*1.1)*L,(5+n)*L,n*2.2*L,L)}}function Be(e,t,n,r){let i=t.x,a=t.status===`running`,o=t.status===`done`,s=t.status===`error`,c=Math.abs(n-(i+Pe/2))<30;a&&(e.fillStyle=`rgba(99,102,241,0.09)`,e.fillRect(i*L,10*L,Pe*L,(Ae-10)*L)),o&&(e.fillStyle=`rgba(34,197,94,0.05)`,e.fillRect(i*L,10*L,Pe*L,(Ae-10)*L));let l=Ne-8;R(e,I.partition,i,8,3,l),R(e,I.partitionH,i+1,8,2,l),R(e,I.partition,i+Pe,8,3,l),R(e,a?I.partitionT:o?`#1a3a2e`:s?`#3a1a1a`:I.partitionH,i,8,Pe+3,4),R(e,`#0f172a`,i+5,14,16,10),R(e,`#162032`,i+6,15,14,3),a?(R(e,I.green,i+7,19,11,1),R(e,I.greenDim,i+7,21,12,1)):o?(R(e,I.green,i+7,18,12,1),R(e,I.green,i+7,20,9,1),R(e,I.green,i+7,22,11,1)):(R(e,I.dim,i+7,19,10,1),R(e,I.dim,i+7,21,7,1));let u=i+Pe-14;R(e,I.pot,u+2,Ie+Fe-5,7,5),R(e,I.plant1,u,Ie+Fe-13,10,8),R(e,I.plant2,u+2,Ie+Fe-17,6,5),R(e,I.plant2,u-2,Ie+Fe-12,5,4),R(e,I.plant2,u+7,Ie+Fe-13,5,4),R(e,I.deskTop,i+4,Ie,Pe-6,3),R(e,I.deskFace,i+4,Ie+3,Pe-6,Fe-3),R(e,I.deskSide,i+4,Ie+Fe,Pe-6,3);let d=Ne-Ie-Fe-3;R(e,I.deskLeg,i+8,Ie+Fe+3,4,d),R(e,I.deskLeg,i+Pe-10,Ie+Fe+3,4,d);let f=i+14,p=Ie-18;if(R(e,I.monBody,f,p,22,14),R(e,I.monFace,f+1,p+1,20,12),R(e,a?I.monScrOn:o?I.monScrDone:I.monScrIdle,f+2,p+2,18,8),a)for(let t=0;t<3;t++){let n=3+Math.floor((r/90+t*5)%12);R(e,I.green,f+3,p+3+t*2,n,1),R(e,I.greenDim,f+3+n,p+3+t*2,12-n,1)}else o?(R(e,I.green,f+3,p+3,10,1),R(e,I.green,f+3,p+5,8,1),R(e,I.green,f+3,p+7,9,1),R(e,I.green,f+13,p+3,2,4)):s?(R(e,I.red,f+8,p+2,4,6),R(e,I.red,f+8,p+9,4,2)):(R(e,I.dim,f+3,p+3,12,1),R(e,I.dim,f+3,p+5,8,1),R(e,I.dim,f+3,p+7,10,1));R(e,I.monStand,f+8,p+14,4,3),R(e,I.monStand,f+5,p+17,10,1),R(e,I.keyboard,f-1,Ie+1,16,4);for(let t=0;t<5;t++)R(e,I.monFace,f+t*3,Ie+2,2,2);R(e,I.cup,i+7,Ie+2,5,5),R(e,`#92400e`,i+12,Ie+4,2,3),R(e,I.paper,i+52,Ie+2,12,7),R(e,I.paper,i+54,Ie+1,12,7),R(e,I.paperLine,i+56,Ie+3,7,1),R(e,I.paperLine,i+56,Ie+5,5,1),R(e,I.chairBack,i+Pe/2-12,Ne-20,24,3),R(e,I.chairSeat,i+Pe/2-13,Ne-14,26,8),R(e,I.chairLeg,i+Pe/2-8,Ne-6,4,6),R(e,I.chairLeg,i+Pe/2+5,Ne-6,4,6),Ve(e,t,i+Pe/2-8,Ne-Re,a,c,r);let m=a?I.cyan:o?I.green:s?I.red:I.dim;e.font=`bold ${4*L}px monospace`,e.fillStyle=m,e.textBaseline=`alphabetic`;let h=t.label.length>14?t.label.slice(0,13)+`…`:t.label;e.fillText(h,(i+4)*L,8*L),R(e,m,i+Pe-4,9,4,4),a&&Math.floor(r/250)%2==0&&R(e,I.white,i+Pe-3,10,2,2)}function Ve(e,t,n,r,i,a,o){let s=I.skin[t.skinIdx],c=I.shirt[t.shirtIdx],l=I.hair[t.hairIdx],u=i&&!a?Math.floor(o/700)%2:0;R(e,l,n+1,r+u,12,3),R(e,l,n,r+1+u,14,2),R(e,l,n,r+3+u,2,5),R(e,l,n+12,r+3+u,2,5),R(e,s,n+1,r+3+u,12,9);let d=l;R(e,d,n+2,r+4+u,3,1),R(e,d,n+9,r+4+u,3,1),R(e,`#e2e8f0`,n+2,r+5+u,3,3),R(e,`#e2e8f0`,n+9,r+5+u,3,3);let f=t.skinIdx%2==0?`#1d4ed8`:`#065f46`;if(R(e,f,n+3,r+6+u,2,2),R(e,f,n+10,r+6+u,2,2),R(e,`#0f172a`,n+3,r+7+u,1,1),R(e,`#0f172a`,n+10,r+7+u,1,1),R(e,`#c97a4a`,n+7,r+9+u,1,2),t.status===`done`)R(e,`#991b1b`,n+4,r+11+u,6,1),R(e,`#991b1b`,n+3,r+10+u,1,1),R(e,`#991b1b`,n+10,r+10+u,1,1);else if(t.status===`error`)R(e,`#991b1b`,n+4,r+10+u,6,1),R(e,`#991b1b`,n+3,r+11+u,1,1),R(e,`#991b1b`,n+10,r+11+u,1,1);else if(t.status===`running`){let t=Math.floor(o/250)%2;R(e,`#991b1b`,n+5,r+10+u,4,t?2:1)}else R(e,`#7f1d1d`,n+5,r+11+u,4,1);if(R(e,s,n+4,r+12,6,2),R(e,c,n+1,r+14,14,11),R(e,`#f1f5f9`,n+4,r+14,3,4),R(e,`#f1f5f9`,n+9,r+14,3,4),e.font=`${4*L}px serif`,e.textBaseline=`middle`,e.textAlign=`center`,e.fillText(t.icon,(n+8)*L,(r+19)*L),e.textAlign=`left`,i&&!a){let t=Math.floor(o/120)%2;R(e,c,n-2,r+14,3,9),R(e,s,n-2,r+22-t,3,3),R(e,c,n+15,r+14,3,9),R(e,s,n+15,r+22-(1-t),3,3)}else if(a&&i){let t=Math.floor(o/180)%3;R(e,c,n-2,r+14,3,8),R(e,s,n-2,r+22,3,3),R(e,c,n+15,r+8+t,3,9),R(e,s,n+15,r+5+t,3,4)}else R(e,c,n-2,r+14,3,9),R(e,s,n-2,r+23,3,2),R(e,c,n+15,r+14,3,9),R(e,s,n+15,r+23,3,2);R(e,I.pants,n+2,r+25,5,7),R(e,I.pants,n+9,r+25,5,7),R(e,`#2d3f5f`,n+3,r+29,3,1),R(e,`#2d3f5f`,n+10,r+29,3,1),R(e,I.shoes,n,r+32,7,4),R(e,I.shoes,n+9,r+32,7,4),R(e,`#1e2a3a`,n+1,r+32,5,1),R(e,`#1e2a3a`,n+10,r+32,5,1),e.fillStyle=`rgba(0,0,0,0.35)`,e.beginPath(),e.ellipse((n+8)*L,(Ne-1)*L,9*L,2*L,0,0,Math.PI*2),e.fill()}function He(e){e.papers.length>=6||e.papers.push({x:e.x+20+Math.random()*(Pe-40),y:Ie-4,vx:(Math.random()-.5)*1.5,vy:-1.5-Math.random()*1,rot:Math.random()*360,vrot:(Math.random()-.5)*10,life:0,maxLife:70+Math.random()*40})}function Ue(e){e.papers=e.papers.filter(e=>e.life<e.maxLife);for(let t of e.papers)t.x+=t.vx,t.y+=t.vy,t.vy+=.06,t.rot+=t.vrot,t.life++}function We(e,t){for(let n of t.papers){let t=1-n.life/n.maxLife;e.save(),e.globalAlpha=t,e.translate(n.x*L,n.y*L),e.rotate(n.rot*Math.PI/180),e.fillStyle=I.paper,e.fillRect(-7*L,-5*L,14*L,10*L),e.fillStyle=I.paperLine,e.fillRect(-5*L,-2*L,8*L,L),e.fillRect(-5*L,0,6*L,L),e.restore()}e.globalAlpha=1}var Ge=`ABCDEFGHIJKLMNOPRSTUVWXYZ0123456789!?-_.:`;function Ke(e,t){return t<=0?e:Ge[Math.floor(Math.random()*41)]??e}function qe(e,t,n,r,i,a=110,o=`down`){if(t.length===0)return;e.fillStyle=I.bubbleBg,e.fillRect(r*L,i*L,a*L,36*L),e.strokeStyle=I.bubbleBdr,e.lineWidth=L,e.strokeRect(r*L,i*L,a*L,36*L),e.fillStyle=I.cyan,e.fillRect(r*L,i*L,a*L,L),e.fillStyle=I.bubbleBdr,e.fillRect(r*L,(i+13+5)*L,a*L,L);for(let o=0;o<2;o++){let s=t[(Math.floor(n)+o)%t.length];if(!s)continue;let c=i+5+o*14;e.fillStyle=o%2==0?`#0f172a`:`#111827`,e.fillRect(r*L,c*L,a*L,13*L);let l=n-Math.floor(n),u=s.text.split(``);e.font=`bold ${5*L}px monospace`,e.textBaseline=`middle`;let d=Math.floor(a/7)-1;for(let t=0;t<Math.min(u.length,d);t++){let n=l>.3&&t>u.length*(1-l)?Ke(u[t],3):u[t];e.globalAlpha=l>0&&t>u.length*(1-l)?.5+l*.5:1,e.fillStyle=s.color,e.fillText(n,(r+4+t*7)*L,(c+6)*L)}e.globalAlpha=1}let s=r+a/2;if(e.fillStyle=I.bubbleBdr,o===`down`){let t=i+36;e.fillRect((s-2)*L,t*L,4*L,4*L),e.fillRect((s-3)*L,(t+4)*L,6*L,3*L)}else e.fillRect((s-2)*L,(i-4)*L,4*L,4*L),e.fillRect((s-3)*L,(i-7)*L,6*L,3*L)}function Je(e,t,n,r){let i=Ne-Re-8,a=n%2,o=a===0?2:0,s=a===0?0:2;e.fillStyle=`rgba(0,0,0,0.4)`,e.beginPath(),e.ellipse(t*L,(Ne-1)*L,9*L,2*L,0,0,Math.PI*2),e.fill(),e.save(),e.translate(t*L,0),e.scale(r?1:-1,1),R(e,I.cHair,-4,i,9,3),R(e,I.cHair,-5,i+1,11,2),R(e,I.cHair,-5,i+3,2,3),R(e,I.cHair,5,i+3,2,3),R(e,I.cSkin,-4,i+3,8,7),R(e,`#0f172a`,-2,i+5,2,2),R(e,`#0f172a`,2,i+5,2,2),R(e,`#1d4ed8`,-1,i+6,1,1),R(e,`#1d4ed8`,3,i+6,1,1),R(e,`#7f1d1d`,-2,i+9,5,1),R(e,I.cSkin,-1,i+10,3,2),R(e,I.cShirt,-5,i+12,11,9),R(e,`#3730a3`,-5,i+12,3,5),R(e,`#3730a3`,3,i+12,3,5),R(e,I.cTie,-1,i+12,3,7),R(e,`#f1f5f9`,-2,i+12,1,3),R(e,`#f1f5f9`,2,i+12,1,3),R(e,I.cShirt,-7,i+13,3,9),R(e,I.cSkin,-7,i+21,3,3),R(e,I.cShirt,5,i+13,3,9),R(e,I.cSkin,5,i+21,3,3),R(e,I.cBag,6,i+24,6,4),R(e,`#92400e`,7,i+23,4,1),R(e,I.cPants,-3,i+21+o,5,10),R(e,I.cPants,1,i+21+s,5,10),R(e,I.cShoes,-5,i+29+o,6,3),R(e,I.cShoes,-1,i+29+s,6,3),e.restore()}var Ye=[[{text:`RUNNING WORKFLOW`,color:I.cyan},{text:`DISPATCHING...`,color:I.amber}],[{text:`ORCHESTRATING`,color:I.cyan},{text:`ALL SYSTEMS GO`,color:I.green}],[{text:`AGENT PIPELINE`,color:I.purple},{text:`EXECUTING...`,color:I.cyan}],[{text:`MONITORING ALL`,color:I.amber},{text:`ON SCHEDULE`,color:I.green}]];function Xe(e,t){let n=e.toUpperCase().slice(0,14);return t===`running`?[{text:n,color:I.cyan},{text:`WORKING...`,color:I.green}]:t===`done`?[{text:n,color:I.green},{text:`TASK DONE`,color:I.green}]:t===`error`?[{text:n,color:I.red},{text:`ERR`,color:I.red}]:[{text:n,color:I.dim},{text:`STANDBY`,color:I.dim}]}function Ze({nodes:e,running:t}){let n=(0,_.useRef)(null),r=(0,_.useRef)(null);return(0,_.useEffect)(()=>{if(e.length===0)return;let t=Math.min(e.length,8)*(Pe+Le)-Le,n=Math.max(6,Math.round((ke-t)/2));if(!r.current)r.current={agents:e.slice(0,8).map((e,t)=>({x:n+t*(Pe+Le),icon:e.icon,label:e.label,status:e.status,skinIdx:t%I.skin.length,shirtIdx:t%I.shirt.length,hairIdx:t%I.hair.length,papers:[],bubbleLines:Xe(e.label,e.status),bubbleScroll:0,bubbleTimer:0})),condX:n+Pe/2,condTargetX:n+Pe/2,condFacing:!0,condFrame:0,condFrameTimer:0,condIdleTimer:0,condBubble:Ye[0],condBubbleScroll:0,condBubbleTimer:0,rafId:0};else{let t=r.current;for(let r=0;r<Math.min(e.length,8);r++){let i=e[r],a=t.agents[r];a?(a.status!==i.status&&(a.status=i.status,a.bubbleLines=Xe(i.label,i.status),a.bubbleScroll=0),a.icon=i.icon,a.label=i.label):t.agents.push({x:n+r*(Pe+Le),icon:i.icon,label:i.label,status:i.status,skinIdx:r%I.skin.length,shirtIdx:r%I.shirt.length,hairIdx:r%I.hair.length,papers:[],bubbleLines:Xe(i.label,i.status),bubbleScroll:0,bubbleTimer:0})}}},[e]),(0,_.useEffect)(()=>{let e=n.current;if(!e)return;let i=e.getContext(`2d`);if(!i||(i.imageSmoothingEnabled=!1,!r.current))return;let a=0,o=0;function s(e){let n=r.current,c=Math.min(e-a,50);a=e;let l=e,u=n.agents.find(e=>e.status===`running`);if(u){let e=u.x+Pe+12,t=u.x-18;n.condTargetX=e<ke-30?e:t,n.condBubbleTimer+=c,n.condBubbleTimer>2e3&&(n.condBubbleTimer=0,n.condBubble=Ye[Math.floor(Math.random()*Ye.length)],n.condBubbleScroll=(n.condBubbleScroll+1)%4)}else if(n.condIdleTimer-=c,n.condIdleTimer<=0){let e=n.agents[Math.floor(Math.random()*n.agents.length)];e&&(n.condTargetX=e.x+Pe/2),n.condIdleTimer=2e3+Math.random()*3e3}let d=n.condTargetX-n.condX;if(Math.abs(d)>1&&(n.condX+=d*(t?.12:.04)*(c/16),n.condFacing=d>0,n.condFrameTimer+=c,n.condFrameTimer>100&&(n.condFrame++,n.condFrameTimer=0)),o+=c,o>200){o=0;for(let e of n.agents)e.status===`running`&&He(e)}for(let e of n.agents)Ue(e);for(let e of n.agents)e.bubbleTimer+=c,e.bubbleTimer>1800&&(e.bubbleTimer=0,e.bubbleScroll=(e.bubbleScroll+1)%4);i.clearRect(0,0,je,Me),ze(i);for(let e of n.agents)Be(i,e,n.condX,l),We(i,e);Je(i,n.condX,n.condFrame,n.condFacing);for(let e of n.agents){if(e.status===`waiting`)continue;let t=Math.min(e.label.length*6+16,90),n=Math.max(e.x+1,Math.min(e.x+(Pe-t)/2,ke-t-2)),r=Ne-Re-36;qe(i,e.bubbleLines,e.bubbleScroll,n,r,t,`down`)}let f=Ne-Re-8-38,p=Math.max(4,Math.min(n.condX-50,ke-106));qe(i,n.condBubble,n.condBubbleScroll,p,f,100,`down`);for(let e=0;e<Me;e+=L*3)i.fillStyle=`rgba(0,0,0,0.04)`,i.fillRect(0,e,je,L);n.rafId=requestAnimationFrame(s)}return r.current.rafId=requestAnimationFrame(s),()=>{r.current&&cancelAnimationFrame(r.current.rafId)}},[t]),e.length===0?null:(0,A.jsx)(`div`,{style:{width:`100%`,lineHeight:0},children:(0,A.jsx)(`canvas`,{ref:n,width:je,height:Me,style:{width:`100%`,height:`auto`,imageRendering:`pixelated`,display:`block`,background:I.bg,boxShadow:`0 0 40px rgba(99,102,241,0.15), inset 0 0 80px rgba(0,0,0,0.4)`}})})}var Qe=[`#f4d4b8`,`#e0a878`,`#c98a5b`,`#9c6b48`,`#7a5238`,`#5a3a26`,`#3d2818`,`#deba90`],$e=[`#dc2626`,`#2563eb`,`#16a34a`,`#7c3aed`,`#f59e0b`,`#0891b2`,`#db2777`,`#475569`],et=[`#1a1a1a`,`#3d2817`,`#6b4423`,`#8b6f47`,`#c9a55c`,`#e4d4a8`,`#6b7280`,`#000000`],tt=[{x:50,y:16},{x:78,y:28},{x:86,y:52},{x:78,y:76},{x:50,y:84},{x:22,y:76},{x:14,y:52},{x:22,y:28}];function nt({agents:e,council:t}){let[n,r]=(0,_.useState)(0);(0,_.useEffect)(()=>{if(!t||t.phase===`idle`||t.phase===`skipped`){r(0);return}let n=Math.min(e.length,tt.length),i=0;r(0);let a=()=>{i++,r(i),i<n&&setTimeout(a,250)},o=setTimeout(a,200);return()=>{clearTimeout(o)}},[t?.phase,e.length]);let[i,a]=(0,_.useState)(0);(0,_.useEffect)(()=>{if(!t||t.phase!==`r2`&&t.phase!==`r3`)return;let n=setInterval(()=>{a(t=>(t+1)%Math.max(1,e.length))},2e3);return()=>clearInterval(n)},[t?.phase,e.length]);let o=t?.phase===`r2`?`Round 2 — Cross-reading (${t.r2Nodes.length}/${e.length})`:t?.phase===`r3`?`Round 3 — HERALD mediazione`:t?.phase===`done`?`Sintesi completata · convergenza ${Math.round((t.r2Convergence||0)*100)}%`:`In attesa…`;return(0,A.jsxs)(`div`,{style:{width:`100%`,height:`100%`,background:`linear-gradient(180deg, #1a1530 0%, #0f0d22 100%)`,position:`relative`,overflow:`hidden`,color:`#fff`,fontFamily:`inherit`},children:[(0,A.jsxs)(`div`,{style:{position:`absolute`,top:8,left:12,right:12,display:`flex`,justifyContent:`space-between`,alignItems:`center`,fontSize:11,letterSpacing:`0.8px`,textTransform:`uppercase`,color:`#a78bfa`,zIndex:10},children:[(0,A.jsx)(`span`,{style:{fontWeight:800},children:`🏛️ Council Room`}),(0,A.jsx)(`span`,{style:{color:`#c4b5fd`},children:o})]}),(0,A.jsxs)(`svg`,{viewBox:`0 0 100 100`,preserveAspectRatio:`none`,style:{position:`absolute`,inset:0,width:`100%`,height:`100%`},children:[(0,A.jsxs)(`defs`,{children:[(0,A.jsxs)(`radialGradient`,{id:`tableGrad`,cx:`50%`,cy:`50%`,r:`50%`,children:[(0,A.jsx)(`stop`,{offset:`0%`,stopColor:`#5b3a8b`}),(0,A.jsx)(`stop`,{offset:`100%`,stopColor:`#2d1d54`})]}),(0,A.jsxs)(`radialGradient`,{id:`floorGrad`,cx:`50%`,cy:`100%`,r:`80%`,children:[(0,A.jsx)(`stop`,{offset:`0%`,stopColor:`#241a3a`}),(0,A.jsx)(`stop`,{offset:`100%`,stopColor:`#15102a`})]})]}),(0,A.jsx)(`rect`,{x:`0`,y:`0`,width:`100`,height:`100`,fill:`url(#floorGrad)`}),(0,A.jsx)(`ellipse`,{cx:`50`,cy:`52`,rx:`32`,ry:`20`,fill:`url(#tableGrad)`,stroke:`#7c3aed`,strokeWidth:`0.4`}),(0,A.jsx)(`ellipse`,{cx:`50`,cy:`49`,rx:`28`,ry:`17`,fill:`rgba(0,0,0,0.18)`}),(0,A.jsx)(`circle`,{cx:`50`,cy:`50`,r:`38`,fill:`none`,stroke:`rgba(167,139,250,0.12)`,strokeWidth:`0.3`})]}),e.slice(0,tt.length).map((e,r)=>{let a=tt[r],o=r<n,s=o&&r===i&&(t?.phase===`r2`||t?.phase===`r3`),c=$e[r%$e.length],l=Qe[r%Qe.length],u=et[r%et.length];return(0,A.jsxs)(`div`,{style:{position:`absolute`,left:`${a.x}%`,top:`${a.y}%`,transform:`translate(-50%, -50%) translateX(${o?0:a.x<50?`-80px`:`80px`})`,opacity:+!!o,transition:`transform 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.4s ease`,display:`flex`,flexDirection:`column`,alignItems:`center`,gap:2,pointerEvents:`none`,zIndex:5},children:[(0,A.jsxs)(`div`,{style:{fontSize:9,padding:`2px 6px`,background:`rgba(255,255,255,0.95)`,color:`#1e1b4b`,borderRadius:8,marginBottom:2,fontWeight:700,opacity:+!!s,transform:s?`translateY(0) scale(1)`:`translateY(4px) scale(0.85)`,transition:`opacity 0.25s, transform 0.25s`,whiteSpace:`nowrap`,maxWidth:110,overflow:`hidden`,textOverflow:`ellipsis`,boxShadow:`0 2px 6px rgba(0,0,0,0.4)`},children:[e.icon,` `,e.label.slice(0,12)]}),(0,A.jsxs)(`svg`,{width:`20`,height:`26`,viewBox:`0 0 20 26`,style:{filter:s?`drop-shadow(0 0 4px rgba(167,139,250,0.8))`:`none`,transition:`filter 0.25s`},children:[(0,A.jsx)(`rect`,{x:`6`,y:`2`,width:`8`,height:`3`,fill:u}),(0,A.jsx)(`rect`,{x:`6`,y:`4`,width:`8`,height:`6`,fill:l}),(0,A.jsx)(`rect`,{x:`8`,y:`7`,width:`1`,height:`1`,fill:`#000`}),(0,A.jsx)(`rect`,{x:`11`,y:`7`,width:`1`,height:`1`,fill:`#000`}),(0,A.jsx)(`rect`,{x:`5`,y:`10`,width:`10`,height:`9`,fill:c}),(0,A.jsx)(`rect`,{x:`9`,y:`10`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`3`,y:`11`,width:`2`,height:`7`,fill:c}),(0,A.jsx)(`rect`,{x:`15`,y:`11`,width:`2`,height:`7`,fill:c}),(0,A.jsx)(`rect`,{x:`3`,y:`18`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`15`,y:`18`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`6`,y:`19`,width:`3`,height:`4`,fill:`#1e293b`}),(0,A.jsx)(`rect`,{x:`11`,y:`19`,width:`3`,height:`4`,fill:`#1e293b`})]}),(0,A.jsx)(`div`,{style:{fontSize:9,color:`#c4b5fd`,fontWeight:600,maxWidth:80,textAlign:`center`,textShadow:`0 1px 2px rgba(0,0,0,0.6)`},children:e.label.slice(0,14)})]},e.agent+r)}),t?.phase===`done`&&(0,A.jsx)(`div`,{style:{position:`absolute`,bottom:10,left:0,right:0,textAlign:`center`,fontSize:10,color:`#c4b5fd`,letterSpacing:`0.5px`},children:t.converged?`✓ Consenso raggiunto`:`⚖ Mediazione completata`})]})}function rt(e){let t=e.toLowerCase();return De.some(e=>t.includes(e.toLowerCase()))}function it(e,t){let n=e=>e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`),r=t.map(e=>e.output).join(`
|
|
278
328
|
|
|
279
329
|
`),i=/([+-]?\d+(?:\.\d+)?)\s*%/g,a=[],o;for(;(o=i.exec(r))!==null;){let e=parseFloat(o[1]);e>=-100&&e<=500&&a.push(e)}let s=t.filter(e=>e.output&&e.output!==`(no output)`).map(e=>{let t=xe(e.output);return`
|
|
280
330
|
<div class="section">
|
package/src/ui-dist/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
9
9
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10
10
|
<title>NHA — NotHumanAllowed</title>
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-DKPyRmuw.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-IQn8QiFW.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|