farmwork 1.0.0 β 1.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.
- package/README.md +116 -88
- package/bin/farmwork.js +0 -6
- package/package.json +8 -2
- package/src/doctor.js +57 -41
- package/src/index.js +0 -1
- package/src/init.js +429 -328
- package/src/status.js +108 -145
- package/src/terminal.js +649 -0
- package/src/add.js +0 -194
package/src/terminal.js
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
import terminalKit from "terminal-kit";
|
|
2
|
+
|
|
3
|
+
const term = terminalKit.terminal;
|
|
4
|
+
|
|
5
|
+
// Farm-themed color palette
|
|
6
|
+
const colors = {
|
|
7
|
+
primary: term.green,
|
|
8
|
+
secondary: term.yellow,
|
|
9
|
+
accent: term.cyan,
|
|
10
|
+
success: term.brightGreen,
|
|
11
|
+
warning: term.brightYellow,
|
|
12
|
+
error: term.red,
|
|
13
|
+
muted: term.gray,
|
|
14
|
+
highlight: term.brightWhite,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Farm emoji set - all farm-themed!
|
|
18
|
+
const emojis = {
|
|
19
|
+
// Equipment & Buildings
|
|
20
|
+
tractor: "π",
|
|
21
|
+
barn: "π‘",
|
|
22
|
+
basket: "π§Ί",
|
|
23
|
+
// Crops & Plants
|
|
24
|
+
corn: "π½",
|
|
25
|
+
wheat: "πΎ",
|
|
26
|
+
seedling: "π±",
|
|
27
|
+
tomato: "π
",
|
|
28
|
+
carrot: "π₯",
|
|
29
|
+
potato: "π₯",
|
|
30
|
+
lettuce: "π₯¬",
|
|
31
|
+
pumpkin: "π",
|
|
32
|
+
sunflower: "π»",
|
|
33
|
+
leaf: "π",
|
|
34
|
+
herb: "πΏ",
|
|
35
|
+
wilted: "π₯",
|
|
36
|
+
// Animals
|
|
37
|
+
cow: "π",
|
|
38
|
+
pig: "π·",
|
|
39
|
+
chicken: "π",
|
|
40
|
+
rooster: "π",
|
|
41
|
+
horse: "π΄",
|
|
42
|
+
sheep: "π",
|
|
43
|
+
dog: "π",
|
|
44
|
+
bee: "π",
|
|
45
|
+
owl: "π¦",
|
|
46
|
+
// Weather & Nature
|
|
47
|
+
sun: "βοΈ",
|
|
48
|
+
rain: "π§οΈ",
|
|
49
|
+
water: "π§",
|
|
50
|
+
// Fruits
|
|
51
|
+
apple: "π",
|
|
52
|
+
strawberry: "π",
|
|
53
|
+
grapes: "π",
|
|
54
|
+
peach: "π",
|
|
55
|
+
cherry: "π",
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ASCII Art
|
|
59
|
+
const TRACTOR_ART = `
|
|
60
|
+
_______________
|
|
61
|
+
| ___________ |
|
|
62
|
+
| | FARMWORK | |
|
|
63
|
+
| |___________| |
|
|
64
|
+
|_______________|
|
|
65
|
+
/ πΎ πΎ πΎ \\
|
|
66
|
+
/ π½ π½ π½ \\
|
|
67
|
+
/_____________________\\
|
|
68
|
+
()) _|__|_ (()
|
|
69
|
+
(()) | | (())
|
|
70
|
+
\\/ |____| \\/
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
const BARN_ART = `
|
|
74
|
+
/\\
|
|
75
|
+
/ \\
|
|
76
|
+
/ \\
|
|
77
|
+
/______\\
|
|
78
|
+
| FARM |
|
|
79
|
+
| WORK |
|
|
80
|
+
|________|
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
const SMALL_TRACTOR = `
|
|
84
|
+
__
|
|
85
|
+
|==|___
|
|
86
|
+
[| | \\
|
|
87
|
+
[|__|____\\>
|
|
88
|
+
() ()
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
// Main logo with apple and FARMWORK text
|
|
92
|
+
const LOGO_ART = {
|
|
93
|
+
apple: [
|
|
94
|
+
" ,--./,-.",
|
|
95
|
+
" / # \\",
|
|
96
|
+
" | |",
|
|
97
|
+
" \\ /",
|
|
98
|
+
" `._,._,'",
|
|
99
|
+
],
|
|
100
|
+
text: [
|
|
101
|
+
"ββββββββ ββββββ βββββββ ββββ βββββββ βββ βββββββ βββββββ βββ βββ",
|
|
102
|
+
"βββββββββββββββββββββββββββββ ββββββββ βββββββββββββββββββββββ ββββ",
|
|
103
|
+
"ββββββ ββββββββββββββββββββββββββββββ ββ ββββββ ββββββββββββββββββ ",
|
|
104
|
+
"ββββββ ββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββ ",
|
|
105
|
+
"βββ βββ ββββββ ββββββ βββ βββββββββββββββββββββββββ ββββββ βββ",
|
|
106
|
+
"βββ βββ ββββββ ββββββ βββ ββββββββ βββββββ βββ ββββββ βββ",
|
|
107
|
+
],
|
|
108
|
+
tagline: "π± Agentic Development Harness",
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Compact logo for smaller displays
|
|
112
|
+
const LOGO_COMPACT = {
|
|
113
|
+
fruit: "π",
|
|
114
|
+
text: [
|
|
115
|
+
" _____ _ ____ __ ____ _____ ____ _ __",
|
|
116
|
+
"| ___/ \\ | _ \\| \\/ \\ \\ / / _ \\| _ \\| |/ /",
|
|
117
|
+
"| |_ / _ \\ | |_) | |\\/| |\\ \\ /\\ / / | | | |_) | ' / ",
|
|
118
|
+
"| _/ ___ \\| _ <| | | | \\ V V /| |_| | _ <| . \\ ",
|
|
119
|
+
"|_|/_/ \\_\\_| \\_\\_| |_| \\_/\\_/ \\___/|_| \\_\\_|\\_\\",
|
|
120
|
+
],
|
|
121
|
+
tagline: "Agentic Development Harness",
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Animation frames for various states
|
|
125
|
+
const GROWING_FRAMES = ["π±", "πΏ", "π³", "π²", "π΄"];
|
|
126
|
+
const HARVEST_FRAMES = ["πΎ", "πΎπΎ", "πΎπΎπΎ", "πΎπΎ", "πΎ"];
|
|
127
|
+
const TRACTOR_FRAMES = ["π ", " π ", " π", " π "];
|
|
128
|
+
const WEATHER_FRAMES = [
|
|
129
|
+
"βοΈ ",
|
|
130
|
+
"π€οΈ",
|
|
131
|
+
"β
",
|
|
132
|
+
"π₯οΈ",
|
|
133
|
+
"βοΈ",
|
|
134
|
+
"π§οΈ",
|
|
135
|
+
"βοΈ",
|
|
136
|
+
"π§οΈ",
|
|
137
|
+
"βοΈ",
|
|
138
|
+
"π₯οΈ",
|
|
139
|
+
"β
",
|
|
140
|
+
"π€οΈ",
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
class FarmTerminal {
|
|
144
|
+
constructor() {
|
|
145
|
+
this.term = term;
|
|
146
|
+
this.colors = colors;
|
|
147
|
+
this.emojis = emojis;
|
|
148
|
+
this.spinnerInterval = null;
|
|
149
|
+
this.progressBar = null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Clear screen
|
|
153
|
+
clear() {
|
|
154
|
+
term.clear();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Move cursor
|
|
158
|
+
moveTo(x, y) {
|
|
159
|
+
term.moveTo(x, y);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Print styled header
|
|
163
|
+
header(text, style = "primary") {
|
|
164
|
+
const width = Math.min(term.width || 60, 60);
|
|
165
|
+
const padding = Math.floor((width - text.length - 4) / 2);
|
|
166
|
+
const paddedText =
|
|
167
|
+
" ".repeat(Math.max(0, padding)) +
|
|
168
|
+
text +
|
|
169
|
+
" ".repeat(Math.max(0, padding));
|
|
170
|
+
|
|
171
|
+
term("\n");
|
|
172
|
+
colors[style]("β" + "β".repeat(width - 2) + "β\n");
|
|
173
|
+
colors[style]("β");
|
|
174
|
+
term.bold.brightWhite(paddedText.slice(0, width - 4).padEnd(width - 4));
|
|
175
|
+
colors[style]("β\n");
|
|
176
|
+
colors[style]("β" + "β".repeat(width - 2) + "β\n");
|
|
177
|
+
term("\n");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Print section header
|
|
181
|
+
section(text, emoji = "π±") {
|
|
182
|
+
term("\n");
|
|
183
|
+
term.bold.cyan(`${emoji} ${text}\n`);
|
|
184
|
+
term.gray("β".repeat(Math.min(text.length + 4, 50)) + "\n");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Print a styled box
|
|
188
|
+
box(title, content, style = "primary") {
|
|
189
|
+
const width = Math.min(term.width || 50, 50);
|
|
190
|
+
const lines = Array.isArray(content) ? content : [content];
|
|
191
|
+
|
|
192
|
+
term("\n");
|
|
193
|
+
colors[style]("β" + "β".repeat(width - 2) + "β\n");
|
|
194
|
+
colors[style]("β");
|
|
195
|
+
term.bold(` ${title}`.padEnd(width - 2));
|
|
196
|
+
colors[style]("β\n");
|
|
197
|
+
colors[style]("β" + "β".repeat(width - 2) + "β€\n");
|
|
198
|
+
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
colors[style]("β");
|
|
201
|
+
term(` ${line}`.slice(0, width - 3).padEnd(width - 2));
|
|
202
|
+
colors[style]("β\n");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
colors[style]("β" + "β".repeat(width - 2) + "β\n");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Table display
|
|
209
|
+
table(headers, rows) {
|
|
210
|
+
const width = Math.min(term.width || 60, 60);
|
|
211
|
+
const colWidth = Math.floor((width - 4) / headers.length);
|
|
212
|
+
|
|
213
|
+
term("\n");
|
|
214
|
+
term.gray("β" + headers.map(() => "β".repeat(colWidth)).join("β¬") + "β\n");
|
|
215
|
+
term.gray("β");
|
|
216
|
+
for (const h of headers) {
|
|
217
|
+
term.bold.white(` ${h}`.slice(0, colWidth - 1).padEnd(colWidth));
|
|
218
|
+
}
|
|
219
|
+
term.gray("β\n");
|
|
220
|
+
term.gray("β" + headers.map(() => "β".repeat(colWidth)).join("βΌ") + "β€\n");
|
|
221
|
+
|
|
222
|
+
for (const row of rows) {
|
|
223
|
+
term.gray("β");
|
|
224
|
+
for (let i = 0; i < headers.length; i++) {
|
|
225
|
+
const cell = row[i] || "";
|
|
226
|
+
term(` ${cell}`.slice(0, colWidth - 1).padEnd(colWidth));
|
|
227
|
+
}
|
|
228
|
+
term.gray("β\n");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
term.gray("β" + headers.map(() => "β".repeat(colWidth)).join("β΄") + "β\n");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Animated spinner with custom message
|
|
235
|
+
async spin(message, asyncFn) {
|
|
236
|
+
const frames = ["π±", "πΏ", "π³", "π²", "π΄", "π²", "π³", "πΏ"];
|
|
237
|
+
let frameIndex = 0;
|
|
238
|
+
let lastLine = "";
|
|
239
|
+
|
|
240
|
+
const interval = setInterval(() => {
|
|
241
|
+
if (lastLine) {
|
|
242
|
+
term.column(1);
|
|
243
|
+
term.eraseLine();
|
|
244
|
+
}
|
|
245
|
+
lastLine = ` ${frames[frameIndex]} ${message}`;
|
|
246
|
+
term.yellow(lastLine);
|
|
247
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
248
|
+
}, 120);
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
const result = await asyncFn();
|
|
252
|
+
clearInterval(interval);
|
|
253
|
+
term.column(1);
|
|
254
|
+
term.eraseLine();
|
|
255
|
+
term.green(` πΏ ${message}\n`);
|
|
256
|
+
return result;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
clearInterval(interval);
|
|
259
|
+
term.column(1);
|
|
260
|
+
term.eraseLine();
|
|
261
|
+
term.red(` π₯ ${message}\n`);
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Simple spinner without async
|
|
267
|
+
startSpinner(message) {
|
|
268
|
+
const frames = ["π±", "πΏ", "π³", "π²", "π΄", "π²", "π³", "πΏ"];
|
|
269
|
+
let frameIndex = 0;
|
|
270
|
+
|
|
271
|
+
this.spinnerInterval = setInterval(() => {
|
|
272
|
+
term.column(1);
|
|
273
|
+
term.eraseLine();
|
|
274
|
+
term.yellow(` ${frames[frameIndex]} ${message}`);
|
|
275
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
276
|
+
}, 120);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
stopSpinner(message, success = true) {
|
|
280
|
+
if (this.spinnerInterval) {
|
|
281
|
+
clearInterval(this.spinnerInterval);
|
|
282
|
+
this.spinnerInterval = null;
|
|
283
|
+
}
|
|
284
|
+
term.column(1);
|
|
285
|
+
term.eraseLine();
|
|
286
|
+
if (success) {
|
|
287
|
+
term.green(` πΏ ${message}\n`);
|
|
288
|
+
} else {
|
|
289
|
+
term.red(` π₯ ${message}\n`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Tractor animation (drives across screen)
|
|
294
|
+
async tractorAnimation(message, durationMs = 2000) {
|
|
295
|
+
const tractorFrames = ["ππ¨", "π π¨", "π π¨", "π "];
|
|
296
|
+
const width = Math.min(term.width || 40, 40) - 10;
|
|
297
|
+
let pos = 0;
|
|
298
|
+
let frameIndex = 0;
|
|
299
|
+
const steps = width;
|
|
300
|
+
const interval = durationMs / steps;
|
|
301
|
+
|
|
302
|
+
return new Promise((resolve) => {
|
|
303
|
+
const timer = setInterval(() => {
|
|
304
|
+
term.column(1);
|
|
305
|
+
term.eraseLine();
|
|
306
|
+
const spaces = " ".repeat(pos);
|
|
307
|
+
term.yellow(`${spaces}${tractorFrames[frameIndex]} ${message}`);
|
|
308
|
+
pos++;
|
|
309
|
+
frameIndex = (frameIndex + 1) % tractorFrames.length;
|
|
310
|
+
|
|
311
|
+
if (pos >= width) {
|
|
312
|
+
clearInterval(timer);
|
|
313
|
+
term.column(1);
|
|
314
|
+
term.eraseLine();
|
|
315
|
+
term.green(` π ${message} πΎ\n`);
|
|
316
|
+
resolve();
|
|
317
|
+
}
|
|
318
|
+
}, interval);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Progress bar for multi-step operations
|
|
323
|
+
createProgressBar(options = {}) {
|
|
324
|
+
const { title = "Progress", items = 10, width = 40 } = options;
|
|
325
|
+
|
|
326
|
+
term("\n");
|
|
327
|
+
this.progressBar = term.progressBar({
|
|
328
|
+
title: ` ${emojis.tractor} ${title}`,
|
|
329
|
+
eta: true,
|
|
330
|
+
percent: true,
|
|
331
|
+
items,
|
|
332
|
+
width: Math.min(width, term.width - 20),
|
|
333
|
+
barStyle: term.green,
|
|
334
|
+
barBracketStyle: term.white,
|
|
335
|
+
percentStyle: term.yellow,
|
|
336
|
+
etaStyle: term.gray,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
return this.progressBar;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
updateProgress(value) {
|
|
343
|
+
if (this.progressBar) {
|
|
344
|
+
this.progressBar.update(value);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Slow typing effect for dramatic text
|
|
349
|
+
async slowType(text, speed = 50) {
|
|
350
|
+
return new Promise((resolve) => {
|
|
351
|
+
term.slowTyping(
|
|
352
|
+
text,
|
|
353
|
+
{
|
|
354
|
+
flashStyle: term.brightWhite,
|
|
355
|
+
delay: speed,
|
|
356
|
+
style: term.green,
|
|
357
|
+
},
|
|
358
|
+
resolve,
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Status indicator (pass/fail/warn)
|
|
364
|
+
status(label, state, details = "") {
|
|
365
|
+
const icons = {
|
|
366
|
+
pass: { icon: "π±", color: term.green },
|
|
367
|
+
fail: { icon: "π", color: term.red },
|
|
368
|
+
warn: { icon: "π", color: term.yellow },
|
|
369
|
+
info: { icon: "π§", color: term.cyan },
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const { icon, color } = icons[state] || icons.info;
|
|
373
|
+
color(` ${icon} ${label}`);
|
|
374
|
+
if (details) {
|
|
375
|
+
term.gray(` ${details}`);
|
|
376
|
+
}
|
|
377
|
+
term("\n");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Score display with visual bar
|
|
381
|
+
score(label, value, max = 10) {
|
|
382
|
+
const percentage = value / max;
|
|
383
|
+
const barWidth = 20;
|
|
384
|
+
const filled = Math.round(percentage * barWidth);
|
|
385
|
+
const empty = barWidth - filled;
|
|
386
|
+
|
|
387
|
+
const color =
|
|
388
|
+
percentage >= 0.8
|
|
389
|
+
? term.green
|
|
390
|
+
: percentage >= 0.5
|
|
391
|
+
? term.yellow
|
|
392
|
+
: term.red;
|
|
393
|
+
const emoji = percentage >= 0.8 ? "π³" : percentage >= 0.5 ? "πΏ" : "π±";
|
|
394
|
+
|
|
395
|
+
term(` ${emoji} ${label.padEnd(18)}`);
|
|
396
|
+
term.gray("[");
|
|
397
|
+
color("β".repeat(filled));
|
|
398
|
+
term.gray("β".repeat(empty));
|
|
399
|
+
term.gray("] ");
|
|
400
|
+
color(`${value}/${max}\n`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Metric display
|
|
404
|
+
metric(label, value, icon = "πΎ") {
|
|
405
|
+
term(` ${icon} ${label.padEnd(25)}`);
|
|
406
|
+
term.bold.white(`${value}\n`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Success message with celebration
|
|
410
|
+
success(message) {
|
|
411
|
+
term("\n");
|
|
412
|
+
term.green.bold(` π» ${message} π»\n`);
|
|
413
|
+
term("\n");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Error message
|
|
417
|
+
error(message) {
|
|
418
|
+
term("\n");
|
|
419
|
+
term.red.bold(` π₯ ${message}\n`);
|
|
420
|
+
term("\n");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Warning message
|
|
424
|
+
warn(message) {
|
|
425
|
+
term.yellow(` π ${message}\n`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Info message
|
|
429
|
+
info(message) {
|
|
430
|
+
term.cyan(` π ${message}\n`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Print tractor ASCII art
|
|
434
|
+
printTractor() {
|
|
435
|
+
term.green(TRACTOR_ART);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Print small tractor
|
|
439
|
+
printSmallTractor() {
|
|
440
|
+
term.green(SMALL_TRACTOR);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Print barn ASCII art
|
|
444
|
+
printBarn() {
|
|
445
|
+
term.yellow(BARN_ART);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Display the main FARMWORK logo with fruit
|
|
449
|
+
logo(compact = false) {
|
|
450
|
+
term("\n");
|
|
451
|
+
|
|
452
|
+
if (compact || (term.width && term.width < 80)) {
|
|
453
|
+
// Compact version for narrow terminals
|
|
454
|
+
term.green(` ${LOGO_COMPACT.fruit} `);
|
|
455
|
+
term.bold.brightGreen("FARMWORK\n");
|
|
456
|
+
term.gray(` ${LOGO_COMPACT.tagline}\n`);
|
|
457
|
+
} else {
|
|
458
|
+
// Full ASCII art logo
|
|
459
|
+
// Draw the apple in red
|
|
460
|
+
for (const line of LOGO_ART.apple) {
|
|
461
|
+
term.red(` ${line}\n`);
|
|
462
|
+
}
|
|
463
|
+
term("\n");
|
|
464
|
+
|
|
465
|
+
// Draw FARMWORK text in green gradient effect
|
|
466
|
+
const greenShades = [
|
|
467
|
+
term.green,
|
|
468
|
+
term.brightGreen,
|
|
469
|
+
term.brightGreen,
|
|
470
|
+
term.green,
|
|
471
|
+
term.green,
|
|
472
|
+
term.dim.green,
|
|
473
|
+
];
|
|
474
|
+
for (let i = 0; i < LOGO_ART.text.length; i++) {
|
|
475
|
+
term(" ");
|
|
476
|
+
greenShades[i](LOGO_ART.text[i] + "\n");
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Tagline
|
|
480
|
+
term("\n");
|
|
481
|
+
term.gray(` ${LOGO_ART.tagline}\n`);
|
|
482
|
+
}
|
|
483
|
+
term("\n");
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Animated logo reveal (types out the text)
|
|
487
|
+
async logoAnimated() {
|
|
488
|
+
term("\n");
|
|
489
|
+
|
|
490
|
+
// Draw apple with slight delay per line
|
|
491
|
+
for (const line of LOGO_ART.apple) {
|
|
492
|
+
term.red(` ${line}\n`);
|
|
493
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
494
|
+
}
|
|
495
|
+
term("\n");
|
|
496
|
+
|
|
497
|
+
// Reveal FARMWORK text line by line
|
|
498
|
+
const greenShades = [
|
|
499
|
+
term.green,
|
|
500
|
+
term.brightGreen,
|
|
501
|
+
term.brightGreen,
|
|
502
|
+
term.green,
|
|
503
|
+
term.green,
|
|
504
|
+
term.dim.green,
|
|
505
|
+
];
|
|
506
|
+
for (let i = 0; i < LOGO_ART.text.length; i++) {
|
|
507
|
+
term(" ");
|
|
508
|
+
greenShades[i](LOGO_ART.text[i] + "\n");
|
|
509
|
+
await new Promise((r) => setTimeout(r, 80));
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
term("\n");
|
|
513
|
+
term.gray(` ${LOGO_ART.tagline}\n`);
|
|
514
|
+
term("\n");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Phrase list display
|
|
518
|
+
phrases(phraseList) {
|
|
519
|
+
term("\n");
|
|
520
|
+
term.bold.cyan(" Quick Phrases:\n\n");
|
|
521
|
+
|
|
522
|
+
for (const { phrase, description, emoji } of phraseList) {
|
|
523
|
+
term(` ${emoji} `);
|
|
524
|
+
term.yellow.bold(phrase.padEnd(18));
|
|
525
|
+
term.gray(` ${emojis.arrow} ${description}\n`);
|
|
526
|
+
}
|
|
527
|
+
term("\n");
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Divider line
|
|
531
|
+
divider(char = "β", width = 50) {
|
|
532
|
+
term.gray(char.repeat(Math.min(width, term.width || 50)) + "\n");
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Newline helper
|
|
536
|
+
nl(count = 1) {
|
|
537
|
+
term("\n".repeat(count));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Raw text output
|
|
541
|
+
print(text) {
|
|
542
|
+
term(text);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Colored text helpers
|
|
546
|
+
green(text) {
|
|
547
|
+
term.green(text);
|
|
548
|
+
}
|
|
549
|
+
yellow(text) {
|
|
550
|
+
term.yellow(text);
|
|
551
|
+
}
|
|
552
|
+
red(text) {
|
|
553
|
+
term.red(text);
|
|
554
|
+
}
|
|
555
|
+
cyan(text) {
|
|
556
|
+
term.cyan(text);
|
|
557
|
+
}
|
|
558
|
+
gray(text) {
|
|
559
|
+
term.gray(text);
|
|
560
|
+
}
|
|
561
|
+
white(text) {
|
|
562
|
+
term.white(text);
|
|
563
|
+
}
|
|
564
|
+
bold(text) {
|
|
565
|
+
term.bold(text);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Wait for keypress
|
|
569
|
+
async waitForKey(message = "Press any key to continue...") {
|
|
570
|
+
term.gray(`\n ${message}`);
|
|
571
|
+
return new Promise((resolve) => {
|
|
572
|
+
term.grabInput(true);
|
|
573
|
+
term.once("key", (key) => {
|
|
574
|
+
term.grabInput(false);
|
|
575
|
+
term("\n");
|
|
576
|
+
resolve(key);
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Countdown animation
|
|
582
|
+
async countdown(seconds, message = "Starting in") {
|
|
583
|
+
for (let i = seconds; i > 0; i--) {
|
|
584
|
+
term.column(1);
|
|
585
|
+
term.eraseLine();
|
|
586
|
+
term.yellow(` π ${message} ${i}...`);
|
|
587
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
588
|
+
}
|
|
589
|
+
term.column(1);
|
|
590
|
+
term.eraseLine();
|
|
591
|
+
term.green(` π Go!\n`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Analyzing animation
|
|
595
|
+
async analyzing(message = "Analyzing", durationMs = 1500) {
|
|
596
|
+
const dots = [" ", ". ", ".. ", "..."];
|
|
597
|
+
let dotIndex = 0;
|
|
598
|
+
const startTime = Date.now();
|
|
599
|
+
|
|
600
|
+
return new Promise((resolve) => {
|
|
601
|
+
const interval = setInterval(() => {
|
|
602
|
+
term.column(1);
|
|
603
|
+
term.eraseLine();
|
|
604
|
+
term.cyan(` π¦ ${message}${dots[dotIndex]}`);
|
|
605
|
+
dotIndex = (dotIndex + 1) % dots.length;
|
|
606
|
+
|
|
607
|
+
if (Date.now() - startTime >= durationMs) {
|
|
608
|
+
clearInterval(interval);
|
|
609
|
+
term.column(1);
|
|
610
|
+
term.eraseLine();
|
|
611
|
+
term.green(` πΏ ${message} complete\n`);
|
|
612
|
+
resolve();
|
|
613
|
+
}
|
|
614
|
+
}, 200);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Planting animation (items appearing one by one)
|
|
619
|
+
async planting(items, message = "Planting") {
|
|
620
|
+
term.yellow(`\n π± ${message}:\n`);
|
|
621
|
+
|
|
622
|
+
for (const item of items) {
|
|
623
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
624
|
+
term.green(` πΏ ${item}\n`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Harvest animation (gathering results)
|
|
629
|
+
async harvesting(items, message = "Harvesting") {
|
|
630
|
+
term.yellow(`\n πΎ ${message}:\n`);
|
|
631
|
+
|
|
632
|
+
for (const item of items) {
|
|
633
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
634
|
+
term.cyan(` ${emojis.wheat} ${item}\n`);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Export singleton instance and class
|
|
640
|
+
export const farmTerm = new FarmTerminal();
|
|
641
|
+
export {
|
|
642
|
+
FarmTerminal,
|
|
643
|
+
term,
|
|
644
|
+
colors,
|
|
645
|
+
emojis,
|
|
646
|
+
TRACTOR_ART,
|
|
647
|
+
BARN_ART,
|
|
648
|
+
SMALL_TRACTOR,
|
|
649
|
+
};
|