noiton 1.0.0 → 2.0.1

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.
@@ -0,0 +1,210 @@
1
+ /**
2
+ * NOITON Installer - auto-installation of supported agents
3
+ *
4
+ * Provides install/uninstall/update flows with spinners.
5
+ */
6
+
7
+ import { execSync, spawn } from "node:child_process";
8
+ import { successMessage, errorMessage, installingMessage } from "../messages.js";
9
+ import { spinner, ok, fail, info, dim } from "../ui.js";
10
+ import { c } from "../constants.js";
11
+
12
+ /**
13
+ * Run a shell command and stream output. Returns a promise that resolves
14
+ * with exit code or rejects with the error.
15
+ * @param {string} cmd
16
+ * @param {object} opts
17
+ * @param {(chunk: string) => void} [opts.onData]
18
+ */
19
+ function execStream(cmd, opts = {}) {
20
+ return new Promise((resolve, reject) => {
21
+ const child = spawn(cmd, {
22
+ shell: true,
23
+ stdio: ["ignore", "pipe", "pipe"],
24
+ });
25
+
26
+ let combined = "";
27
+ child.stdout.on("data", (d) => {
28
+ const s = d.toString();
29
+ combined += s;
30
+ if (opts.onData) opts.onData(s);
31
+ });
32
+ child.stderr.on("data", (d) => {
33
+ const s = d.toString();
34
+ combined += s;
35
+ if (opts.onData) opts.onData(s);
36
+ });
37
+
38
+ child.on("error", (err) => reject(err));
39
+ child.on("close", (code) => {
40
+ if (code === 0) resolve(combined);
41
+ else reject(new Error(`Comando falhou com código ${code}`));
42
+ });
43
+ });
44
+ }
45
+
46
+ /**
47
+ * Install an agent by ID.
48
+ * @param {string} agentId
49
+ * @returns {Promise<{ok: boolean, output?: string, error?: string}>}
50
+ */
51
+ export async function installAgent(agentId) {
52
+ const { AGENT_REGISTRY } = await import("./detect.js");
53
+ const agent = AGENT_REGISTRY.find((a) => a.id === agentId);
54
+ if (!agent || !agent.installCmd) {
55
+ return { ok: false, error: `Agente ${agentId} não pode ser instalado automaticamente` };
56
+ }
57
+
58
+ const s = spinner(`${installingMessage()} Instalando ${agent.name}...`);
59
+ try {
60
+ const output = await execStream(agent.installCmd, {
61
+ onData: () => s.text = `${installingMessage()} Instalando ${agent.name}...`,
62
+ });
63
+ s.succeed(`${agent.name} instalado!`);
64
+ return { ok: true, output };
65
+ } catch (err) {
66
+ s.fail(`Erro ao instalar ${agent.name}`);
67
+ return { ok: false, error: err.message };
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Update an agent to latest version.
73
+ * @param {string} agentId
74
+ * @returns {Promise<{ok: boolean, error?: string}>}
75
+ */
76
+ export async function updateAgent(agentId) {
77
+ const { AGENT_REGISTRY } = await import("./detect.js");
78
+ const agent = AGENT_REGISTRY.find((a) => a.id === agentId);
79
+ if (!agent) return { ok: false, error: `Agente ${agentId} desconhecido` };
80
+
81
+ const updateCmds = {
82
+ opencode: "curl -fsSL https://opencode.ai/install | bash",
83
+ kilo: "npm update -g @kilocode/cli",
84
+ aider: "pip install --upgrade aider-chat",
85
+ hermes: "pip install --upgrade nous-hermes",
86
+ };
87
+
88
+ const cmd = updateCmds[agentId];
89
+ if (!cmd) return { ok: false, error: `Update não suportado para ${agent.name}` };
90
+
91
+ const s = spinner(`Atualizando ${agent.name}...`);
92
+ try {
93
+ await execStream(cmd);
94
+ s.succeed(`${agent.name} atualizado!`);
95
+ return { ok: true };
96
+ } catch (err) {
97
+ s.fail(`Erro ao atualizar ${agent.name}`);
98
+ return { ok: false, error: err.message };
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Quick check if command exists (used by checkAgentAvailable).
104
+ * @param {string} cmd
105
+ */
106
+ function commandExists(cmd) {
107
+ try {
108
+ execSync(`command -v ${cmd}`, { stdio: "ignore" });
109
+ return true;
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Check if Python is available (needed for pip installs).
117
+ */
118
+ export function pythonAvailable() {
119
+ return commandExists("python3") || commandExists("python");
120
+ }
121
+
122
+ /**
123
+ * Get current OpenCode version from binary.
124
+ * @returns {string|null}
125
+ */
126
+ export function getOpenCodeVersion() {
127
+ try {
128
+ const out = execSync("opencode --version 2>/dev/null", {
129
+ encoding: "utf8",
130
+ timeout: 5000,
131
+ });
132
+ const match = out.match(/(\d+\.\d+(?:\.\d+)?)/);
133
+ return match ? match[1] : out.trim();
134
+ } catch {
135
+ return null;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Confirm dialog using inquirer (re-exported for convenience).
141
+ */
142
+ import { confirm } from "@inquirer/prompts";
143
+
144
+ /**
145
+ * Offer to install OpenCode when none found.
146
+ * Asks the user a few quick questions to choose between OpenCode and Hermes.
147
+ */
148
+ export async function offerInstall() {
149
+ const { select } = await import("@inquirer/prompts");
150
+ console.log();
151
+ info(c.bold("🐙 Vamos descobrir o que você precisa!"));
152
+ console.log();
153
+ const useCase = await select({
154
+ message: "Pra que você quer usar IA?",
155
+ choices: [
156
+ {
157
+ name: "💻 Programar (código, debug, refatorar)",
158
+ value: "programming",
159
+ description: "Recomenda: OpenCode",
160
+ },
161
+ {
162
+ name: "🤖 Assistente (tarefas, integrações)",
163
+ value: "assistant",
164
+ description: "Recomenda: Hermes",
165
+ },
166
+ {
167
+ name: "🤔 Não sei, me explica as diferenças",
168
+ value: "explain",
169
+ description: "Mostra comparação detalhada",
170
+ },
171
+ ],
172
+ });
173
+
174
+ if (useCase === "explain") {
175
+ info("🐙 OpenCode: programador IA que escreve código junto com você");
176
+ info("🐙 Hermes: assistente que faz tarefas e se integra com apps");
177
+ console.log();
178
+ return offerInstall();
179
+ }
180
+
181
+ const wantInstall = await confirm({
182
+ message: `${useCase === "programming" ? "💻 OpenCode" : "🤖 Hermes"} é o que brilha aí. Instalar agora?`,
183
+ default: true,
184
+ });
185
+
186
+ if (!wantInstall) return null;
187
+
188
+ return useCase === "programming" ? "opencode" : "hermes";
189
+ }
190
+
191
+ /**
192
+ * Compare current and latest version, return update message if needed.
193
+ * @param {string} currentVer
194
+ * @param {string} latestVer
195
+ * @returns {string|null}
196
+ */
197
+ export function needsUpdateMessage(currentVer, latestVer) {
198
+ if (!currentVer || !latestVer) return null;
199
+ const cur = currentVer.split(".").map(Number);
200
+ const lat = latestVer.split(".").map(Number);
201
+ for (let i = 0; i < Math.max(cur.length, lat.length); i++) {
202
+ const c = cur[i] || 0;
203
+ const l = lat[i] || 0;
204
+ if (l > c) {
205
+ return `🔄 OpenCode v${latestVer} disponível! Você tem v${currentVer}.`;
206
+ }
207
+ if (l < c) return null;
208
+ }
209
+ return null;
210
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * NOITON Chatty - funny phrases, easter eggs, and contextual messages
3
+ *
4
+ * Each "moment" returns a random phrase from its pool. Some pools are
5
+ * time-bound (easter eggs for holidays), others are rare (5% chance).
6
+ */
7
+
8
+ import { c } from "./constants.js";
9
+
10
+ const TODAY = () => {
11
+ const d = new Date();
12
+ return { month: d.getMonth() + 1, day: d.getDate(), weekday: d.getDay() };
13
+ };
14
+
15
+ // ============= PHRASE POOLS =============
16
+
17
+ const successStart = [
18
+ "Houston, temos um coder! 🚀 Bora decolar!",
19
+ "✨ Pronto! Agora você fala a língua dos modelos!",
20
+ "🎉 Tá preparado, astronauta do código! Tudo conectado!",
21
+ "🧠 Sua IA tá conectada. Que o código esteja com você!",
22
+ "💜 Bem-vindo ao clube dos que quebram galho!",
23
+ "🤖 beep boop... chave aceita. Iniciando protocolo de poder.",
24
+ "⚡ Acabou de ganhar superpoderes. Use com responsabilidade!",
25
+ "🌟 Pronto pra criar o que ninguém pensou em criar!",
26
+ "🎯 Agora você é 10x developer. (Métricas não inclusas)",
27
+ "🍕 Sua pizza tá no forno... quer dizer, seu código!",
28
+ "🐙 O polvo abraçou você. Que os commits sejam verdes!",
29
+ "🔮 Os ventos do digital lhe favorecem. Mystic vibes on.",
30
+ "🎪 Espetáculo vai começar - você é a estrela!",
31
+ "💎 Brilhante! Como diamante em superfície de código.",
32
+ "🦄 Você está oficialmente powered by a magic sea creature.",
33
+ ];
34
+
35
+ const noAgentPhrases = [
36
+ "🌵 Aqui só tem deserto digital...",
37
+ "👻 Achou que ia rodar sozinho? Pior que precisa de um agent!",
38
+ "🦗 *grilo* ... bora instalar o OpenCode?",
39
+ "🐙 O polvo está olhando pra você esperando uma direção...",
40
+ "🕳️ Tão vazio quanto geladeira de estagiário em sexta-feira.",
41
+ "🌌 Silêncio cósmico... nenhum agente responde.",
42
+ "🤷 Sua máquina tá mais vazia que estacionamento de shopping domingo.",
43
+ ];
44
+
45
+ const installingPhrases = [
46
+ "📥 Instalando mais rápido que wifi de lan house!",
47
+ "🔧 Apertando parafusos digitais...",
48
+ "⚙️ Calibrando tentáculos do polvo...",
49
+ "🚀 Decolando em 3... 2... 1...",
50
+ ];
51
+
52
+ const thinkingPhrases = [
53
+ "🤔 Pensando mais rápido que polvo em aquário...",
54
+ "💭 Sondando as profundezas do digital...",
55
+ "🐙 O polvo está consultando seus 8 cérebros...",
56
+ "⚡ Calculando a matrix...",
57
+ "✨ Alinhando os chakras da configuração...",
58
+ ];
59
+
60
+ const errorPhrases = [
61
+ "💥 Ih, o polvo tropeçou no próprio tentáculo!",
62
+ "🤕 Até polvo erra às vezes, né?",
63
+ "🥴 Houston, temos um problema (mas é pequeno, confia).",
64
+ "🦑 Enroscaram os fios. Tenta de novo?",
65
+ ];
66
+
67
+ const cancelPhrases = [
68
+ "✋ Operação cancelada. O polvo acenou tchau.",
69
+ "🐙 Tudo bem, o polvo vai pescar!",
70
+ "👋 Sem stress. Volta quando quiser!",
71
+ ];
72
+
73
+ const farewell = [
74
+ "🐙 O polvo manda um abraço de 8 tentáculos!",
75
+ "✨ Até a próxima conexão!",
76
+ "🚀 Vai lá brilhar, astronauta!",
77
+ "💜 Tchau! O polvo cuida de você.",
78
+ ];
79
+
80
+ const rarePhrases = [
81
+ "🦄 Você encontrou o unicórnio! Bug-free garantido* (*exceto às terças)",
82
+ "👽 Aliens detectados no código. A IA neutralizou.",
83
+ "🍕 Pizza virtual entregue! Calorias: 0",
84
+ "🎰 Você é o visitante nº 1.337! Ganhou superpoderes extras.",
85
+ "🏆 NOITON Pro Tip #42: Polvos são mais espertos que devs (segredo)",
86
+ ];
87
+
88
+ const easterEggs = [
89
+ // Seasonal
90
+ { date: { month: 12, day: 25 }, message: "🎄 HO HO HO! Papai Noel trouxe superpoderes de IA!", emoji: "🎄" },
91
+ { date: { month: 12, day: 31 }, message: "🎆 Feliz Ano Novo! Que seus bugs sejam poucos!", emoji: "🎆" },
92
+ { date: { month: 1, day: 1 }, message: "🥂 Feliz 2026! Que venha o código limpo!", emoji: "🥂" },
93
+ { date: { month: 9, day: 13 }, message: "👨‍💻 Feliz Dia do Programador! Café grátis (simbólico)!", emoji: "👨‍💻" },
94
+ { date: { month: 10, day: 31 }, message: "🎃 BOO! Fantasmas no código? A IA resolve!", emoji: "🎃" },
95
+ { date: { month: 2, day: 14 }, message: "💘 Você <3 NOITON. O polvo sabe.", emoji: "💘" },
96
+ { date: { month: 6, day: 12 }, message: "🇧🇷 Viva o Brasil! O polvo tá sambando!", emoji: "🇧🇷" },
97
+ ];
98
+
99
+ // ============= EASTER EGG FUNCTION =============
100
+
101
+ /**
102
+ * Check for holiday/spacial date easter egg.
103
+ * Returns a message string or null.
104
+ */
105
+ export function getEasterEgg() {
106
+ const { month, day } = TODAY();
107
+ const egg = easterEggs.find((e) => e.date.month === month && e.date.day === day);
108
+ if (egg) return egg.message;
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * Get a random phrase from a pool
114
+ * Optionally include holiday easter egg in the pool
115
+ */
116
+ function pick(pool, count = 1) {
117
+ const egg = getEasterEgg();
118
+ const combined = egg ? [egg, ...pool] : pool;
119
+ const shuffled = [...combined].sort(() => Math.random() - 0.5);
120
+ return shuffled.slice(0, count);
121
+ }
122
+
123
+ // ============= PUBLIC MESSAGE FUNCTIONS =============
124
+
125
+ export function successMessage() {
126
+ const pick1 = pick(successStart, 1)[0];
127
+ const pick2 = pick(rarePhrases, 1)[0];
128
+ // 5% chance of rare phrase
129
+ const showRare = Math.random() < 0.05;
130
+ return showRare ? `${pick1}\n\n${c.dim("💎 Easter egg:")} ${pick2}` : pick1;
131
+ }
132
+
133
+ export function farewellMessage() {
134
+ return pick(farewell, 1)[0];
135
+ }
136
+
137
+ export function noAgentMessage() {
138
+ return pick(noAgentPhrases, 1)[0];
139
+ }
140
+
141
+ export function installingMessage() {
142
+ return pick(installingPhrases, 1)[0];
143
+ }
144
+
145
+ export function thinkingMessage() {
146
+ return pick(thinkingPhrases, 1)[0];
147
+ }
148
+
149
+ export function errorMessage() {
150
+ return pick(errorPhrases, 1)[0];
151
+ }
152
+
153
+ export function cancelMessage() {
154
+ return pick(cancelPhrases, 1)[0];
155
+ }
156
+
157
+ // All goodbye variants - randomly rotate
158
+ export function randomGoodbye() {
159
+ const options = [
160
+ "🐙 O polvo mandou um abraço!",
161
+ "✨ Que os commits sejam verdes!",
162
+ "🚀 Vai lá brilhar!",
163
+ "💜 Tchauzinho do NOITON!",
164
+ "🎯 Acerta em cheio!",
165
+ "🐙 *squish* (tradução: até mais)",
166
+ "👋 Bye bye do polvo!",
167
+ ];
168
+ return options[Math.floor(Math.random() * options.length)];
169
+ }
package/src/octopus.js ADDED
@@ -0,0 +1,254 @@
1
+ /**
2
+ * NOITON Octopus Mascot - "O Polvo NOITON"
3
+ *
4
+ * Design philosophy:
5
+ * - Smooth organic curves using ╭╮╰╯ ◠ ◡ ⌒ ~ characters
6
+ * - Round bulbous head with expressive eyes
7
+ * - 8 graceful flowing tentacles (not blocky)
8
+ * - Brand gradient: purple → magenta → pink → cyan
9
+ */
10
+
11
+ import gradient from "gradient-string";
12
+
13
+ // NOITON brand gradient
14
+ const brandGradient = gradient([
15
+ "#A78BFA", // light purple
16
+ "#C084FC", // purple
17
+ "#E879F9", // magenta
18
+ "#F472B6", // pink
19
+ "#22D3EE", // cyan
20
+ ]);
21
+
22
+ const successGradient = gradient(["#34D399", "#22D3EE", "#A78BFA"]);
23
+ const errorGradient = gradient(["#F87171", "#EF4444"]);
24
+
25
+ /**
26
+ * Main happy octopus - graceful, friendly, ready to help
27
+ * Clean organic curves, expressive eyes, flowing tentacles
28
+ */
29
+ export function octopusHappy() {
30
+ return brandGradient.multiline(`
31
+ ╭━━━━━━━━━╮
32
+ ╱ ╲
33
+ │ ◉ ◉ │
34
+ │ ◡ │
35
+ ╲___________╱
36
+ │ ╲___╱ │
37
+ ╭┤ ├╮
38
+ ╭┘╰┐ ╭━┘╰┐
39
+ ╭┘ ╰╮ ╭┘ ╰┐
40
+ ╭┘ ◠ ╰━╯ ◠ ╰┐
41
+ ╭┘ ╰┐
42
+ ╭┘ ♥ ✦ ♥ ╰┐
43
+ │ │
44
+ ╰╮ ╭╯
45
+ ╰╮ ✦ ♥ ╭╯
46
+ ╰╮ ╭╯
47
+ ╰─✦───♥─✦─╯
48
+ `);
49
+ }
50
+
51
+ /**
52
+ * Compact octopus for tight spaces
53
+ */
54
+ export function octopusMini() {
55
+ return brandGradient.multiline(`
56
+ ╭━━━━━╮
57
+ ╱ ◉ ◉ ╲
58
+ │ ◡ │
59
+ ╲_______╱
60
+ ╭╯ ╰╮
61
+ ╭┘ ◠ ◠ ╰┐
62
+ ╰╮ ♥ ╭╯
63
+ ╰───╯
64
+ `);
65
+ }
66
+
67
+ /**
68
+ * Curious octopus - looking around with question marks
69
+ */
70
+ export function octopusCurious() {
71
+ return brandGradient.multiline(`
72
+ ╭━━━━━━━━━╮
73
+ ╱ ╲
74
+ │ ? ? │
75
+ │ ◡ │
76
+ ╲___________╱
77
+ │ ╲___╱ │
78
+ ╭┤ ├╮
79
+ ╭┘╰┐ ╭━┘╰┐
80
+ ╭┘ ╰╮ ╭┘ ╰┐
81
+ ╭┘ ╰━╯ ╰┐
82
+ ╭┘ ╰┐
83
+ ╭┘ ? ? ? ╰┐
84
+ │ │
85
+ ╰╮ ╭╯
86
+ ╰╮ ╭╯
87
+ ╰╮ ? ? ╭╯
88
+ ╰───────────╯
89
+ `);
90
+ }
91
+
92
+ /**
93
+ * Working octopus - focused, with swirly eyes
94
+ */
95
+ export function octopusWorking() {
96
+ return brandGradient.multiline(`
97
+ ╭━━━━━━━━━╮
98
+ ╱ ╲
99
+ │ @ @ │
100
+ │ ▬ │
101
+ ╲___________╱
102
+ │ ╲___╱ │
103
+ ╭┤ ├╮
104
+ ╭┘╰┐ ╭━┘╰┐
105
+ ╭┘ ╰╮ ╭┘ ╰┐
106
+ ╭┘ ◠ ╰━╯ ◠ ╰┐
107
+ ╭┘ ⚙ ╰┐
108
+ ╭┘ ⚙ ⚙ ⚙ ╰┐
109
+ │ ⚙ ⚙ │
110
+ ╰╮ ⚙ ⚙ ╭╯
111
+ ╰╮ ╭╯
112
+ ╰╮──⚙───⚙──⚙─╭╯
113
+ ╰───────────╯
114
+ `);
115
+ }
116
+
117
+ /**
118
+ * Dancing/celebrating octopus - happy eyes, hearts and stars
119
+ */
120
+ export function octopusDancing() {
121
+ return successGradient.multiline(`
122
+ ╭━━━━━━━━━╮
123
+ ╱ ╲
124
+ │ ◠ ◠ │
125
+ │ ◡ │
126
+ ╲___________╱
127
+ │ ╲___╱ │
128
+ ╭┤ ├╮
129
+ ✦ ╭┘╰┐ ╭━┘╰┐ ✦
130
+ ╭━━━╭┘ ╰╮ ╭┘ ╰┐━━━╮
131
+ ╭━━━╭┘ ◠ ╰━╯ ◠ ╰┐━━━╮
132
+ ╭━━━╭┘ ╰┐━━━╮
133
+ ╭━━━╭┘ ♥ ★ ♥ ╰┐━━━╮
134
+ │ │
135
+ ╰╮ ★ ♥ ★ ♥ ╭╯
136
+ ╰╮ ╭╯
137
+ ★ ╰╮ ♥ ★ ♥ ╭╯ ★
138
+ ╰─★──♥───★──♥──★╯
139
+ ♪ ♥ ♪
140
+ `);
141
+ }
142
+
143
+ /**
144
+ * Sad octopus - drooping tentacles
145
+ */
146
+ export function octopusSad() {
147
+ return errorGradient.multiline(`
148
+ ╭━━━━━━━━━╮
149
+ ╱ ╲
150
+ │ . . │
151
+ │ ╲ │
152
+ ╲___________╱
153
+ │ ╲___╱ │
154
+ ╭┤ ├╮
155
+ ╭┘╰┐ ╭━┘╰┐
156
+ ╭━┘ ╰╮ ╭┘ ╰━╗
157
+ ╭┘ ╰━╯ ╰┐
158
+ ╭┘ ╰┐
159
+ │ │
160
+ ╰╮ ╭╯
161
+ ╰╮ ╭╯
162
+ ╰╮ ╭╯
163
+ ╰╮ ╭╯
164
+ ╰──────╯
165
+ `);
166
+ }
167
+
168
+ /**
169
+ * Sleeping octopus - zzz floating
170
+ */
171
+ export function octopusSleeping() {
172
+ return brandGradient.multiline(`
173
+ ╭━━━━━━━━━╮
174
+ ╱ ╲
175
+ │ - - │
176
+ │ ▬ │
177
+ ╲___________╱ z
178
+ │ ╲___╱ │ zZ
179
+ ╭┤ ├╮ zZZ
180
+ ╭┘╰┐ ╭━┘╰┐
181
+ ╭┘ ╰╮ ╭┘ ╰┐
182
+ ╭┘ ╰━╯ ╰┐
183
+ ╭┘ z ╰┐
184
+ ╭┘ zZ ╰┐
185
+ │ zZZ │
186
+ ╰╮ ╭╯
187
+ ╰╮ ╭╯
188
+ ╰──────────────╯
189
+ `);
190
+ }
191
+
192
+ /**
193
+ * Coding octopus - typing on keyboard
194
+ */
195
+ export function octopusCoding() {
196
+ return brandGradient.multiline(`
197
+ ╭━━━━━━━━━╮
198
+ ╱ ╲
199
+ │ ◉ ◉ │
200
+ │ ◡ │
201
+ ╲___________╱
202
+ │ ╲___╱ │
203
+ ╭┤ ├╮
204
+ ╭───╭┘╰┐ ╭━┘╰┐───╮
205
+ ╭┘ ╭┘ ╰╮ ╭┘ ╰┐ ╰┐
206
+ ╭┘ ╭┘ ◠ ╰━╯ ◠ ╰┐ ╰┐
207
+ ╭┘ ╭┘ ╰┐ ╰┐
208
+ ╭┘ ╭┘ ╰┐ ╰┐
209
+ │ │ [ / / / / ] │ │
210
+ ╰╮ ╰╮ ╭╯ ╭╯
211
+ ╰╮ ╰╮ ▓▓▓▓▓▓▓ ╭╯ ╭╯
212
+ ╰╮ ╰╮ ╭╯ ╭╯
213
+ ╰╮╰──────────╯ ╭╯
214
+ ╰╮ ▓▓ ╭╯
215
+ ╰──────────╯
216
+ `);
217
+ }
218
+
219
+ /**
220
+ * NOITON wordmark logo with small octopus on top
221
+ */
222
+ export function octopusLogo() {
223
+ return brandGradient.multiline(`
224
+ ╭━━━━━╮
225
+ ╱ ◉ ◉ ╲
226
+ │ ◡ │
227
+ ╲_______╱
228
+ ╭╯ ╰╮
229
+ ╭┘ ◠ ◠ ╰┐
230
+ ╰╮ ♥ ╭╯
231
+ ╰───╯
232
+
233
+ ███╗ ██╗ ██████╗ ██╗████████╗ ██████╗ ███╗ ██╗
234
+ ████╗ ██║██╔═══██╗██║╚══██╔══╝██╔═══██╗████╗ ██║
235
+ ██╔██╗ ██║██║ ██║██║ ██║ ██║ ██║██╔██╗ ██║
236
+ ██║╚██╗██║██║ ██║██║ ██║ ██║ ██║██║╚██╗██║
237
+ ██║ ╚████║╚██████╔╝██║ ██║ ╚██████╔╝██║ ╚████║
238
+ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
239
+ `);
240
+ }
241
+
242
+ /**
243
+ * Get a random octopus pose
244
+ */
245
+ export function randomOctopus() {
246
+ const poses = [octopusHappy, octopusDancing, octopusCoding];
247
+ return poses[Math.floor(Math.random() * poses.length)]();
248
+ }
249
+
250
+ export {
251
+ brandGradient,
252
+ successGradient,
253
+ errorGradient,
254
+ };