accelr8 1.0.0 → 3.0.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/README.md +118 -0
- package/dist/chunks/agentcli-ZI2O7ID7.js +176 -0
- package/dist/chunks/chunk-3JNUWI22.js +563 -0
- package/dist/chunks/chunk-SF6NNSYZ.js +60 -0
- package/dist/chunks/client-7BVK2DEW.js +3454 -0
- package/dist/chunks/server-ZP2DV4IO.js +930 -0
- package/dist/chunks/update-UEPNGB6Y.js +106 -0
- package/dist/cli.js +88 -0
- package/package.json +63 -12
- package/ascii.txt +0 -11
- package/bin/index.js +0 -314
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/update.ts
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
var REGISTRY = "https://registry.npmjs.org/accelr8/latest";
|
|
9
|
+
var CACHE_FILE = "update-check.json";
|
|
10
|
+
var CHECK_EVERY_MS = 6 * 60 * 60 * 1e3;
|
|
11
|
+
function cachePath(baseDir) {
|
|
12
|
+
return path.join(baseDir ?? path.join(os.homedir(), ".accelr8"), CACHE_FILE);
|
|
13
|
+
}
|
|
14
|
+
function compareVersions(a, b) {
|
|
15
|
+
const clean = (v) => v.trim().replace(/^v/, "").split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
16
|
+
const pa = clean(a), pb = clean(b);
|
|
17
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
18
|
+
const x = pa[i] ?? 0, y = pb[i] ?? 0;
|
|
19
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
function cachedUpdate(current, baseDir) {
|
|
24
|
+
try {
|
|
25
|
+
const raw = fs.readFileSync(cachePath(baseDir), "utf8");
|
|
26
|
+
const c = JSON.parse(raw);
|
|
27
|
+
if (c && typeof c.latest === "string" && compareVersions(current, c.latest) < 0) {
|
|
28
|
+
return { latest: c.latest, current };
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function refreshUpdateCache(baseDir, now = Date.now()) {
|
|
35
|
+
let fresh = false;
|
|
36
|
+
try {
|
|
37
|
+
const c = JSON.parse(fs.readFileSync(cachePath(baseDir), "utf8"));
|
|
38
|
+
fresh = typeof c.checkedAt === "number" && now - c.checkedAt < CHECK_EVERY_MS;
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
if (fresh) return;
|
|
42
|
+
const ac = new AbortController();
|
|
43
|
+
const timer = setTimeout(() => ac.abort(), 2500);
|
|
44
|
+
Promise.resolve().then(() => globalThis.fetch ? globalThis.fetch(REGISTRY, { signal: ac.signal, headers: { accept: "application/json" } }) : null).then((r) => r && r.ok ? r.json() : null).then((j) => {
|
|
45
|
+
const latest = j && typeof j.version === "string" ? j.version : null;
|
|
46
|
+
if (!latest) return;
|
|
47
|
+
const dir = baseDir ?? path.join(os.homedir(), ".accelr8");
|
|
48
|
+
try {
|
|
49
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
const tmp = path.join(dir, CACHE_FILE + ".tmp-" + process.pid);
|
|
53
|
+
fs.writeFileSync(tmp, JSON.stringify({ latest, checkedAt: now }));
|
|
54
|
+
fs.renameSync(tmp, cachePath(baseDir));
|
|
55
|
+
}).catch(() => {
|
|
56
|
+
}).finally(() => clearTimeout(timer));
|
|
57
|
+
}
|
|
58
|
+
function updateBanner(current, baseDir) {
|
|
59
|
+
refreshUpdateCache(baseDir);
|
|
60
|
+
const u = cachedUpdate(current, baseDir);
|
|
61
|
+
return u ? `update available \xB7 v${u.latest} \xB7 npm i -g accelr8@latest` : null;
|
|
62
|
+
}
|
|
63
|
+
function runUpdate(cb) {
|
|
64
|
+
let child;
|
|
65
|
+
const MANUAL = "run it yourself: npm i -g accelr8@latest";
|
|
66
|
+
try {
|
|
67
|
+
child = spawn("npm", ["install", "-g", "accelr8@latest", "--no-fund", "--no-audit"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
68
|
+
} catch (e) {
|
|
69
|
+
queueMicrotask(() => cb.onDone(false, `couldn't start npm \xB7 ${MANUAL}`));
|
|
70
|
+
return () => {
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
let tail = "";
|
|
74
|
+
const feed = (chunk) => {
|
|
75
|
+
tail += chunk.toString();
|
|
76
|
+
let nl;
|
|
77
|
+
while ((nl = tail.indexOf("\n")) >= 0) {
|
|
78
|
+
const line = tail.slice(0, nl).trim();
|
|
79
|
+
tail = tail.slice(nl + 1);
|
|
80
|
+
if (line) cb.onLine(line);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
child.stdout?.on("data", feed);
|
|
84
|
+
child.stderr?.on("data", feed);
|
|
85
|
+
child.on("error", (e) => {
|
|
86
|
+
cb.onDone(false, e.code === "ENOENT" ? `npm not found \xB7 ${MANUAL}` : `${e.message} \xB7 ${MANUAL}`);
|
|
87
|
+
});
|
|
88
|
+
child.on("close", (code) => {
|
|
89
|
+
if (tail.trim()) cb.onLine(tail.trim());
|
|
90
|
+
if (code === 0) cb.onDone(true, "updated \xB7 restart accelr8 to use the new version");
|
|
91
|
+
else cb.onDone(false, code === 243 || code === 3 ? `permission denied \xB7 ${MANUAL}` : `npm exited ${code} \xB7 ${MANUAL}`);
|
|
92
|
+
});
|
|
93
|
+
return () => {
|
|
94
|
+
try {
|
|
95
|
+
child.kill();
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
cachedUpdate,
|
|
102
|
+
compareVersions,
|
|
103
|
+
refreshUpdateCache,
|
|
104
|
+
runUpdate,
|
|
105
|
+
updateBanner
|
|
106
|
+
};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_SERVER,
|
|
4
|
+
PORT,
|
|
5
|
+
VERSION
|
|
6
|
+
} from "./chunks/chunk-SF6NNSYZ.js";
|
|
7
|
+
|
|
8
|
+
// src/cli.ts
|
|
9
|
+
var HELP = `
|
|
10
|
+
accelr8 v${VERSION} - collaborative ascii canvas in your terminal
|
|
11
|
+
|
|
12
|
+
USAGE
|
|
13
|
+
accelr8 open the app
|
|
14
|
+
accelr8 join <CODE> open straight into a project
|
|
15
|
+
accelr8 serve run your own sync server
|
|
16
|
+
accelr8 --intro replay the full boot animation
|
|
17
|
+
|
|
18
|
+
OPTIONS
|
|
19
|
+
--name <name> display name (remembered for next time)
|
|
20
|
+
--server <url> sync server url
|
|
21
|
+
(default: ${DEFAULT_SERVER})
|
|
22
|
+
--port <port> port for serve (default: ${PORT})
|
|
23
|
+
-v, --version print version
|
|
24
|
+
-h, --help this help
|
|
25
|
+
|
|
26
|
+
IN THE APP
|
|
27
|
+
mouse click tabs, buttons, project rows, brushes
|
|
28
|
+
click + drag on canvas paint (right-click erases)
|
|
29
|
+
tab / 1 2 3 switch tabs
|
|
30
|
+
q quit
|
|
31
|
+
|
|
32
|
+
Everyone in a project sees everyone else's cursor move live on the canvas.
|
|
33
|
+
`;
|
|
34
|
+
function argVal(args, flag) {
|
|
35
|
+
const i = args.indexOf(flag);
|
|
36
|
+
if (i >= 0 && i + 1 < args.length) return args[i + 1];
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
async function main() {
|
|
40
|
+
const args = process.argv.slice(2);
|
|
41
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
42
|
+
console.log(HELP);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (args.includes("-v") || args.includes("--version")) {
|
|
46
|
+
console.log(VERSION);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (args[0] === "serve") {
|
|
50
|
+
const port = Number(argVal(args, "--port") || PORT);
|
|
51
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
52
|
+
console.error(`invalid port: ${argVal(args, "--port")}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const { runServer } = await import("./chunks/server-ZP2DV4IO.js");
|
|
56
|
+
runServer(port);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (args[0] === "agent") {
|
|
60
|
+
const { runAgentCli } = await import("./chunks/agentcli-ZI2O7ID7.js");
|
|
61
|
+
await runAgentCli(args.slice(1));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let joinCode;
|
|
65
|
+
if (args[0] === "join") {
|
|
66
|
+
joinCode = args[1];
|
|
67
|
+
if (!joinCode || joinCode.startsWith("-")) {
|
|
68
|
+
console.error("usage: accelr8 join <CODE>");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!process.stdout.isTTY) {
|
|
73
|
+
console.error("accelr8 is a terminal app and needs an interactive terminal (TTY).");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
const { cachedUpdate, refreshUpdateCache } = await import("./chunks/update-UEPNGB6Y.js");
|
|
77
|
+
refreshUpdateCache();
|
|
78
|
+
const update = cachedUpdate(VERSION);
|
|
79
|
+
const { runClient } = await import("./chunks/client-7BVK2DEW.js");
|
|
80
|
+
runClient({
|
|
81
|
+
name: argVal(args, "--name"),
|
|
82
|
+
server: argVal(args, "--server"),
|
|
83
|
+
joinCode,
|
|
84
|
+
intro: args.includes("--intro"),
|
|
85
|
+
update
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,19 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "accelr8",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
-
},
|
|
8
|
-
"keywords": [],
|
|
9
|
-
"author": "",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Collaborative ascii canvas in your terminal. Real multiplayer, live cursors, mouse-driven TUI.",
|
|
5
|
+
"type": "module",
|
|
10
6
|
"license": "ISC",
|
|
11
|
-
"
|
|
12
|
-
|
|
7
|
+
"author": "Pat Santiago",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Patonchain/accelr8-cli.git"
|
|
13
11
|
},
|
|
14
12
|
"bin": {
|
|
15
|
-
"accelr8": "
|
|
13
|
+
"accelr8": "dist/cli.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"cli",
|
|
24
|
+
"tui",
|
|
25
|
+
"ascii",
|
|
26
|
+
"collaborative",
|
|
27
|
+
"canvas",
|
|
28
|
+
"live-cursors",
|
|
29
|
+
"terminal",
|
|
30
|
+
"accelr8"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "node scripts/build.mjs",
|
|
34
|
+
"dev": "tsx src/cli.ts",
|
|
35
|
+
"serve": "tsx src/cli.ts serve",
|
|
36
|
+
"smoketest": "tsx src/smoketest.ts",
|
|
37
|
+
"rendertest": "tsx src/rendertest.ts",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"prepublishOnly": "npm run build",
|
|
40
|
+
"coretest": "tsx src/coretest.ts",
|
|
41
|
+
"orbtest": "tsx src/orbtest.ts",
|
|
42
|
+
"chattest": "tsx src/chattest.ts",
|
|
43
|
+
"inputtest": "tsx src/inputtest.ts",
|
|
44
|
+
"torturetest": "tsx src/torturetest.ts",
|
|
45
|
+
"test": "for t in core chat input ladder boot orb crypto accounts executor agentcontext map voice glyph materialize update contrast; do tsx src/${t}test.ts || exit 1; done && tsx src/rendertest.ts > /dev/null && echo RENDER OK",
|
|
46
|
+
"laddertest": "tsx src/laddertest.ts",
|
|
47
|
+
"boottest": "tsx src/boottest.ts",
|
|
48
|
+
"reconnecttest": "tsx src/reconnecttest.ts",
|
|
49
|
+
"agenttest": "tsx src/agenttest.ts",
|
|
50
|
+
"maptest": "tsx src/maptest.ts",
|
|
51
|
+
"voicetest": "tsx src/voicetest.ts",
|
|
52
|
+
"cryptotest": "tsx src/cryptotest.ts",
|
|
53
|
+
"accountstest": "tsx src/accountstest.ts",
|
|
54
|
+
"executortest": "tsx src/executortest.ts",
|
|
55
|
+
"agentcontexttest": "tsx src/agentcontexttest.ts",
|
|
56
|
+
"chaintest": "tsx src/chaintest.ts"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"terminal-kit": "3.1.2",
|
|
60
|
+
"ws": "8.18.0"
|
|
16
61
|
},
|
|
17
|
-
"devDependencies": {
|
|
18
|
-
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "22.10.2",
|
|
64
|
+
"@types/terminal-kit": "2.5.7",
|
|
65
|
+
"@types/ws": "8.5.13",
|
|
66
|
+
"esbuild": "0.28.1",
|
|
67
|
+
"tsx": "4.19.2",
|
|
68
|
+
"typescript": "5.7.2"
|
|
69
|
+
}
|
|
19
70
|
}
|
package/ascii.txt
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
('-. ('-. _ .-')
|
|
2
|
-
( OO ).-. _( OO) ( \( -O )
|
|
3
|
-
/ . --. / .-----. .-----. (,------. ,--. ,------. .-----.
|
|
4
|
-
| \-. \ ' .--./ ' .--./ | .---' | |.-') | /`. ' / .-. \
|
|
5
|
-
.-'-' | | | |('-. | |('-. | | | | OO ) | / | | | \_.' /
|
|
6
|
-
\| |_.' | /_) |OO ) /_) |OO ) (| '--. | |`-' | | |_.' | / .-. '.
|
|
7
|
-
| .-. | || |`-'| || |`-'| | .--' (| '---.' | . '.' | | | |
|
|
8
|
-
| | | | (_' '--'\ (_' '--'\ | `---. | | | |\ \ \ '-' /
|
|
9
|
-
`--' `--' `-----' `-----' `------' `------' `--' '--' `----''
|
|
10
|
-
|
|
11
|
-
|
package/bin/index.js
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const blessed = require('reblessed');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const { spawn } = require('child_process');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
|
|
7
|
-
// Settings
|
|
8
|
-
const MIN_WIDTH = 100;
|
|
9
|
-
const MIN_HEIGHT = 30;
|
|
10
|
-
const screen = blessed.screen({ smartCSR: true, title: 'ACCELR8 Terminal' });
|
|
11
|
-
|
|
12
|
-
let hasLoadedUI = false;
|
|
13
|
-
let asciiLogo, menu, errorBox, leftBox, rightBox;
|
|
14
|
-
|
|
15
|
-
// Load ASCII logo
|
|
16
|
-
const asciiContent = fs.readFileSync(path.join(__dirname, '../ascii.txt'), 'utf-8');
|
|
17
|
-
const logoLines = asciiContent.split('\n');
|
|
18
|
-
const logoHeight = logoLines.length;
|
|
19
|
-
const logoWidth = Math.max(...logoLines.map(line => line.length));
|
|
20
|
-
|
|
21
|
-
// Konami Code: up, up, down, down, left, right, left, right
|
|
22
|
-
const konamiSequence = ['up', 'up', 'down', 'down', 'left', 'right', 'left', 'right'];
|
|
23
|
-
let konamiIndex = 0;
|
|
24
|
-
|
|
25
|
-
screen.on('keypress', (ch, key) => {
|
|
26
|
-
if (!key) return;
|
|
27
|
-
if (key.name === konamiSequence[konamiIndex]) {
|
|
28
|
-
konamiIndex++;
|
|
29
|
-
if (konamiIndex === konamiSequence.length) {
|
|
30
|
-
launchNetHack();
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
konamiIndex = 0;
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// === Size Check ===
|
|
38
|
-
function tooSmall() {
|
|
39
|
-
return screen.width < MIN_WIDTH || screen.height < MIN_HEIGHT;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function showError() {
|
|
43
|
-
if (errorBox) return;
|
|
44
|
-
errorBox = blessed.box({
|
|
45
|
-
top: 'center',
|
|
46
|
-
left: 'center',
|
|
47
|
-
width: 'shrink',
|
|
48
|
-
height: 5,
|
|
49
|
-
content: '⚠️ Please enlarge your terminal window.\n(min size: 100x30)',
|
|
50
|
-
align: 'center',
|
|
51
|
-
valign: 'middle',
|
|
52
|
-
border: { type: 'line' },
|
|
53
|
-
style: { fg: 'red', border: { fg: 'red' } },
|
|
54
|
-
});
|
|
55
|
-
screen.append(errorBox);
|
|
56
|
-
screen.render();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function hideError() {
|
|
60
|
-
if (errorBox) {
|
|
61
|
-
errorBox.destroy();
|
|
62
|
-
errorBox = null;
|
|
63
|
-
screen.render();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function showUI() {
|
|
68
|
-
if (hasLoadedUI) return;
|
|
69
|
-
|
|
70
|
-
const logoTop = Math.floor((screen.height - logoHeight - 10) / 2);
|
|
71
|
-
const logoLeft = Math.floor((screen.width - logoWidth) / 2);
|
|
72
|
-
|
|
73
|
-
asciiLogo = blessed.box({
|
|
74
|
-
top: logoTop,
|
|
75
|
-
left: logoLeft,
|
|
76
|
-
width: logoWidth,
|
|
77
|
-
height: logoHeight,
|
|
78
|
-
content: asciiContent,
|
|
79
|
-
tags: false,
|
|
80
|
-
style: { fg: 'cyan', bold: true },
|
|
81
|
-
wrap: false,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const sideWidth = Math.floor(screen.width * 0.25);
|
|
85
|
-
const sideHeight = screen.height - 2;
|
|
86
|
-
|
|
87
|
-
leftBox = blessed.box({
|
|
88
|
-
top: 1,
|
|
89
|
-
left: 0,
|
|
90
|
-
width: sideWidth,
|
|
91
|
-
height: sideHeight,
|
|
92
|
-
tags: true,
|
|
93
|
-
style: { bg: 'default' },
|
|
94
|
-
content: '',
|
|
95
|
-
wrap: false,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
rightBox = blessed.box({
|
|
99
|
-
top: 1,
|
|
100
|
-
left: screen.width - sideWidth,
|
|
101
|
-
width: sideWidth,
|
|
102
|
-
height: sideHeight,
|
|
103
|
-
tags: true,
|
|
104
|
-
style: { bg: 'default' },
|
|
105
|
-
content: '',
|
|
106
|
-
wrap: false,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
menu = blessed.list({
|
|
110
|
-
top: logoTop + logoHeight + 2,
|
|
111
|
-
left: 'center',
|
|
112
|
-
width: '40%',
|
|
113
|
-
height: 7,
|
|
114
|
-
items: [
|
|
115
|
-
'[1] About the House',
|
|
116
|
-
'[2] Meet the Founders',
|
|
117
|
-
'[3] Upcoming Events',
|
|
118
|
-
'[4] Apply to Stay',
|
|
119
|
-
'[5] Exit',
|
|
120
|
-
],
|
|
121
|
-
keys: true,
|
|
122
|
-
mouse: true,
|
|
123
|
-
border: { type: 'line' },
|
|
124
|
-
style: {
|
|
125
|
-
selected: { bg: 'blue', fg: 'white' },
|
|
126
|
-
item: { hover: { bg: 'gray' } },
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
menu.on('select', (item, index) => {
|
|
131
|
-
if (index === 4) {
|
|
132
|
-
screen.destroy();
|
|
133
|
-
process.exit(0);
|
|
134
|
-
} else {
|
|
135
|
-
const messageBox = blessed.message({
|
|
136
|
-
top: 'center',
|
|
137
|
-
left: 'center',
|
|
138
|
-
width: '50%',
|
|
139
|
-
height: 5,
|
|
140
|
-
border: { type: 'line' },
|
|
141
|
-
style: { fg: 'yellow', border: { fg: 'cyan' } },
|
|
142
|
-
});
|
|
143
|
-
screen.append(messageBox);
|
|
144
|
-
messageBox.display(`Loading: ${item.getText()}`, 2, () => {
|
|
145
|
-
messageBox.destroy();
|
|
146
|
-
screen.render();
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
screen.append(leftBox);
|
|
152
|
-
screen.append(rightBox);
|
|
153
|
-
screen.append(asciiLogo);
|
|
154
|
-
screen.append(menu);
|
|
155
|
-
menu.focus();
|
|
156
|
-
screen.render();
|
|
157
|
-
|
|
158
|
-
startFluidLoop(leftBox, sideWidth, sideHeight);
|
|
159
|
-
startFluidLoop(rightBox, sideWidth, sideHeight);
|
|
160
|
-
|
|
161
|
-
hasLoadedUI = true;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function destroyUI() {
|
|
165
|
-
[asciiLogo, menu, leftBox, rightBox].forEach(el => el && el.destroy());
|
|
166
|
-
asciiLogo = null;
|
|
167
|
-
menu = null;
|
|
168
|
-
leftBox = null;
|
|
169
|
-
rightBox = null;
|
|
170
|
-
hasLoadedUI = false;
|
|
171
|
-
screen.render();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function checkSizeAndUpdate() {
|
|
175
|
-
if (tooSmall()) {
|
|
176
|
-
destroyUI();
|
|
177
|
-
showError();
|
|
178
|
-
} else {
|
|
179
|
-
hideError();
|
|
180
|
-
showUI();
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// === Boot Animation ===
|
|
185
|
-
const bootLines = [
|
|
186
|
-
'Booting ACCELR8...',
|
|
187
|
-
'Verifying resident keys...',
|
|
188
|
-
'Warming up the coffee script engine...',
|
|
189
|
-
'Wiping whiteboard...',
|
|
190
|
-
'Ready.',
|
|
191
|
-
];
|
|
192
|
-
|
|
193
|
-
const bootBox = blessed.box({
|
|
194
|
-
top: 'center',
|
|
195
|
-
left: 'center',
|
|
196
|
-
width: '80%',
|
|
197
|
-
height: bootLines.length + 4,
|
|
198
|
-
tags: true,
|
|
199
|
-
border: { type: 'line' },
|
|
200
|
-
style: { fg: 'green', border: { fg: 'green' } },
|
|
201
|
-
content: '',
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
screen.append(bootBox);
|
|
205
|
-
screen.render();
|
|
206
|
-
|
|
207
|
-
let i = 0;
|
|
208
|
-
const showNextBootLine = () => {
|
|
209
|
-
if (i < bootLines.length) {
|
|
210
|
-
bootBox.setContent(bootBox.getContent() + `\n${bootLines[i++]}`);
|
|
211
|
-
screen.render();
|
|
212
|
-
setTimeout(showNextBootLine, 400);
|
|
213
|
-
} else {
|
|
214
|
-
setTimeout(() => {
|
|
215
|
-
screen.remove(bootBox);
|
|
216
|
-
screen.render();
|
|
217
|
-
checkSizeAndUpdate();
|
|
218
|
-
}, 1000);
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
showNextBootLine();
|
|
222
|
-
|
|
223
|
-
// === Resize Listener ===
|
|
224
|
-
screen.on('resize', () => {
|
|
225
|
-
checkSizeAndUpdate();
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// === Quit keys ===
|
|
229
|
-
screen.key(['escape', 'q', 'C-c'], () => {
|
|
230
|
-
screen.destroy();
|
|
231
|
-
process.exit(0);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
// === Konami Easter Egg: Launch NetHack ===
|
|
235
|
-
function launchNetHack() {
|
|
236
|
-
screen.children.forEach(child => child.destroy());
|
|
237
|
-
screen.render();
|
|
238
|
-
|
|
239
|
-
playGlitchAnimation(() => {
|
|
240
|
-
screen.destroy();
|
|
241
|
-
const game = spawn('nethack', { stdio: 'inherit' });
|
|
242
|
-
game.on('exit', code => process.exit(code));
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function playGlitchAnimation(callback) {
|
|
247
|
-
const glitchChars = ['█', '▓', '▒', '░', '#', '%', '&', '@', 'Ξ', '∆'];
|
|
248
|
-
const glitchBox = blessed.box({
|
|
249
|
-
top: 0,
|
|
250
|
-
left: 0,
|
|
251
|
-
width: '100%',
|
|
252
|
-
height: '100%',
|
|
253
|
-
tags: true,
|
|
254
|
-
style: { fg: 'magenta', bg: 'black' },
|
|
255
|
-
content: '',
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
screen.append(glitchBox);
|
|
259
|
-
let frame = 0;
|
|
260
|
-
const totalFrames = 15;
|
|
261
|
-
|
|
262
|
-
const interval = setInterval(() => {
|
|
263
|
-
let output = '';
|
|
264
|
-
for (let y = 0; y < screen.height; y++) {
|
|
265
|
-
let line = '';
|
|
266
|
-
for (let x = 0; x < screen.width; x++) {
|
|
267
|
-
line += Math.random() > 0.92 ? glitchChars[Math.floor(Math.random() * glitchChars.length)] : ' ';
|
|
268
|
-
}
|
|
269
|
-
output += line + '\n';
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
glitchBox.setContent(output);
|
|
273
|
-
screen.render();
|
|
274
|
-
frame++;
|
|
275
|
-
|
|
276
|
-
if (frame === totalFrames) {
|
|
277
|
-
clearInterval(interval);
|
|
278
|
-
glitchBox.setContent('{green-fg}ACCESS GRANTED: Launching Secret Mode...{/green-fg}');
|
|
279
|
-
glitchBox.style.fg = 'green';
|
|
280
|
-
screen.render();
|
|
281
|
-
setTimeout(callback, 1500);
|
|
282
|
-
}
|
|
283
|
-
}, 50);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// === Animated Fluid Side Panels ===
|
|
287
|
-
function startFluidLoop(box, width, height) {
|
|
288
|
-
let t = 0;
|
|
289
|
-
setInterval(() => {
|
|
290
|
-
const frame = generateFluidFrame(width - 2, height - 2, t);
|
|
291
|
-
box.setContent(frame);
|
|
292
|
-
screen.render();
|
|
293
|
-
t += 1;
|
|
294
|
-
}, 80);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function generateFluidFrame(width, height, t) {
|
|
298
|
-
let output = '';
|
|
299
|
-
for (let y = 0; y < height; y++) {
|
|
300
|
-
let line = '';
|
|
301
|
-
for (let x = 0; x < width; x++) {
|
|
302
|
-
const wave = Math.sin((x + t) / 4) + Math.cos((y + t) / 4);
|
|
303
|
-
const char = wave > 1 ? '▓' : wave > 0.3 ? '▒' : wave > -0.5 ? '░' : ' ';
|
|
304
|
-
line += `{${getColorCode(t + x + y)}-fg}${char}{/}`;
|
|
305
|
-
}
|
|
306
|
-
output += line + '\n';
|
|
307
|
-
}
|
|
308
|
-
return output;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function getColorCode(n) {
|
|
312
|
-
const colors = ['red', 'magenta', 'blue', 'cyan', 'green', 'yellow'];
|
|
313
|
-
return colors[Math.floor(n / 4) % colors.length];
|
|
314
|
-
}
|