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,137 @@
1
+ // Sandbox for isolated Python execution
2
+ // Runs strategy code in a temp directory with stripped environment
3
+
4
+ import { mkdtemp, writeFile, rm } from "fs/promises";
5
+ import { join } from "path";
6
+ import { tmpdir } from "os";
7
+ import { mkdirSync as mkdirSyncFs, existsSync } from "fs";
8
+ import { listEncryptedEnvNames, getDecryptedEnv } from "../platform/config.ts";
9
+
10
+ interface SandboxResult {
11
+ stdout: string;
12
+ stderr: string;
13
+ exitCode: number | null;
14
+ timedOut: boolean;
15
+ }
16
+
17
+ interface SandboxOptions {
18
+ code: string;
19
+ timeout?: number; // ms, default 30000
20
+ cwd?: string; // override temp dir
21
+ }
22
+
23
+ // Minimal env for backtesting — NO secrets, NO exchange keys
24
+ // Used by runInSandbox (backtest, validation)
25
+ function backtestEnv(): Record<string, string> {
26
+ const env: Record<string, string> = {};
27
+ const allow = ["PATH", "HOME", "LANG", "LC_ALL", "TERM", "PYTHONPATH", "VIRTUAL_ENV", "CONDA_PREFIX"];
28
+ for (const key of allow) {
29
+ if (process.env[key]) env[key] = process.env[key]!;
30
+ }
31
+ env.PYTHONUNBUFFERED = "1";
32
+ env.PYTHONDONTWRITEBYTECODE = "1";
33
+ return env;
34
+ }
35
+
36
+ // Full env for live/paper execution — includes exchange keys
37
+ // Used by spawnInSandbox (run_strategy) — user has explicitly chosen to run
38
+ function liveEnv(): Record<string, string> {
39
+ const env = backtestEnv();
40
+ // Add SDK keys needed for hz.run() to connect to exchanges
41
+ const secrets = ["HORIZON_API_KEY", "HORIZON_PRIVATE_KEY", "POLYMARKET_PRIVATE_KEY", "PK", "CLOB_API_KEY", "CLOB_SECRET", "CLOB_PASSPHRASE"];
42
+ for (const key of secrets) {
43
+ if (process.env[key]) env[key] = process.env[key]!;
44
+ }
45
+ // Pass HORIZON_ prefixed vars
46
+ for (const [key, val] of Object.entries(process.env)) {
47
+ if (key.startsWith("HORIZON_") && val && !env[key]) env[key] = val;
48
+ }
49
+ // Inject encrypted env vars from /env command
50
+ try {
51
+ for (const name of listEncryptedEnvNames()) {
52
+ if (!env[name]) {
53
+ const val = getDecryptedEnv(name);
54
+ if (val) env[name] = val;
55
+ }
56
+ }
57
+ } catch {}
58
+ return env;
59
+ }
60
+
61
+ /**
62
+ * Run Python code in an isolated sandbox.
63
+ * - Creates a temp directory
64
+ * - Writes code to a file (avoids shell escaping issues with -c)
65
+ * - Runs with stripped environment
66
+ * - Enforces timeout
67
+ * - Cleans up temp directory
68
+ */
69
+ export async function runInSandbox(opts: SandboxOptions): Promise<SandboxResult> {
70
+ const timeout = opts.timeout ?? 30000;
71
+ const dir = opts.cwd ?? await mkdtemp(join(tmpdir(), "horizon-sandbox-"));
72
+ const scriptPath = join(dir, "strategy.py");
73
+
74
+ try {
75
+ await writeFile(scriptPath, opts.code, "utf-8");
76
+
77
+ const pythonPath = process.env.HORIZON_PYTHON ?? process.env.PYTHON_PATH ?? "python3";
78
+ const proc = Bun.spawn([pythonPath, "-u", scriptPath], {
79
+ stdout: "pipe",
80
+ stderr: "pipe",
81
+ cwd: dir,
82
+ env: backtestEnv(), // No secrets — backtesting doesn't need exchange keys
83
+ });
84
+
85
+ let timedOut = false;
86
+ const timer = setTimeout(() => {
87
+ timedOut = true;
88
+ proc.kill();
89
+ }, timeout);
90
+
91
+ const stdout = await new Response(proc.stdout).text();
92
+ const stderr = await new Response(proc.stderr).text();
93
+ await proc.exited;
94
+ clearTimeout(timer);
95
+
96
+ return { stdout, stderr, exitCode: proc.exitCode, timedOut };
97
+ } finally {
98
+ // Clean up temp directory
99
+ if (!opts.cwd) {
100
+ rm(dir, { recursive: true, force: true }).catch(() => {});
101
+ }
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Run Python code as a background process in sandbox.
107
+ * Returns the process and temp dir for cleanup.
108
+ */
109
+ export function spawnInSandbox(code: string, dir?: string): {
110
+ proc: ReturnType<typeof Bun.spawn>;
111
+ dir: string;
112
+ cleanup: () => Promise<void>;
113
+ } {
114
+ const sandboxDir = dir ?? join(tmpdir(), `horizon-sandbox-${Date.now()}`);
115
+ if (!existsSync(sandboxDir)) mkdirSyncFs(sandboxDir, { recursive: true });
116
+ const scriptPath = join(sandboxDir, "strategy.py");
117
+
118
+ // Sync write since we need it before spawn
119
+ Bun.write(scriptPath, code);
120
+
121
+ const pythonPath = process.env.HORIZON_PYTHON ?? process.env.PYTHON_PATH ?? "python3";
122
+ const proc = Bun.spawn([pythonPath, "-u", scriptPath], {
123
+ stdout: "pipe",
124
+ stderr: "pipe",
125
+ cwd: sandboxDir,
126
+ env: liveEnv(), // Live execution — includes exchange keys for hz.run()
127
+ });
128
+
129
+ return {
130
+ proc,
131
+ dir: sandboxDir,
132
+ cleanup: async () => {
133
+ try { proc.kill(); } catch {}
134
+ rm(sandboxDir, { recursive: true, force: true }).catch(() => {});
135
+ },
136
+ };
137
+ }