the universal sandbox runtime for agents and humans.
pocketenv.io
sandbox
openclaw
agent
claude-code
vercel-sandbox
deno-sandbox
cloudflare-sandbox
atproto
sprites
daytona
1import chalk from "chalk";
2import { execSync } from "child_process";
3import * as fs from "fs";
4
5function detectLightTerminal(): boolean {
6 // VS Code terminal
7 const vscodeTheme = process.env.VSCODE_THEME_KIND;
8 if (vscodeTheme) {
9 return vscodeTheme === "vscode-light" || vscodeTheme === "vscode-high-contrast-light";
10 }
11
12 // COLORFGBG — set by xterm, iTerm2, etc. ("fg;bg", bg >= 8 = light)
13 const colorfgbg = process.env.COLORFGBG;
14 if (colorfgbg) {
15 const parts = colorfgbg.split(";");
16 const bg = parseInt(parts[parts.length - 1] ?? "", 10);
17 if (!isNaN(bg)) return bg >= 8;
18 }
19
20 // OSC 11 background color query — works with Apple Terminal, iTerm2, etc.
21 // stty is redirected from /dev/tty explicitly because execSync pipes stdio,
22 // which means stty would otherwise fail to find the terminal.
23 if (process.stdout.isTTY) {
24 try {
25 const savedState = execSync("stty -g </dev/tty 2>/dev/null", { encoding: "utf8" }).trim();
26 // If we couldn't save the state, skip to avoid leaving the terminal in raw mode.
27 if (!savedState) return false;
28 const tty = fs.openSync("/dev/tty", "r+");
29 try {
30 // Use -icanon -echo instead of raw: avoids disabling ISIG (Ctrl+C) so
31 // signal handling stays intact even if the restore below fails.
32 execSync("stty -icanon -echo min 0 time 2 </dev/tty 2>/dev/null");
33 fs.writeSync(tty, "\x1b]11;?\x07");
34 // Read in a loop until we see the response terminator (BEL or ST),
35 // so leftover bytes don't leak into the terminal input buffer.
36 let resp = "";
37 const buf = Buffer.alloc(64);
38 for (let i = 0; i < 16; i++) {
39 const n = fs.readSync(tty, buf, 0, 64, null);
40 if (n === 0) break;
41 resp += buf.slice(0, n).toString();
42 if (resp.includes("\x07") || resp.includes("\x1b\\")) break;
43 }
44 const m = resp.match(/rgb:([0-9a-f]+)\/([0-9a-f]+)\/([0-9a-f]+)/i);
45 if (m?.[1] && m[2] && m[3]) {
46 // Components can be 2 or 4 hex digits; normalize to 0-255
47 const norm = (h: string) => parseInt(h.slice(0, 2), 16);
48 const r = norm(m[1]), g = norm(m[2]), b = norm(m[3]);
49 return 0.299 * r + 0.587 * g + 0.114 * b > 127;
50 }
51 } finally {
52 try { fs.closeSync(tty); } catch {}
53 try { execSync(`stty ${savedState} </dev/tty 2>/dev/null`); } catch {}
54 }
55 } catch {}
56 }
57
58 return false;
59}
60
61const isLightTerminal = detectLightTerminal();
62
63export const c = {
64 primary: (s: string | number) => chalk.rgb(0, 232, 198)(s),
65 secondary: (s: string | number) => chalk.rgb(0, 198, 232)(s),
66 accent: (s: string | number) => chalk.rgb(130, 100, 255)(s),
67 highlight: (s: string | number) => chalk.rgb(100, 232, 130)(s),
68 muted: (s: string | number) => isLightTerminal ? chalk.black(s) : chalk.rgb(200, 210, 220)(s),
69 link: (s: string | number) => chalk.rgb(255, 160, 100)(s),
70 sky: (s: string | number) => chalk.rgb(0, 210, 255)(s),
71 error: (s: string | number) => chalk.rgb(255, 100, 100)(s),
72};