fidelizare-integrate 0.1.0 → 0.3.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.
- package/package.json +1 -1
- package/src/cli.js +5 -13
- package/src/core/agent-loop.js +17 -12
- package/src/core/apply.js +6 -15
- package/src/ui/anim.js +65 -23
- package/src/ui/logo.ans +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fidelizare-integrate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Asistent de integrare Fidelizare pentru softuri de gestiune si case de marcat. Scaneaza codul, gaseste locul potrivit si propune integrarea API-ului, in siguranta.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli.js
CHANGED
|
@@ -90,24 +90,16 @@ async function main() {
|
|
|
90
90
|
// 5a. Agentic tool-loop (real coding agent): the model uses tools and
|
|
91
91
|
// applies the integration itself, each write gated by confirmation.
|
|
92
92
|
if (has('--agent') && (process.env.OPENROUTER_API_KEY || process.env.FIDELIZARE_LLM_KEY)) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
else if (!git.clean) note('• Repo git cu modificari necomise (le poti anula cu git checkout).', C.amber);
|
|
96
|
-
else note('• Repo git curat. Orice modificare e reversibila.', C.green);
|
|
97
|
-
note('• Agent: ' + bold(paint(val('--model') || 'Opus 4.8', C.red)) +
|
|
98
|
-
paint(' tool-uri: list/read/grep/create/insert/check. Fiecare scriere cere confirmare.', C.dim));
|
|
99
|
-
out('\n');
|
|
93
|
+
out(' ' + paint('Agent ', C.ink) + bold(paint(val('--model') || 'Opus 4.8', C.red)) +
|
|
94
|
+
paint(' · scrierile cer confirmare (y / a / N)', C.dim) + '\n\n');
|
|
100
95
|
const r = await runAgentLoop({
|
|
101
96
|
stack: det.stack, rootDir: root, files,
|
|
102
|
-
askRaw: ask, auto: AUTO, model: val('--model'),
|
|
97
|
+
askRaw: ask, auto: AUTO, model: val('--model'),
|
|
103
98
|
});
|
|
104
99
|
if (r.ok) {
|
|
105
100
|
writeEnv(root, vaultResolve(keyRef));
|
|
106
|
-
|
|
107
|
-
note('
|
|
108
|
-
note('Fisiere atinse: ' + (r.changes.join(', ') || '—'), C.ink);
|
|
109
|
-
note('Configuratie: .env (cheia API, nu in cod). Anulare: fisiere .bak / git checkout.', C.dim);
|
|
110
|
-
note('Test: scaneaza un cod de fidelitate (ex 12333) si verifica punctele.', C.ink);
|
|
101
|
+
out('\n ' + paint('✓', C.green) + ' ' + paint(r.summary.split('\n')[0].slice(0, 88), C.white) + '\n');
|
|
102
|
+
note((r.changes.join(', ') || '—') + paint(' · cheia in .env, anulare: git checkout', C.dim), C.ink);
|
|
111
103
|
out('\n ' + paint('Integrare pregatita.', C.green) + ' ' + paint('Fidelizare', C.red) + '\n\n');
|
|
112
104
|
rl.close();
|
|
113
105
|
return;
|
package/src/core/agent-loop.js
CHANGED
|
@@ -12,6 +12,7 @@ import { execFileSync } from 'child_process';
|
|
|
12
12
|
import { redact } from './vault.js';
|
|
13
13
|
import { safeCreateFile, safeInsert, insideRoot } from './apply.js';
|
|
14
14
|
import { C, paint, out } from '../ui/ansi.js';
|
|
15
|
+
import { thinking, toolLine } from '../ui/anim.js';
|
|
15
16
|
|
|
16
17
|
const URL = 'https://openrouter.ai/api/v1/chat/completions';
|
|
17
18
|
const DEFAULT_MODEL = 'anthropic/claude-opus-4.8';
|
|
@@ -82,20 +83,14 @@ export async function runAgentLoop({ stack, rootDir, files, askRaw, auto, model,
|
|
|
82
83
|
create_file: async ({ path, content }) => {
|
|
83
84
|
const a = abs(path);
|
|
84
85
|
if (!insideRoot(rootDir, a)) return 'REFUZAT: in afara proiectului';
|
|
85
|
-
|
|
86
|
-
content.split('\n').slice(0, 12).forEach((l) => log(' ' + paint('+ ', C.green) + paint(redact(l), C.ink) + '\n'));
|
|
87
|
-
if (!(await permit('create_file', 'Creez acest fisier?'))) return 'REFUZAT de utilizator';
|
|
86
|
+
if (!(await permit('create_file', ' ↳ scriu ' + rel(a) + '?'))) return 'REFUZAT de utilizator';
|
|
88
87
|
const r = safeCreateFile(rootDir, a, content);
|
|
89
88
|
changes.push(rel(a)); return 'OK creat (' + r.lines + ' linii)';
|
|
90
89
|
},
|
|
91
|
-
insert_code: async ({ path, atLine, lines
|
|
90
|
+
insert_code: async ({ path, atLine, lines }) => {
|
|
92
91
|
const a = abs(path);
|
|
93
92
|
if (!insideRoot(rootDir, a) || !existsSync(a)) return 'REFUZAT: fisier inexistent';
|
|
94
|
-
|
|
95
|
-
log('\n ' + paint('✎ ' + rel(a) + ' @ linia ' + atLine, C.amber) + (reason ? paint(' ' + reason, C.dim) : '') + '\n');
|
|
96
|
-
for (let i = Math.max(0, atLine - 2); i < atLine; i++) log(' ' + paint(' ' + (src[i] ?? ''), C.dim) + '\n');
|
|
97
|
-
lines.forEach((l) => log(' ' + paint('+ ' + l, C.green) + '\n'));
|
|
98
|
-
if (!(await permit('insert_code', 'Aplic aceasta modificare?'))) return 'REFUZAT de utilizator';
|
|
93
|
+
if (!(await permit('insert_code', ' ↳ modific ' + rel(a) + ' @' + atLine + '?'))) return 'REFUZAT de utilizator';
|
|
99
94
|
const r = safeInsert(rootDir, a, atLine, lines);
|
|
100
95
|
changes.push(rel(a)); return 'OK inserat ' + r.added + ' linii dupa ' + r.atLine;
|
|
101
96
|
},
|
|
@@ -114,7 +109,16 @@ export async function runAgentLoop({ stack, rootDir, files, askRaw, auto, model,
|
|
|
114
109
|
{ role: 'user', content: `Stack: ${stack}. Proiect cu ${files.length} fisiere sursa. Integreaza API-ul Fidelizare. Incepe prin a te orienta in cod.` },
|
|
115
110
|
];
|
|
116
111
|
|
|
112
|
+
const NAMES = { list_files: 'List', read_file: 'Read', grep: 'Grep', create_file: 'Write', insert_code: 'Edit', check_syntax: 'Check', finish: 'Done' };
|
|
113
|
+
const brief = (name, a) => {
|
|
114
|
+
if (name === 'grep') return a.pattern ? '"' + a.pattern + '"' : '';
|
|
115
|
+
if (name === 'insert_code') return (a.path ? rel(abs(a.path)) : '') + (a.atLine ? ' @' + a.atLine : '');
|
|
116
|
+
if (a.path) return rel(abs(a.path));
|
|
117
|
+
return '';
|
|
118
|
+
};
|
|
119
|
+
|
|
117
120
|
for (let step = 0; step < MAX_STEPS && !finished; step++) {
|
|
121
|
+
const stop = thinking('Gandeste');
|
|
118
122
|
let data;
|
|
119
123
|
try {
|
|
120
124
|
const res = await fetch(URL, {
|
|
@@ -123,16 +127,16 @@ export async function runAgentLoop({ stack, rootDir, files, askRaw, auto, model,
|
|
|
123
127
|
body: JSON.stringify({ model: usedModel, temperature: 0, tools: TOOLS, messages }),
|
|
124
128
|
signal: AbortSignal.timeout(90_000),
|
|
125
129
|
});
|
|
126
|
-
if (!res.ok) return { ok: false, reason: 'http_' + res.status };
|
|
130
|
+
if (!res.ok) { stop(); return { ok: false, reason: 'http_' + res.status }; }
|
|
127
131
|
data = await res.json();
|
|
128
|
-
} catch (e) { return { ok: false, reason: 'fetch:' + (e?.message || e) }; }
|
|
132
|
+
} catch (e) { stop(); return { ok: false, reason: 'fetch:' + (e?.message || e) }; }
|
|
133
|
+
stop();
|
|
129
134
|
|
|
130
135
|
const msg = data.choices?.[0]?.message;
|
|
131
136
|
if (!msg) return { ok: false, reason: 'no_message' };
|
|
132
137
|
messages.push(msg);
|
|
133
138
|
const calls = msg.tool_calls || [];
|
|
134
139
|
if (calls.length === 0) {
|
|
135
|
-
// model spoke without a tool call; nudge once, else stop
|
|
136
140
|
if (step > 0 && !finished) break;
|
|
137
141
|
messages.push({ role: 'user', content: 'Continua folosind tool-urile. Cand ai terminat, apeleaza finish.' });
|
|
138
142
|
continue;
|
|
@@ -140,6 +144,7 @@ export async function runAgentLoop({ stack, rootDir, files, askRaw, auto, model,
|
|
|
140
144
|
for (const call of calls) {
|
|
141
145
|
let arg = {};
|
|
142
146
|
try { arg = JSON.parse(call.function.arguments || '{}'); } catch { /* */ }
|
|
147
|
+
toolLine(NAMES[call.function.name] || call.function.name, brief(call.function.name, arg));
|
|
143
148
|
const fn = tools[call.function.name];
|
|
144
149
|
const result = fn ? await fn(arg) : 'tool necunoscut';
|
|
145
150
|
messages.push({ role: 'tool', tool_call_id: call.id, content: String(result) });
|
package/src/core/apply.js
CHANGED
|
@@ -28,30 +28,21 @@ function insideRoot(root, p) {
|
|
|
28
28
|
return rel && !rel.startsWith('..') && !resolve(p).includes('\0');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// Compact preview of the plan (one line per change), no writes.
|
|
32
32
|
export function renderPlan(plan, rootDir) {
|
|
33
33
|
for (const nf of plan.newFiles) {
|
|
34
|
-
out('
|
|
35
|
-
|
|
36
|
-
for (const l of lines) out(' ' + paint('+ ', C.green) + paint(l, C.ink) + '\n');
|
|
37
|
-
if (nf.content.split('\n').length > 14) out(' ' + paint(' …', C.dim) + '\n');
|
|
34
|
+
out(' ' + paint('●', C.red) + ' ' + paint('Write ', C.white) +
|
|
35
|
+
paint(relative(rootDir, nf.path) + ' (' + nf.content.split('\n').length + ' linii)', C.dim) + '\n');
|
|
38
36
|
}
|
|
39
|
-
// group edits by file
|
|
40
37
|
const byFile = new Map();
|
|
41
38
|
for (const e of plan.edits) {
|
|
42
39
|
if (!byFile.has(e.path)) byFile.set(e.path, []);
|
|
43
40
|
byFile.get(e.path).push(e);
|
|
44
41
|
}
|
|
45
42
|
for (const [file, edits] of byFile) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const ctx = Math.max(0, e.atLine - 2);
|
|
50
|
-
out(' ' + paint(`@@ linia ${e.atLine} ${e.reason}`, C.dim) + '\n');
|
|
51
|
-
for (let i = ctx; i < e.atLine; i++) out(' ' + paint(' ' + (src[i] ?? ''), C.dim) + '\n');
|
|
52
|
-
for (const ins of e.insert) out(' ' + paint('+ ' + ins, C.green) + '\n');
|
|
53
|
-
if (src[e.atLine] !== undefined) out(' ' + paint(' ' + src[e.atLine], C.dim) + '\n');
|
|
54
|
-
}
|
|
43
|
+
const added = edits.reduce((s, e) => s + e.insert.length, 0);
|
|
44
|
+
out(' ' + paint('●', C.red) + ' ' + paint('Edit ', C.white) +
|
|
45
|
+
paint(relative(rootDir, file) + ' +' + added + ' linii', C.dim) + '\n');
|
|
55
46
|
}
|
|
56
47
|
}
|
|
57
48
|
|
package/src/ui/anim.js
CHANGED
|
@@ -1,41 +1,83 @@
|
|
|
1
1
|
// Hand-built ASCII animations. No animation libraries: every frame is drawn
|
|
2
2
|
// with raw ANSI cursor control. Honors --no-anim / non-TTY by degrading to
|
|
3
3
|
// plain static lines.
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
4
6
|
import { ansi, C, paint, bold, out, isTTY, sleep } from './ansi.js';
|
|
5
7
|
|
|
6
8
|
let ANIM = isTTY && !process.argv.includes('--no-anim');
|
|
7
9
|
export const setAnim = (v) => { ANIM = v && isTTY; };
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
const cols = () => process.stdout.columns || 80;
|
|
12
|
+
const visLen = (s) => s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '').length;
|
|
13
|
+
const center = (s, w = visLen(s)) => ' '.repeat(Math.max(0, Math.floor((cols() - w) / 2))) + s;
|
|
14
|
+
|
|
15
|
+
let LOGO_LINES = null;
|
|
16
|
+
function loadLogo() {
|
|
17
|
+
if (LOGO_LINES) return LOGO_LINES;
|
|
18
|
+
try {
|
|
19
|
+
LOGO_LINES = readFileSync(fileURLToPath(new URL('./logo.ans', import.meta.url)), 'utf8').replace(/\n+$/, '').split('\n');
|
|
20
|
+
} catch { LOGO_LINES = []; }
|
|
21
|
+
return LOGO_LINES;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function topBar() {
|
|
25
|
+
const w = cols();
|
|
26
|
+
const left = ' Fidelizare Integrate';
|
|
27
|
+
const right = 'v0.1.0 · fidelizare.ro ';
|
|
28
|
+
const gap = Math.max(1, w - left.length - right.length);
|
|
29
|
+
out(C.onRed + ansi.bold + left + ' '.repeat(gap) + right + ansi.reset + '\n');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Thinking indicator (Claude-style morphing star + elapsed time). Returns a
|
|
33
|
+
// stop() that clears the line so the next output starts clean.
|
|
34
|
+
const STARS = ['✶', '✸', '✹', '✺', '✦', '✧'];
|
|
35
|
+
export function thinking(label = 'Gandeste') {
|
|
36
|
+
if (!ANIM) { out(' ' + paint('✶', C.red) + ' ' + paint(label, C.ink) + '…\n'); return () => {}; }
|
|
37
|
+
out(ansi.hideCursor);
|
|
38
|
+
let i = 0;
|
|
39
|
+
const t0 = Date.now();
|
|
40
|
+
const id = setInterval(() => {
|
|
41
|
+
const s = Math.round((Date.now() - t0) / 1000);
|
|
42
|
+
out(ansi.toLineStart + ansi.clearLine + ' ' + paint(STARS[i++ % STARS.length], C.red) +
|
|
43
|
+
' ' + paint(label, C.ink) + paint('… ' + s + 's', C.dim));
|
|
44
|
+
}, 95);
|
|
45
|
+
return () => { clearInterval(id); out(ansi.toLineStart + ansi.clearLine + ansi.showCursor); };
|
|
46
|
+
}
|
|
22
47
|
|
|
48
|
+
// Compact tool-call line, Claude-style: ● Tool brief
|
|
49
|
+
export function toolLine(name, brief = '') {
|
|
50
|
+
out(' ' + paint('●', C.red) + ' ' + paint(name.padEnd(6), C.white) +
|
|
51
|
+
(brief ? paint(brief, C.dim) : '') + '\n');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function swayLogo(logo, frames = [0, 1, 2, 1, 0, -1, -2, -1, 0]) {
|
|
55
|
+
if (!ANIM) return;
|
|
56
|
+
const base = Math.max(0, Math.floor((cols() - 26) / 2));
|
|
57
|
+
for (const off of frames) {
|
|
58
|
+
out(ansi.up(logo.length));
|
|
59
|
+
for (const line of logo) out(ansi.clearLine + ' '.repeat(Math.max(0, base + off)) + line + '\n');
|
|
60
|
+
await sleep(70);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Startup screen: top bar, the real (image-rendered) logo that reveals then
|
|
65
|
+
// gently sways, a title, and one line. Terse.
|
|
23
66
|
export async function logoIntro() {
|
|
24
67
|
if (!ANIM) {
|
|
25
68
|
out(paint(' Fidelizare', C.red) + paint(' · asistent de integrare\n\n', C.ink));
|
|
26
69
|
return;
|
|
27
70
|
}
|
|
28
|
-
out(ansi.hideCursor);
|
|
71
|
+
out('\x1b[2J\x1b[H' + ansi.hideCursor);
|
|
72
|
+
topBar();
|
|
29
73
|
out('\n');
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
await typewrite('FIDELIZARE', 30, C.red, true);
|
|
38
|
-
out(paint(' · asistent de integrare\n\n', C.ink));
|
|
74
|
+
const logo = loadLogo();
|
|
75
|
+
for (const line of logo) { out(center(line, 26) + '\n'); await sleep(24); }
|
|
76
|
+
await swayLogo(logo);
|
|
77
|
+
out('\n');
|
|
78
|
+
const title = ansi.bold + C.red + 'FIDELIZARE' + ansi.reset + paint(' · asistent de integrare', C.ink);
|
|
79
|
+
out(center(title, 'FIDELIZARE · asistent de integrare'.length) + '\n\n');
|
|
80
|
+
await sleep(140);
|
|
39
81
|
out(ansi.showCursor);
|
|
40
82
|
}
|
|
41
83
|
|
package/src/ui/logo.ans
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[0m [38;2;0;0;0m [0m [38;2;26;34;41m▁[38;2;22;30;38m▂[38;2;22;29;37m▂[38;2;25;32;41m▁[0m [38;2;13;13;13m [0m
|
|
2
|
+
[38;2;0;0;0m [0m [38;2;28;35;41m▂▅[38;2;30;38;43;48;2;16;21;25m▆[48;2;30;38;44m [38;2;31;39;44;48;2;30;37;43m▅▅[38;2;30;38;44m▇[48;2;29;37;43m▏[38;2;30;37;43;48;2;26;34;40m▇[0m[38;2;11;18;24m▎[38;2;28;36;44m▗[38;2;24;31;37m▃[0m [38;2;13;13;13m [0m
|
|
3
|
+
[38;2;0;0;0m [38;2;20;27;33m▗[38;2;10;13;15;48;2;26;33;39m▁[38;2;11;13;15;48;2;30;37;43m▁[38;2;11;12;15;48;2;30;38;44m▁[38;2;11;13;15m▁▁▁[38;2;10;13;14m▁▁▁[38;2;10;12;14;48;2;29;37;43m▁[0m[38;2;10;19;26m▏[7m[38;2;27;35;43m▍[0m[38;2;30;38;44;48;2;29;37;43m╴[38;2;28;36;41;48;2;19;23;27m▇[0m[38;2;26;34;40m▖ [0m
|
|
4
|
+
[38;2;0;0;0m [0m [38;2;25;33;39m▃[38;2;27;35;42m▄[38;2;28;36;42m▄[38;2;27;35;41m▄[38;2;24;31;37m▖[0m [38;2;150;35;42m▃[38;2;212;48;58m▄[38;2;209;48;58m▄[38;2;204;46;55m▄[38;2;202;44;54m▄[38;2;197;44;53m▄[0m [7m[38;2;27;36;44m▍[0m[38;2;31;39;45;48;2;31;38;43m▆[48;2;30;38;44m▄[38;2;30;38;44;48;2;29;37;43m▆[38;2;29;37;43;48;2;16;20;24m▖[0m [38;2;13;13;13m [0m
|
|
5
|
+
[38;2;0;0;0m [7m[38;2;23;30;35m▋[0m[38;2;30;38;44;48;2;30;37;43m┊[48;2;31;38;44m▆[38;2;18;22;26;48;2;29;37;43m▂[0m[7m[38;2;25;32;37m▅[0m[38;2;144;31;38m▃[7m[38;2;150;32;40m▚[38;2;209;47;57m▅[38;2;207;49;59m▁[0m[38;2;216;52;62;48;2;214;50;60m▊[0m[38;2;204;46;54m▌[7m[38;2;174;39;46m▇[0m[38;2;142;36;42;48;2;215;49;58m▏[0m[38;2;182;44;46m▎[7m[38;2;28;36;44m▍[0m[38;2;13;14;17;48;2;31;39;45m [38;2;31;39;45;48;2;30;38;44m▌[38;2;30;38;44;48;2;29;37;43m╴[0m[38;2;22;30;35m▖ [0m
|
|
6
|
+
[38;2;0;0;0m [7m[38;2;27;34;40m▌[0m[38;2;18;21;25;48;2;29;36;42m▂[0m[7m[38;2;25;31;37m▅[0m[38;2;149;36;42m▃[38;2;202;48;57m▆[7m[38;2;203;47;56m▝[0m[38;2;212;48;59m▅[7m[38;2;176;38;48m╸[38;2;141;31;38m▄[38;2;181;43;50m╸[0m[38;2;151;39;45;48;2;208;49;59m▁[38;2;216;52;62;48;2;218;48;59m▇[38;2;215;51;61;48;2;218;49;58m▉[0m[38;2;201;50;51m▎[7m[38;2;28;36;44m▍[0m[38;2;30;38;44;48;2;31;39;45m▌ [38;2;31;39;45;48;2;30;38;44m▋[38;2;30;38;44;48;2;29;37;43m▊[0m[38;2;27;36;42m▌ [0m
|
|
7
|
+
[38;2;0;0;0m [7m[38;2;17;21;24m▇[0m[38;2;147;34;41m▃[7m[38;2;201;48;57m▘[0m[38;2;216;54;63;48;2;217;50;59m▇[48;2;216;55;64m [38;2;216;54;64;48;2;216;54;63m┊[38;2;215;50;60m▝[0m[38;2;205;49;58m▅[7m[38;2;193;42;51m▄[0m[38;2;198;42;51m▘[7m[38;2;172;38;48m━[38;2;197;44;53m╸[38;2;201;45;54m▄[0m [7m[38;2;26;33;40m▌[0m[38;2;13;15;18;48;2;30;38;44m [38;2;30;38;44;48;2;31;39;45m▆[48;2;30;38;44m [48;2;29;37;43m╴[0m[38;2;22;29;34m▘ [0m
|
|
8
|
+
[38;2;0;0;0m [7m[38;2;173;40;49m▖[0m[38;2;216;53;63;48;2;216;53;62m╵[38;2;218;54;64;48;2;216;54;63m┕[38;2;217;54;64m▏[38;2;215;54;63;48;2;216;54;64m▗[48;2;216;55;64m▃[48;2;216;54;63m [38;2;215;52;62;48;2;214;51;60m▊[0m[7m[38;2;182;41;49m▝[38;2;201;44;54m▄[38;2;117;27;34m▇[0m[38;2;28;36;42m▄[38;2;29;37;43;48;2;18;22;25m▆[0m[38;2;25;31;36m▄[7m[38;2;22;28;33m▆[38;2;29;36;42m▃[0m[38;2;30;38;44;48;2;29;37;43m╴[0m[38;2;29;37;43m▘[0m [38;2;13;13;13m [0m
|
|
9
|
+
[38;2;0;0;0m [38;2;215;50;60m▝[38;2;217;51;61;48;2;208;50;60m╴[38;2;216;54;63;48;2;216;53;63m╵[38;2;217;52;62;48;2;216;54;63m▁[38;2;214;51;61;48;2;215;53;63m▁[38;2;214;52;62;48;2;207;48;58m╵[0m[7m[38;2;201;45;54m▄[38;2;119;28;34m▇[0m[38;2;29;36;42m▄[48;2;30;38;43m [38;2;31;39;45;48;2;30;38;44m▇[48;2;31;39;45m [38;2;31;38;44;48;2;30;38;44m▌[38;2;28;35;41;48;2;19;23;26m▇[0m[38;2;27;35;40m▖[7m[38;2;20;26;33m▇[0m[38;2;13;13;14m [0m
|
|
10
|
+
[38;2;0;0;0m [0m [7m[38;2;199;47;56m▃[0m[38;2;216;52;62;48;2;206;48;57m╵[0m[7m[38;2;200;45;55m▄[0m [38;2;29;36;42m▄[48;2;30;37;43m [48;2;31;39;45m [48;2;30;38;44m [38;2;30;38;44;48;2;29;36;42m╴[0m[7m[38;2;26;33;38m▃[38;2;17;21;25m▇[0m[38;2;12;13;13m [0m
|
|
11
|
+
[38;2;0;0;0m [0m [38;2;27;35;41m╺[38;2;22;29;34;48;2;29;37;43m▁[38;2;30;38;44;48;2;31;39;45m▁▂▄[48;2;30;38;44m [38;2;24;30;36;48;2;29;37;43m▁[0m[7m[38;2;26;34;39m▃[0m[38;2;18;23;26m▘ [0m
|
|
12
|
+
[38;2;0;0;0m [7m[38;2;17;22;26m▇[38;2;27;35;41m▄[0m[38;2;21;27;33;48;2;29;37;43m▁[48;2;28;36;42m [0m[7m[38;2;27;34;40m▄[38;2;18;23;27m▇[0m[38;2;12;13;13m [0m
|
|
13
|
+
[38;2;0;0;0m [0m [7m[38;2;16;20;24m▇[38;2;19;23;26m▇[0m[38;2;13;13;13m [0m
|