klio 1.4.1 ā 1.4.2
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 +28 -0
- package/commands.db +0 -0
- package/package.json +4 -1
- package/src/cli/cli.js +37 -0
- package/src/gui/commandLogger.js +67 -0
- package/src/gui/database.js +135 -0
- package/src/gui/public/index.html +381 -0
- package/src/gui/routes/api.js +134 -0
- package/src/gui/server.js +82 -0
package/README.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
A command-line tool for astrological calculations, health analysis, and personalized astrology insights.
|
|
4
4
|
|
|
5
|
+
**Features:**
|
|
6
|
+
- Command-line interface for astrological calculations
|
|
7
|
+
- Web-based GUI for easy exploration
|
|
8
|
+
- Health data analysis from Apple Health exports
|
|
9
|
+
- CSV data analysis with statistical testing
|
|
10
|
+
- Personalized astrology with multiple chart support
|
|
11
|
+
- AI integration for astrological insights
|
|
12
|
+
|
|
5
13
|
## Prerequisites
|
|
6
14
|
|
|
7
15
|
### For Linux Users
|
|
@@ -28,6 +36,26 @@ npm install -g klio
|
|
|
28
36
|
klio [options]
|
|
29
37
|
```
|
|
30
38
|
|
|
39
|
+
### Web Interface
|
|
40
|
+
|
|
41
|
+
AstroCLI includes a web-based GUI that you can launch with the `--gui` flag:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
klio --gui
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This will start a web server on port 37421 (or a different port if specified) and open a browser interface where you can:
|
|
48
|
+
|
|
49
|
+
- **Run commands** directly from the browser
|
|
50
|
+
- **View command output** in a formatted display
|
|
51
|
+
- **Browse all available commands** with examples and documentation
|
|
52
|
+
- **Access command history** to reuse previous commands
|
|
53
|
+
|
|
54
|
+
**Custom Port:**
|
|
55
|
+
```bash
|
|
56
|
+
klio --gui --gui-port 8080
|
|
57
|
+
```
|
|
58
|
+
|
|
31
59
|
### Configuration
|
|
32
60
|
|
|
33
61
|
- **Show status**: `--status` - Shows the stored configuration data
|
package/commands.db
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klio",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "A CLI for astrological calculations",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,8 +23,11 @@
|
|
|
23
23
|
"commander": "^14.0.3",
|
|
24
24
|
"csv": "^6.4.1",
|
|
25
25
|
"csv-parser": "^3.0.0",
|
|
26
|
+
"express": "^4.19.2",
|
|
26
27
|
"fast-xml-parser": "^5.3.4",
|
|
27
28
|
"moment-timezone": "^0.6.0",
|
|
29
|
+
"node-fetch": "^3.3.2",
|
|
30
|
+
"sqlite3": "^5.1.7",
|
|
28
31
|
"swisseph": "^0.5.17"
|
|
29
32
|
}
|
|
30
33
|
}
|
package/src/cli/cli.js
CHANGED
|
@@ -11,6 +11,15 @@ const swisseph = require('swisseph');
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
|
|
14
|
+
// GUI Server import
|
|
15
|
+
let guiServer = null;
|
|
16
|
+
try {
|
|
17
|
+
guiServer = require('../gui/server');
|
|
18
|
+
} catch (error) {
|
|
19
|
+
// GUI server not available (optional feature)
|
|
20
|
+
console.debug('GUI server not available');
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
const program = new Command();
|
|
15
24
|
|
|
16
25
|
// Helper function to check if person data should be used (including new options)
|
|
@@ -245,11 +254,39 @@ program
|
|
|
245
254
|
.option('--v <count>', 'Shows past aspects between two planets (Format: --v <count> planet1 aspectType planet2)')
|
|
246
255
|
.option('--z <count>', 'Shows future aspects between two planets (Format: --z <count> planet1 aspectType planet2)')
|
|
247
256
|
.option('--csv <filepath>', 'Analyzes a CSV file with ISO-Datetime values or Unix timestamps')
|
|
257
|
+
.option('--gui', 'Launches the web interface for command history (port 37421)')
|
|
258
|
+
.option('--gui-port <port>', 'Specify custom port for GUI server')
|
|
248
259
|
.description('Shows astrological data for a planet')
|
|
249
260
|
.action(async (planetArg, planet2Arg, options) => {
|
|
250
261
|
// If planet2Arg is an object, it contains the options (commander behavior)
|
|
251
262
|
let planet2 = typeof planet2Arg === 'string' ? planet2Arg : null;
|
|
252
263
|
let actualOptions = typeof planet2Arg === 'object' ? planet2Arg : options;
|
|
264
|
+
|
|
265
|
+
// Handle GUI launch if --gui flag is specified
|
|
266
|
+
if (options.gui) {
|
|
267
|
+
if (!guiServer) {
|
|
268
|
+
console.error('ā GUI server is not available. Please install required dependencies.');
|
|
269
|
+
console.error('š” Run: npm install express sqlite3');
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
await guiServer.initialize();
|
|
275
|
+
const port = options.guiPort ? parseInt(options.guiPort) : 37421;
|
|
276
|
+
await guiServer.start(port);
|
|
277
|
+
|
|
278
|
+
console.log(`\nš GUI Server started successfully!`);
|
|
279
|
+
console.log(`š Open your browser and navigate to: http://localhost:${guiServer.getPort()}`);
|
|
280
|
+
console.log(`š” Press Ctrl+C to stop the server and return to CLI`);
|
|
281
|
+
|
|
282
|
+
// Keep the process running
|
|
283
|
+
await new Promise(() => {});
|
|
284
|
+
return;
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.error('ā Failed to start GUI server:', err.message);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
253
290
|
|
|
254
291
|
// Show critical planets if --c option is specified (no planet required)
|
|
255
292
|
if (actualOptions.c) {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const commandDatabase = require('./database');
|
|
2
|
+
|
|
3
|
+
class CommandLogger {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.isInitialized = false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async initialize() {
|
|
9
|
+
try {
|
|
10
|
+
await commandDatabase.initialize();
|
|
11
|
+
this.isInitialized = true;
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.error('Failed to initialize command logger:', err);
|
|
14
|
+
this.isInitialized = false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async logCommand(command, arguments = '', category = 'general', tags = '') {
|
|
19
|
+
if (!this.isInitialized) {
|
|
20
|
+
await this.initialize();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!this.isInitialized) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Capture the command result by temporarily overriding console.log
|
|
29
|
+
const originalLog = console.log;
|
|
30
|
+
let capturedOutput = '';
|
|
31
|
+
|
|
32
|
+
console.log = (...args) => {
|
|
33
|
+
capturedOutput += args.join(' ') + '\n';
|
|
34
|
+
originalLog(...args);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Execute the command (this would be called from CLI)
|
|
38
|
+
// For now, we'll just save the command structure
|
|
39
|
+
|
|
40
|
+
const commandId = await commandDatabase.saveCommand(
|
|
41
|
+
command,
|
|
42
|
+
arguments,
|
|
43
|
+
'', // Result will be captured later
|
|
44
|
+
tags,
|
|
45
|
+
category
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Restore original console.log
|
|
49
|
+
console.log = originalLog;
|
|
50
|
+
|
|
51
|
+
return commandId;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.error('Error logging command:', err);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async close() {
|
|
59
|
+
try {
|
|
60
|
+
await commandDatabase.close();
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error('Error closing command logger:', err);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = new CommandLogger();
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const sqlite3 = require('sqlite3').verbose();
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
class CommandDatabase {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.dbPath = path.join(__dirname, '..', '..', 'commands.db');
|
|
8
|
+
this.db = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async initialize() {
|
|
12
|
+
// Create directory if it doesn't exist
|
|
13
|
+
const dbDir = path.dirname(this.dbPath);
|
|
14
|
+
if (!fs.existsSync(dbDir)) {
|
|
15
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.db = new sqlite3.Database(this.dbPath);
|
|
19
|
+
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
this.db.serialize(() => {
|
|
22
|
+
this.db.run(`
|
|
23
|
+
CREATE TABLE IF NOT EXISTS commands (
|
|
24
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
26
|
+
command TEXT NOT NULL,
|
|
27
|
+
arguments TEXT,
|
|
28
|
+
result TEXT,
|
|
29
|
+
tags TEXT,
|
|
30
|
+
category TEXT
|
|
31
|
+
)
|
|
32
|
+
`);
|
|
33
|
+
|
|
34
|
+
// Create indexes for better search performance
|
|
35
|
+
this.db.run('CREATE INDEX IF NOT EXISTS idx_command ON commands(command)');
|
|
36
|
+
this.db.run('CREATE INDEX IF NOT EXISTS idx_timestamp ON commands(timestamp)');
|
|
37
|
+
this.db.run('CREATE INDEX IF NOT EXISTS idx_tags ON commands(tags)');
|
|
38
|
+
this.db.run('CREATE INDEX IF NOT EXISTS idx_category ON commands(category)');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
resolve();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async saveCommand(command, args = '', result = '', tags = '', category = '') {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const stmt = this.db.prepare(
|
|
48
|
+
'INSERT INTO commands (command, arguments, result, tags, category) VALUES (?, ?, ?, ?, ?)'
|
|
49
|
+
);
|
|
50
|
+
stmt.run([command, args, result, tags, category], function(err) {
|
|
51
|
+
if (err) {
|
|
52
|
+
reject(err);
|
|
53
|
+
} else {
|
|
54
|
+
resolve(this.lastID);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
stmt.finalize();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async getAllCommands() {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
this.db.all('SELECT * FROM commands ORDER BY timestamp DESC', (err, rows) => {
|
|
64
|
+
if (err) {
|
|
65
|
+
reject(err);
|
|
66
|
+
} else {
|
|
67
|
+
resolve(rows);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async searchCommands(query) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const searchQuery = `%${query}%`;
|
|
76
|
+
this.db.all(`
|
|
77
|
+
SELECT * FROM commands
|
|
78
|
+
WHERE command LIKE ?
|
|
79
|
+
OR arguments LIKE ?
|
|
80
|
+
OR result LIKE ?
|
|
81
|
+
OR tags LIKE ?
|
|
82
|
+
OR category LIKE ?
|
|
83
|
+
ORDER BY timestamp DESC
|
|
84
|
+
`, [searchQuery, searchQuery, searchQuery, searchQuery, searchQuery], (err, rows) => {
|
|
85
|
+
if (err) {
|
|
86
|
+
reject(err);
|
|
87
|
+
} else {
|
|
88
|
+
resolve(rows);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async getCommandById(id) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
this.db.get('SELECT * FROM commands WHERE id = ?', [id], (err, row) => {
|
|
97
|
+
if (err) {
|
|
98
|
+
reject(err);
|
|
99
|
+
} else {
|
|
100
|
+
resolve(row);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async getCommandsByCategory(category) {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
this.db.all('SELECT * FROM commands WHERE category = ? ORDER BY timestamp DESC', [category], (err, rows) => {
|
|
109
|
+
if (err) {
|
|
110
|
+
reject(err);
|
|
111
|
+
} else {
|
|
112
|
+
resolve(rows);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async close() {
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
if (this.db) {
|
|
121
|
+
this.db.close((err) => {
|
|
122
|
+
if (err) {
|
|
123
|
+
reject(err);
|
|
124
|
+
} else {
|
|
125
|
+
resolve();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
resolve();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = new CommandDatabase();
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Klio GUI</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<style>
|
|
9
|
+
body {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
11
|
+
}
|
|
12
|
+
.command-input {
|
|
13
|
+
font-family: 'Courier New', monospace;
|
|
14
|
+
}
|
|
15
|
+
.output-area {
|
|
16
|
+
font-family: 'Courier New', monospace;
|
|
17
|
+
white-space: pre-wrap;
|
|
18
|
+
}
|
|
19
|
+
.nav-link {
|
|
20
|
+
transition: all 0.2s ease;
|
|
21
|
+
}
|
|
22
|
+
.nav-link:hover {
|
|
23
|
+
background-color: rgba(59, 130, 246, 0.1);
|
|
24
|
+
}
|
|
25
|
+
.nav-link.active {
|
|
26
|
+
background-color: rgba(59, 130, 246, 0.2);
|
|
27
|
+
border-left: 3px solid #3b82f6;
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
30
|
+
</head>
|
|
31
|
+
<body class="bg-gray-50 min-h-screen">
|
|
32
|
+
<div class="flex min-h-screen">
|
|
33
|
+
<!-- Sidebar Navigation -->
|
|
34
|
+
<div class="w-64 bg-white border-r border-gray-200 flex flex-col">
|
|
35
|
+
<div class="p-4 border-b border-gray-200">
|
|
36
|
+
<h1 class="text-xl font-bold text-gray-800">Klio GUI</h1>
|
|
37
|
+
<p class="text-sm text-gray-600">Web Interface</p>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<nav class="flex-1 p-4">
|
|
41
|
+
<ul class="space-y-2">
|
|
42
|
+
<li>
|
|
43
|
+
<a href="#" onclick="showPage('runner')" id="nav-runner" class="nav-link flex items-center p-3 rounded-lg text-gray-700 active">
|
|
44
|
+
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
45
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
|
|
46
|
+
</svg>
|
|
47
|
+
Command Runner
|
|
48
|
+
</a>
|
|
49
|
+
</li>
|
|
50
|
+
<li>
|
|
51
|
+
<a href="#" onclick="showPage('commands')" id="nav-commands" class="nav-link flex items-center p-3 rounded-lg text-gray-700">
|
|
52
|
+
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
53
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
54
|
+
</svg>
|
|
55
|
+
Commands Reference
|
|
56
|
+
</a>
|
|
57
|
+
</li>
|
|
58
|
+
<li>
|
|
59
|
+
<a href="#" onclick="showPage('history')" id="nav-history" class="nav-link flex items-center p-3 rounded-lg text-gray-700">
|
|
60
|
+
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
61
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
62
|
+
</svg>
|
|
63
|
+
Command History
|
|
64
|
+
</a>
|
|
65
|
+
</li>
|
|
66
|
+
</ul>
|
|
67
|
+
</nav>
|
|
68
|
+
|
|
69
|
+
<div class="p-4 border-t border-gray-200">
|
|
70
|
+
<div class="text-xs text-gray-500">
|
|
71
|
+
<p>Klio GUI v0.1</p>
|
|
72
|
+
<p>GUI Interface</p>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<!-- Main Content -->
|
|
78
|
+
<div class="flex-1 flex flex-col">
|
|
79
|
+
<!-- Header -->
|
|
80
|
+
<header class="bg-white border-b border-gray-200 p-4">
|
|
81
|
+
<div class="flex items-center justify-between">
|
|
82
|
+
<h1 class="text-lg font-semibold text-gray-800" id="page-title">Command Runner</h1>
|
|
83
|
+
</div>
|
|
84
|
+
</header>
|
|
85
|
+
|
|
86
|
+
<!-- Page Content -->
|
|
87
|
+
<main class="flex-1 overflow-y-auto p-6">
|
|
88
|
+
<!-- Command Runner Page -->
|
|
89
|
+
<div id="runner-page">
|
|
90
|
+
<div class="bg-white shadow-sm rounded-lg p-6 mb-6">
|
|
91
|
+
<div class="mb-4">
|
|
92
|
+
<label for="commandInput" class="block text-sm font-medium text-gray-700 mb-2">Command</label>
|
|
93
|
+
<div class="flex gap-2">
|
|
94
|
+
<input
|
|
95
|
+
type="text"
|
|
96
|
+
id="commandInput"
|
|
97
|
+
placeholder="e.g., moon --s"
|
|
98
|
+
class="flex-1 command-input px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
99
|
+
>
|
|
100
|
+
<button
|
|
101
|
+
onclick="runCommand()"
|
|
102
|
+
class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
|
103
|
+
>
|
|
104
|
+
Run
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="text-sm text-gray-600">
|
|
109
|
+
<p class="mb-2">Examples:</p>
|
|
110
|
+
<div class="flex flex-wrap gap-4 text-xs">
|
|
111
|
+
<button onclick="commandInput.value = '--v 3 saturn neptune'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">moon --s</button>
|
|
112
|
+
<button onclick="commandInput.value = '--rx --i'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">--rx</button>
|
|
113
|
+
<button onclick="commandInput.value = '--c'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">--c</button>
|
|
114
|
+
<button onclick="commandInput.value = 'mars venus --k'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">mars venus --k</button>
|
|
115
|
+
<button onclick="commandInput.value = '--el'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">--el</button>
|
|
116
|
+
<button onclick="commandInput.value = '--s'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">--el</button>
|
|
117
|
+
<button onclick="commandInput.value = '--a'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">--el</button>
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div class="bg-white shadow-sm rounded-lg p-6">
|
|
125
|
+
<h2 class="text-lg font-semibold text-gray-800 mb-4">Output</h2>
|
|
126
|
+
<div id="outputArea" class="output-area bg-gray-100 p-4 rounded border min-h-[300px]">
|
|
127
|
+
Command output will appear here...
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<!-- Commands Reference Page -->
|
|
133
|
+
<div id="commands-page" class="hidden">
|
|
134
|
+
<div class="bg-white shadow-sm rounded-lg p-6">
|
|
135
|
+
<h2 class="text-lg font-semibold text-gray-800 mb-4">Available Commands</h2>
|
|
136
|
+
<div class="prose max-w-none text-sm">
|
|
137
|
+
<h3 class="text-base font-semibold text-gray-800 mt-6 mb-3">Basic Astrological Data</h3>
|
|
138
|
+
<ul class="list-disc pl-6 space-y-2">
|
|
139
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">klio [planet]</code> - Displays astrological data for a specific planet</li>
|
|
140
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">klio [planet] --hs koch</code> - Shows planet data with house and house system</li>
|
|
141
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">klio [planet] [planet2] --k</code> - Shows aspects between two planets</li>
|
|
142
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">klio --a</code> - Shows all current aspects</li>
|
|
143
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">klio --s</code> - Shows all planet positions</li>
|
|
144
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">klio --s --hs placidus</code> - Different house systems</li>
|
|
145
|
+
</ul>
|
|
146
|
+
|
|
147
|
+
<h3 class="text-base font-semibold text-gray-800 mt-6 mb-3">House Systems</h3>
|
|
148
|
+
<ul class="list-disc pl-6 space-y-2">
|
|
149
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--hs <system></code> - Shows planet + house position (Placidus, Koch, Porphyry, Regiomontanus, Campanus, Equal, WholeSign, Gauquelin, Vehlow, Topocentric, Alcabitius, Morinus)</li>
|
|
150
|
+
</ul>
|
|
151
|
+
|
|
152
|
+
<h3 class="text-base font-semibold text-gray-800 mt-6 mb-3">Date and Time</h3>
|
|
153
|
+
<ul class="list-disc pl-6 space-y-2">
|
|
154
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--d <date></code> - Use a specific date (Format: DD.MM.YYYY or "DD.MM.YYYY HH:MM")</li>
|
|
155
|
+
</ul>
|
|
156
|
+
|
|
157
|
+
<h3 class="text-base font-semibold text-gray-800 mt-6 mb-3">Advanced Features</h3>
|
|
158
|
+
<ul class="list-disc pl-6 space-y-2">
|
|
159
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--rx</code> - Shows all retrograde or stationary planets</li>
|
|
160
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--c</code> - Shows all planets on critical degrees</li>
|
|
161
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--el</code> - Shows element distribution of planets</li>
|
|
162
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--af</code> - Shows active aspect figures</li>
|
|
163
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--tr</code> - Shows personal transits based on birth data</li>
|
|
164
|
+
</ul>
|
|
165
|
+
|
|
166
|
+
<h3 class="text-base font-semibold text-gray-800 mt-6 mb-3">Past and Future Aspects</h3>
|
|
167
|
+
<ul class="list-disc pl-6 space-y-2">
|
|
168
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--v <count> [planet1] [aspect-type] [planet2]</code> - Shows past aspects</li>
|
|
169
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--z <count> [planet1] [aspect-type] [planet2]</code> - Shows future aspects</li>
|
|
170
|
+
</ul>
|
|
171
|
+
|
|
172
|
+
<h3 class="text-base font-semibold text-gray-800 mt-6 mb-3">Configuration</h3>
|
|
173
|
+
<ul class="list-disc pl-6 space-y-2">
|
|
174
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--setup</code> - Setup for a default chart</li>
|
|
175
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--status</code> - Shows the stored configuration data</li>
|
|
176
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--i</code> - Uses birth data from setup</li>
|
|
177
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--person <id> <data></code> - Creates or updates any person</li>
|
|
178
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">--people</code> - Lists all saved persons</li>
|
|
179
|
+
</ul>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<!-- Command History Page -->
|
|
185
|
+
<div id="history-page" class="hidden">
|
|
186
|
+
<div class="bg-white shadow-sm rounded-lg p-6">
|
|
187
|
+
<h2 class="text-lg font-semibold text-gray-800 mb-4">Command History</h2>
|
|
188
|
+
<div id="historyContainer" class="space-y-2 max-h-[500px] overflow-y-auto pr-2"></div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</main>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div id="toast" class="fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-2 rounded-md shadow-lg opacity-0 transform translate-y-4 transition-all duration-300"></div>
|
|
196
|
+
|
|
197
|
+
<script>
|
|
198
|
+
let commandHistory = [];
|
|
199
|
+
const maxHistory = 50;
|
|
200
|
+
|
|
201
|
+
// DOM elements
|
|
202
|
+
const commandInput = document.getElementById('commandInput');
|
|
203
|
+
const historyContainer = document.getElementById('historyContainer');
|
|
204
|
+
const outputArea = document.getElementById('outputArea');
|
|
205
|
+
const toastElement = document.getElementById('toast');
|
|
206
|
+
|
|
207
|
+
// Initialize the app
|
|
208
|
+
function init() {
|
|
209
|
+
loadHistory();
|
|
210
|
+
|
|
211
|
+
// Add event listener for Enter key
|
|
212
|
+
commandInput.addEventListener('keypress', (e) => {
|
|
213
|
+
if (e.key === 'Enter') {
|
|
214
|
+
runCommand();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Focus on command input
|
|
219
|
+
commandInput.focus();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Page navigation function
|
|
223
|
+
function showPage(pageName) {
|
|
224
|
+
// Hide all pages
|
|
225
|
+
document.getElementById('runner-page').classList.add('hidden');
|
|
226
|
+
document.getElementById('commands-page').classList.add('hidden');
|
|
227
|
+
document.getElementById('history-page').classList.add('hidden');
|
|
228
|
+
|
|
229
|
+
// Remove active state from all nav items
|
|
230
|
+
document.getElementById('nav-runner').classList.remove('active');
|
|
231
|
+
document.getElementById('nav-commands').classList.remove('active');
|
|
232
|
+
document.getElementById('nav-history').classList.remove('active');
|
|
233
|
+
|
|
234
|
+
// Show selected page
|
|
235
|
+
if (pageName === 'runner') {
|
|
236
|
+
document.getElementById('runner-page').classList.remove('hidden');
|
|
237
|
+
document.getElementById('nav-runner').classList.add('active');
|
|
238
|
+
document.getElementById('page-title').textContent = 'Command Runner';
|
|
239
|
+
} else if (pageName === 'commands') {
|
|
240
|
+
document.getElementById('commands-page').classList.remove('hidden');
|
|
241
|
+
document.getElementById('nav-commands').classList.add('active');
|
|
242
|
+
document.getElementById('page-title').textContent = 'Commands Reference';
|
|
243
|
+
} else if (pageName === 'history') {
|
|
244
|
+
document.getElementById('history-page').classList.remove('hidden');
|
|
245
|
+
document.getElementById('nav-history').classList.add('active');
|
|
246
|
+
document.getElementById('page-title').textContent = 'Command History';
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Load history from localStorage
|
|
251
|
+
function loadHistory() {
|
|
252
|
+
try {
|
|
253
|
+
const savedHistory = localStorage.getItem('astrocliHistory');
|
|
254
|
+
if (savedHistory) {
|
|
255
|
+
commandHistory = JSON.parse(savedHistory);
|
|
256
|
+
renderHistory();
|
|
257
|
+
}
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error('Error loading history:', error);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Save history to localStorage
|
|
264
|
+
function saveHistory() {
|
|
265
|
+
try {
|
|
266
|
+
localStorage.setItem('astrocliHistory', JSON.stringify(commandHistory));
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error('Error saving history:', error);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Add command to history
|
|
273
|
+
function addToHistory(command) {
|
|
274
|
+
// Remove duplicate if exists
|
|
275
|
+
commandHistory = commandHistory.filter(cmd => cmd !== command);
|
|
276
|
+
|
|
277
|
+
// Add new command to beginning
|
|
278
|
+
commandHistory.unshift(command);
|
|
279
|
+
|
|
280
|
+
// Limit history size
|
|
281
|
+
if (commandHistory.length > maxHistory) {
|
|
282
|
+
commandHistory = commandHistory.slice(0, maxHistory);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
saveHistory();
|
|
286
|
+
renderHistory();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Render history
|
|
290
|
+
function renderHistory() {
|
|
291
|
+
if (commandHistory.length === 0) {
|
|
292
|
+
historyContainer.innerHTML = '<div class="text-gray-500 text-sm">No command history yet</div>';
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let html = '<div class="space-y-1">';
|
|
297
|
+
commandHistory.forEach((cmd, index) => {
|
|
298
|
+
html += `<div
|
|
299
|
+
class="command-input px-3 py-2 bg-gray-100 rounded cursor-pointer hover:bg-gray-200 text-sm truncate"
|
|
300
|
+
title="${cmd}"
|
|
301
|
+
onclick="commandInput.value = '${cmd.replace(/'/g, "\\'")}'; showPage('runner'); commandInput.focus()"
|
|
302
|
+
>${cmd}</div>`;
|
|
303
|
+
});
|
|
304
|
+
html += '</div>';
|
|
305
|
+
|
|
306
|
+
historyContainer.innerHTML = html;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Run command
|
|
310
|
+
async function runCommand() {
|
|
311
|
+
const command = commandInput.value.trim();
|
|
312
|
+
|
|
313
|
+
if (!command) {
|
|
314
|
+
showToast('Please enter a command');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
// Show loading state
|
|
320
|
+
outputArea.textContent = 'Running command...\n\n';
|
|
321
|
+
|
|
322
|
+
// Add to history
|
|
323
|
+
addToHistory(command);
|
|
324
|
+
|
|
325
|
+
// Clear input for next command
|
|
326
|
+
commandInput.value = '';
|
|
327
|
+
|
|
328
|
+
// Run the command via API
|
|
329
|
+
const response = await fetch('/api/run-command', {
|
|
330
|
+
method: 'POST',
|
|
331
|
+
headers: {
|
|
332
|
+
'Content-Type': 'application/json'
|
|
333
|
+
},
|
|
334
|
+
body: JSON.stringify({ command })
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const result = await response.json();
|
|
338
|
+
|
|
339
|
+
if (response.ok) {
|
|
340
|
+
// Display the result with better formatting
|
|
341
|
+
if (result.output) {
|
|
342
|
+
const cleanedOutput = result.output
|
|
343
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
344
|
+
.trim();
|
|
345
|
+
outputArea.textContent = cleanedOutput;
|
|
346
|
+
} else {
|
|
347
|
+
outputArea.textContent = 'Command executed successfully';
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
let errorMessage = 'Error: ' + result.error;
|
|
351
|
+
if (result.details) {
|
|
352
|
+
errorMessage += '\n' + result.details;
|
|
353
|
+
}
|
|
354
|
+
outputArea.textContent = errorMessage;
|
|
355
|
+
showToast('Command failed');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
} catch (error) {
|
|
359
|
+
console.error('Error running command:', error);
|
|
360
|
+
outputArea.textContent = 'Error: ' + error.message;
|
|
361
|
+
showToast('Command failed');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Show toast message
|
|
366
|
+
function showToast(message) {
|
|
367
|
+
toastElement.textContent = message;
|
|
368
|
+
toastElement.classList.remove('opacity-0', 'translate-y-4');
|
|
369
|
+
toastElement.classList.add('opacity-100', 'translate-y-0');
|
|
370
|
+
|
|
371
|
+
setTimeout(() => {
|
|
372
|
+
toastElement.classList.add('opacity-0', 'translate-y-4');
|
|
373
|
+
toastElement.classList.remove('opacity-100', 'translate-y-0');
|
|
374
|
+
}, 3000);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Initialize the app when DOM is loaded
|
|
378
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
379
|
+
</script>
|
|
380
|
+
</body>
|
|
381
|
+
</html>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const commandDatabase = require('../database');
|
|
4
|
+
const { exec } = require('child_process');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
// Initialize database
|
|
8
|
+
commandDatabase.initialize().catch(err => {
|
|
9
|
+
console.error('Failed to initialize database:', err);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Get all commands
|
|
13
|
+
router.get('/commands', async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const commands = await commandDatabase.getAllCommands();
|
|
16
|
+
res.json(commands);
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error('Error fetching commands:', err);
|
|
19
|
+
res.status(500).json({ error: 'Failed to fetch commands' });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Search commands
|
|
24
|
+
router.get('/commands/search', async (req, res) => {
|
|
25
|
+
const query = req.query.q || '';
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const results = await commandDatabase.searchCommands(query);
|
|
29
|
+
res.json(results);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error('Error searching commands:', err);
|
|
32
|
+
res.status(500).json({ error: 'Failed to search commands' });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Get specific command by ID
|
|
37
|
+
router.get('/commands/:id', async (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const command = await commandDatabase.getCommandById(req.params.id);
|
|
40
|
+
if (command) {
|
|
41
|
+
res.json(command);
|
|
42
|
+
} else {
|
|
43
|
+
res.status(404).json({ error: 'Command not found' });
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error('Error fetching command:', err);
|
|
47
|
+
res.status(500).json({ error: 'Failed to fetch command' });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Get commands by category
|
|
52
|
+
router.get('/commands/category/:category', async (req, res) => {
|
|
53
|
+
try {
|
|
54
|
+
const commands = await commandDatabase.getCommandsByCategory(req.params.category);
|
|
55
|
+
res.json(commands);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error('Error fetching commands by category:', err);
|
|
58
|
+
res.status(500).json({ error: 'Failed to fetch commands by category' });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Save a new command
|
|
63
|
+
router.post('/commands', async (req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
const { command, args, result, tags, category } = req.body;
|
|
66
|
+
|
|
67
|
+
if (!command) {
|
|
68
|
+
return res.status(400).json({ error: 'Command is required' });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const commandId = await commandDatabase.saveCommand(command, args, result, tags, category);
|
|
72
|
+
res.status(201).json({ id: commandId, message: 'Command saved successfully' });
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error('Error saving command:', err);
|
|
75
|
+
res.status(500).json({ error: 'Failed to save command' });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Health check endpoint
|
|
80
|
+
router.get('/health', (req, res) => {
|
|
81
|
+
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Run command endpoint
|
|
85
|
+
router.post('/run-command', async (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
const { command } = req.body;
|
|
88
|
+
|
|
89
|
+
if (!command) {
|
|
90
|
+
return res.status(400).json({ error: 'Command is required' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Execute the command using child_process
|
|
94
|
+
const commandToRun = command.startsWith('klio ') ? command : `klio ${command}`;
|
|
95
|
+
const mainJsPath = path.join(__dirname, '..', '..', '..', 'src', 'main.js');
|
|
96
|
+
|
|
97
|
+
exec(`node ${mainJsPath} ${command}`, {
|
|
98
|
+
cwd: path.join(__dirname, '..', '..', '..'),
|
|
99
|
+
maxBuffer: 1024 * 1024 * 10 // 10MB buffer
|
|
100
|
+
}, (error, stdout, stderr) => {
|
|
101
|
+
if (error) {
|
|
102
|
+
console.error('Command execution error:', error);
|
|
103
|
+
console.error('Stderr:', stderr);
|
|
104
|
+
return res.status(500).json({
|
|
105
|
+
error: 'Command execution failed',
|
|
106
|
+
details: stderr || error.message,
|
|
107
|
+
command: commandToRun
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Save command to history
|
|
112
|
+
commandDatabase.saveCommand(
|
|
113
|
+
command,
|
|
114
|
+
'',
|
|
115
|
+
stdout,
|
|
116
|
+
'',
|
|
117
|
+
'executed'
|
|
118
|
+
).catch(err => {
|
|
119
|
+
console.error('Failed to save command to history:', err);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
res.json({
|
|
123
|
+
output: stdout,
|
|
124
|
+
command: command
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.error('Error in run-command endpoint:', err);
|
|
130
|
+
res.status(500).json({ error: 'Failed to execute command' });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
module.exports = router;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const apiRouter = require('./routes/api');
|
|
4
|
+
const commandDatabase = require('./database');
|
|
5
|
+
|
|
6
|
+
class GUIServer {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.app = express();
|
|
9
|
+
this.server = null;
|
|
10
|
+
this.port = 37421; // Less common port
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async initialize() {
|
|
14
|
+
// Initialize database
|
|
15
|
+
await commandDatabase.initialize();
|
|
16
|
+
|
|
17
|
+
// Middleware
|
|
18
|
+
this.app.use(express.json());
|
|
19
|
+
this.app.use(express.urlencoded({ extended: true }));
|
|
20
|
+
|
|
21
|
+
// API routes
|
|
22
|
+
this.app.use('/api', apiRouter);
|
|
23
|
+
|
|
24
|
+
// Serve static files from public directory
|
|
25
|
+
this.app.use(express.static(path.join(__dirname, 'public')));
|
|
26
|
+
|
|
27
|
+
// Serve index.html for all other routes (for SPA behavior)
|
|
28
|
+
this.app.get('*', (req, res) => {
|
|
29
|
+
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Error handling middleware
|
|
33
|
+
this.app.use((err, req, res, next) => {
|
|
34
|
+
console.error('Server error:', err);
|
|
35
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
start(port = this.port) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
this.server = this.app.listen(port, () => {
|
|
42
|
+
console.log(`š GUI Server running on http://localhost:${port}`);
|
|
43
|
+
console.log(`š Command history available at http://localhost:${port}`);
|
|
44
|
+
console.log('š” Press Ctrl+C to stop the server');
|
|
45
|
+
resolve();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this.server.on('error', (err) => {
|
|
49
|
+
if (err.code === 'EADDRINUSE') {
|
|
50
|
+
console.error(`Port ${port} is already in use. Trying port ${port + 1}...`);
|
|
51
|
+
this.start(port + 1).then(resolve).catch(reject);
|
|
52
|
+
} else {
|
|
53
|
+
reject(err);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async stop() {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
if (this.server) {
|
|
62
|
+
this.server.close(async () => {
|
|
63
|
+
await commandDatabase.close();
|
|
64
|
+
console.log('GUI Server stopped');
|
|
65
|
+
resolve();
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
resolve();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getPort() {
|
|
74
|
+
if (this.server) {
|
|
75
|
+
const address = this.server.address();
|
|
76
|
+
return address && address.port;
|
|
77
|
+
}
|
|
78
|
+
return this.port;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = new GUIServer();
|