File size: 4,822 Bytes
20f108c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Phantom β€” shared visual components
// ANSI colors, ASCII art, banners, prompts, separators
// Neon violet/cyan on dark β€” hacker-in-hoodie aesthetic

// ── ANSI helpers (local β€” no dependency on phantom.mjs) ──
function fg(name) {
  const colors = { black:30, red:31, green:32, yellow:33, blue:34, magenta:35, cyan:36, white:37, dim:2 };
  const n = colors[name] ?? 37;
  return `\x1b[${n}m`;
}
const _R = "\x1b[0m";
const _B = "\x1b[1m";
const _D = "\x1b[2m";

// ── Hooded figure logo (cyberpunk neon) ─────────────────
export function renderLogo(opts = {}) {
  const {
    mode = "",  // "non-interactive", "v0.2.0"
    tools = 0,
    wide = false,
  } = opts;

  const M = fg("magenta");
  const C = fg("cyan");
  const G = fg("green");
  const Y = fg("yellow");
  const dim = fg("dim");
  const _r = _R;
  const b = _B;
  const d = _D;

  // Shadow figlet logo β€” no box-drawing borders, clean
  const lines = [
    `  ${M}${b} _ \\  |   |    \\     \\  |__ __| _ \\   \\  |${_r}`,
    `  ${M}${b} |   | |   |   _ \\     \\ |   |  |   | |\\/ |${_r}`,
    `  ${M}${b} ___/  ___ |  ___ \\  |\\  |   |  |   | |   |${_r}`,
    `  ${M}${b} _|    _|  _|_/    _\\_| \\_|  _| \\___/ _|  _|${_r}`,
    `  ${C}${b}P${_r} ${M}${b}H${_r} ${Y}${b}A${_r} ${C}${b}N${_r} ${M}${b}T${_r} ${Y}${b}O${_r} ${C}${b}M${_r}`,
  ];
  if (tools) lines.push(`  ${dim}${tools} tools${_r}`);
  return lines.join("\n");
}

// ── Simple banner (small terminals / DesktopUI) ──
export function renderBanner(text = "PHANTOM", subtitle = "") {
  const C = fg("cyan");
  const dim = fg("dim");
  const _r = _R;
  const b = _B;
  return `${C}${b}${text}${_r}${subtitle ? ` ${dim}${subtitle}${_r}` : ""}`;
}

// ── Prompt symbols ────────────────────────────────
export const prompt = {
  ghost:  `${fg("green")}πŸ‘»${_R} `,
  bolt:   `${fg("cyan")}⚑${_R} `,
  hash:   `${fg("magenta")}#${_R} `,
  arrow:  `${fg("green")}β†’${_R} `,
  dim:    `${fg("dim")}β§©${_R} `,
};

// ── Separator line ──────────────────────────────────
export function separator(char = "─", len = 50) {
  return `${fg("dim")}${char.repeat(len)}${_R}`;
}

// ── Status icons ────────────────────────────────────
// ── Animated spinner ──────────────────────────────────
// Minimal frame-based spinner; no external deps.
export function createSpinner(startTime, opts = {}) {
  const frames = ['β ‹', 'β ™', 'β Ή', 'β Έ', 'β Ό', 'β ΄', 'β ¦', 'β §', 'β ‡', '⠏'];
  let i = 0, timer = null;
  const write = opts.write || ((s) => process.stdout.write(s));
  const fmt = (ts) => {
    const s = Math.floor((Date.now() - ts) / 1000);
    return s > 3600 ? `${Math.floor(s/3600)}h ${Math.floor((s%3600)/60)}m`
         : s > 60   ? `${Math.floor(s/60)}m ${s%60}s`
         :            `${s}s`;
  };
  return {
    start(msg) {
      if (timer) return;
      timer = setInterval(() => {
        const f = frames[i = (i + 1) % frames.length];
        write(`\r${fg("cyan")}${_B}${f}${_R} [${fmt(startTime)}] ${msg}`);
      }, 80);
    },
    stop() {
      if (!timer) return;
      clearInterval(timer); timer = null;
      write('\r\x1b[K');
    },
    update(msg) {
      const f = timer ? frames[i] : frames[0];
      write(`\r${fg("cyan")}${_B}${f}${_R} [${fmt(startTime)}] ${msg}`);
    }
  };
}
    
export const icons = {
  ok:     `${fg("green")}βœ“${_R}`,
  err:    `${fg("red")}βœ•${_R}`,
  warn:   `${fg("yellow")}⚠${_R}`,
  info:   `${fg("cyan")}β—ˆ${_R}`,
  bullet: `${fg("magenta")}β—†${_R}`,
  arrow:  `${fg("green")}β†’${_R}`,
};

// ── Chat border β€” wraps multi-line text in a bordered box ──
// width  = total width (defaults to terminal cols - 2)
// title  = optional top-left label
// color  = border color function (default dim)
export function chatBorder(text, opts = {}) {
  const { width, title, color = fg("dim") } = opts;
  const cols = width || (process.stdout.columns || 80) - 4;
  const c = color;
  const _r = _R;
  const lines = text.split("\n");
  const out = [];
  const top = title
    ? `${c}╔═ ${_B}${title}${_R}${c} ${"═".repeat(Math.max(0, cols - title.length - 4))}β•—${_r}`
    : `${c}β•”${"═".repeat(cols + 2)}β•—${_r}`;
  out.push(top);
  for (const line of lines) {
    const wrapped = line.length > cols ? line.substring(0, cols - 1) + "…" : line;
    const pad = " ".repeat(Math.max(0, cols - wrapped.length + 2));
    out.push(`${c}β•‘${_r} ${wrapped}${pad}${c}β•‘${_r}`);
  }
  out.push(`${c}β•š${"═".repeat(cols + 2)}╝${_r}`);
  return out.join("\n");
}