horizon-code 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/assets/python/highlights.scm +137 -0
  2. package/assets/python/tree-sitter-python.wasm +0 -0
  3. package/bin/horizon.js +2 -0
  4. package/package.json +40 -0
  5. package/src/ai/client.ts +369 -0
  6. package/src/ai/system-prompt.ts +86 -0
  7. package/src/app.ts +1454 -0
  8. package/src/chat/messages.ts +48 -0
  9. package/src/chat/renderer.ts +243 -0
  10. package/src/chat/types.ts +18 -0
  11. package/src/components/code-panel.ts +329 -0
  12. package/src/components/footer.ts +72 -0
  13. package/src/components/hooks-panel.ts +224 -0
  14. package/src/components/input-bar.ts +193 -0
  15. package/src/components/mode-bar.ts +245 -0
  16. package/src/components/session-panel.ts +294 -0
  17. package/src/components/settings-panel.ts +372 -0
  18. package/src/components/splash.ts +156 -0
  19. package/src/components/strategy-panel.ts +489 -0
  20. package/src/components/tab-bar.ts +112 -0
  21. package/src/components/tutorial-panel.ts +680 -0
  22. package/src/components/widgets/progress-bar.ts +38 -0
  23. package/src/components/widgets/sparkline.ts +57 -0
  24. package/src/hooks/executor.ts +109 -0
  25. package/src/index.ts +22 -0
  26. package/src/keys/handler.ts +198 -0
  27. package/src/platform/auth.ts +36 -0
  28. package/src/platform/client.ts +159 -0
  29. package/src/platform/config.ts +121 -0
  30. package/src/platform/session-sync.ts +158 -0
  31. package/src/platform/supabase.ts +376 -0
  32. package/src/platform/sync.ts +149 -0
  33. package/src/platform/tiers.ts +103 -0
  34. package/src/platform/tools.ts +163 -0
  35. package/src/platform/types.ts +86 -0
  36. package/src/platform/usage.ts +224 -0
  37. package/src/research/apis.ts +367 -0
  38. package/src/research/tools.ts +205 -0
  39. package/src/research/widgets.ts +523 -0
  40. package/src/state/store.ts +256 -0
  41. package/src/state/types.ts +109 -0
  42. package/src/strategy/ascii-chart.ts +74 -0
  43. package/src/strategy/code-stream.ts +146 -0
  44. package/src/strategy/dashboard.ts +140 -0
  45. package/src/strategy/persistence.ts +82 -0
  46. package/src/strategy/prompts.ts +626 -0
  47. package/src/strategy/sandbox.ts +137 -0
  48. package/src/strategy/tools.ts +764 -0
  49. package/src/strategy/validator.ts +216 -0
  50. package/src/strategy/widgets.ts +270 -0
  51. package/src/syntax/setup.ts +54 -0
  52. package/src/theme/colors.ts +107 -0
  53. package/src/theme/icons.ts +27 -0
  54. package/src/util/hyperlink.ts +21 -0
@@ -0,0 +1,140 @@
1
+ // Dashboard spawner — local HTTP server for live strategy monitoring
2
+ // Serves a self-contained HTML+CSS dashboard with Chart.js
3
+
4
+ import { platform } from "../platform/client.ts";
5
+
6
+ const DASHBOARD_HTML = (strategyId: string) => `<!DOCTYPE html>
7
+ <html lang="en">
8
+ <head>
9
+ <meta charset="utf-8">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1">
11
+ <title>Horizon — Strategy Dashboard</title>
12
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
13
+ <style>
14
+ * { margin: 0; padding: 0; box-sizing: border-box; }
15
+ body { font-family: 'SF Mono', 'Fira Code', monospace; background: #1a1a1a; color: #e0e0e0; }
16
+ .header { padding: 16px 24px; border-bottom: 1px solid #333; display: flex; align-items: center; gap: 12px; }
17
+ .header h1 { font-size: 14px; color: #fab283; letter-spacing: 2px; }
18
+ .header .status { font-size: 12px; color: #7fd88f; }
19
+ .grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; padding: 16px 24px; }
20
+ .metric { background: #212121; border: 1px solid #333; border-radius: 8px; padding: 12px 16px; }
21
+ .metric .label { font-size: 10px; color: #888; text-transform: uppercase; letter-spacing: 1px; }
22
+ .metric .value { font-size: 20px; font-weight: bold; margin-top: 4px; }
23
+ .metric .value.green { color: #7fd88f; }
24
+ .metric .value.red { color: #e06c75; }
25
+ .charts { display: grid; grid-template-columns: 2fr 1fr; gap: 12px; padding: 0 24px 16px; }
26
+ .chart-box { background: #212121; border: 1px solid #333; border-radius: 8px; padding: 16px; }
27
+ .chart-box h3 { font-size: 11px; color: #888; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 12px; }
28
+ .logs { padding: 0 24px 16px; }
29
+ .logs-box { background: #212121; border: 1px solid #333; border-radius: 8px; padding: 16px; max-height: 200px; overflow-y: auto; }
30
+ .logs-box h3 { font-size: 11px; color: #888; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px; }
31
+ .log-line { font-size: 11px; color: #aaa; padding: 2px 0; font-family: monospace; }
32
+ </style>
33
+ </head>
34
+ <body>
35
+ <div class="header">
36
+ <h1>H O R I Z O N</h1>
37
+ <span class="status" id="status">● connecting...</span>
38
+ </div>
39
+ <div class="grid" id="metrics"></div>
40
+ <div class="charts">
41
+ <div class="chart-box"><h3>Equity Curve</h3><canvas id="equity"></canvas></div>
42
+ <div class="chart-box"><h3>Positions</h3><div id="positions"></div></div>
43
+ </div>
44
+ <div class="logs"><div class="logs-box"><h3>Logs</h3><div id="logs"></div></div></div>
45
+ <script>
46
+ const SID = "${strategyId.replace(/[^a-zA-Z0-9-]/g, "")}";
47
+ let equityChart;
48
+ async function refresh() {
49
+ try {
50
+ const [mRes, lRes] = await Promise.all([
51
+ fetch("/api/metrics").then(r => r.json()),
52
+ fetch("/api/logs").then(r => r.json()),
53
+ ]);
54
+ document.getElementById("status").textContent = "● live";
55
+ document.getElementById("status").style.color = "#7fd88f";
56
+ const m = mRes.latest || {};
57
+ const grid = document.getElementById("metrics");
58
+ grid.innerHTML = [
59
+ ["Total P&L", m.total_pnl, m.total_pnl >= 0],
60
+ ["Win Rate", ((m.win_rate||0)*100).toFixed(1)+"%", (m.win_rate||0) >= 0.5],
61
+ ["Trades", m.total_trades, true],
62
+ ["Max DD", ((m.max_drawdown_pct||0)).toFixed(1)+"%", false],
63
+ ].map(([l,v,g]) => '<div class="metric"><div class="label">'+l+'</div><div class="value '+(g?"green":"red")+'">'+v+'</div></div>').join("");
64
+ // Equity chart
65
+ const hist = (mRes.pnl_history||[]).map(h => h.total_pnl);
66
+ if (equityChart) { equityChart.data.labels = hist.map((_,i)=>i); equityChart.data.datasets[0].data = hist; equityChart.update(); }
67
+ else { equityChart = new Chart(document.getElementById("equity"), { type: "line", data: { labels: hist.map((_,i)=>i), datasets: [{ data: hist, borderColor: "#fab283", borderWidth: 1.5, fill: false, pointRadius: 0 }] }, options: { responsive: true, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { ticks: { color: "#888" }, grid: { color: "#333" } } } } }); }
68
+ // Positions
69
+ const pos = m.positions || [];
70
+ document.getElementById("positions").innerHTML = pos.length === 0 ? '<div style="color:#888;font-size:12px">No positions</div>' : pos.map(p => '<div style="font-size:12px;padding:4px 0;border-bottom:1px solid #333"><span style="color:'+(p.side==="BUY"?"#7fd88f":"#e06c75")+'">'+p.side+'</span> '+p.slug+' <span style="color:#888">'+p.size+'@'+p.avg_entry_price.toFixed(2)+'</span></div>').join("");
71
+ // Logs
72
+ const logs = lRes.logs || lRes || [];
73
+ document.getElementById("logs").innerHTML = (Array.isArray(logs) ? logs : []).slice(-20).map(l => '<div class="log-line">'+(l.message||l.text||JSON.stringify(l))+'</div>').join("");
74
+ } catch { document.getElementById("status").textContent = "● disconnected"; document.getElementById("status").style.color = "#e06c75"; }
75
+ }
76
+ refresh();
77
+ setInterval(refresh, 3000);
78
+ </script>
79
+ </body>
80
+ </html>`;
81
+
82
+ export class DashboardServer {
83
+ private server: ReturnType<typeof Bun.serve> | null = null;
84
+ private _port = 0;
85
+ private _strategyId = "";
86
+
87
+ get port(): number { return this._port; }
88
+ get url(): string { return `http://localhost:${this._port}`; }
89
+ get running(): boolean { return this.server !== null; }
90
+
91
+ start(strategyId: string, port = 0): string {
92
+ if (this.server) this.stop();
93
+
94
+ this._strategyId = strategyId;
95
+
96
+ this.server = Bun.serve({
97
+ port: port || 0,
98
+ hostname: "127.0.0.1", // localhost only — not accessible from network
99
+ fetch: async (req) => {
100
+ const url = new URL(req.url);
101
+
102
+ if (url.pathname === "/api/metrics") {
103
+ try {
104
+ const data = await platform.getMetrics(strategyId, 20);
105
+ return Response.json(data);
106
+ } catch (err) {
107
+ return Response.json({ error: String(err) }, { status: 500 });
108
+ }
109
+ }
110
+
111
+ if (url.pathname === "/api/logs") {
112
+ try {
113
+ const data = await platform.getLogs(strategyId, 50);
114
+ return Response.json(data);
115
+ } catch (err) {
116
+ return Response.json({ error: String(err) }, { status: 500 });
117
+ }
118
+ }
119
+
120
+ // Serve dashboard HTML
121
+ return new Response(DASHBOARD_HTML(strategyId), {
122
+ headers: { "Content-Type": "text/html" },
123
+ });
124
+ },
125
+ });
126
+
127
+ this._port = this.server.port ?? 0;
128
+ return this.url;
129
+ }
130
+
131
+ stop(): void {
132
+ if (this.server) {
133
+ this.server.stop();
134
+ this.server = null;
135
+ this._port = 0;
136
+ }
137
+ }
138
+ }
139
+
140
+ export const dashboard = new DashboardServer();
@@ -0,0 +1,82 @@
1
+ // Strategy file persistence — saves to ~/.horizon/strategies/
2
+ // Each strategy is a single .py file on disk
3
+
4
+ import { mkdir, readFile, writeFile, readdir, unlink } from "fs/promises";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+
8
+ const STRATEGIES_DIR = join(homedir(), ".horizon", "strategies");
9
+
10
+ async function ensureDir(): Promise<void> {
11
+ await mkdir(STRATEGIES_DIR, { recursive: true });
12
+ }
13
+
14
+ function slugify(name: string): string {
15
+ return name
16
+ .replace(/([A-Z])/g, "_$1")
17
+ .toLowerCase()
18
+ .replace(/^_/, "")
19
+ .replace(/[^a-z0-9_]/g, "_")
20
+ .replace(/_+/g, "_");
21
+ }
22
+
23
+ /** Save strategy code to ~/.horizon/strategies/<name>.py */
24
+ export async function saveStrategy(name: string, code: string): Promise<string> {
25
+ await ensureDir();
26
+ const filename = `${slugify(name)}.py`;
27
+ const filepath = join(STRATEGIES_DIR, filename);
28
+ await writeFile(filepath, code, "utf-8");
29
+ return filepath;
30
+ }
31
+
32
+ /** Load strategy code from ~/.horizon/strategies/<name>.py */
33
+ export async function loadStrategy(name: string): Promise<{ code: string; path: string } | null> {
34
+ await ensureDir();
35
+ const filename = `${slugify(name)}.py`;
36
+ const filepath = join(STRATEGIES_DIR, filename);
37
+ try {
38
+ const code = await readFile(filepath, "utf-8");
39
+ return { code, path: filepath };
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /** List all saved strategies */
46
+ export async function listSavedStrategies(): Promise<{ name: string; path: string; modified: number }[]> {
47
+ await ensureDir();
48
+ const files = await readdir(STRATEGIES_DIR);
49
+ const strategies: { name: string; path: string; modified: number }[] = [];
50
+ for (const f of files) {
51
+ if (!f.endsWith(".py")) continue;
52
+ const filepath = join(STRATEGIES_DIR, f);
53
+ try {
54
+ const stat = await Bun.file(filepath).stat();
55
+ strategies.push({
56
+ name: f.replace(".py", ""),
57
+ path: filepath,
58
+ modified: stat?.mtime?.getTime() ?? 0,
59
+ });
60
+ } catch {
61
+ strategies.push({ name: f.replace(".py", ""), path: filepath, modified: 0 });
62
+ }
63
+ }
64
+ return strategies.sort((a, b) => b.modified - a.modified);
65
+ }
66
+
67
+ /** Delete a saved strategy */
68
+ export async function deleteStrategy(name: string): Promise<boolean> {
69
+ const filename = `${slugify(name)}.py`;
70
+ const filepath = join(STRATEGIES_DIR, filename);
71
+ try {
72
+ await unlink(filepath);
73
+ return true;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /** Get the strategies directory path */
80
+ export function getStrategiesDir(): string {
81
+ return STRATEGIES_DIR;
82
+ }