@darksol/terminal 0.10.0 → 0.11.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 +111 -1
- package/package.json +4 -1
- package/src/browser/actions.js +58 -0
- package/src/cli.js +136 -0
- package/src/config/keys.js +12 -2
- package/src/daemon/index.js +225 -0
- package/src/daemon/manager.js +148 -0
- package/src/daemon/pid.js +80 -0
- package/src/services/browser.js +659 -0
- package/src/services/telegram.js +570 -0
- package/src/web/commands.js +135 -1
- package/src/web/server.js +21 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service registry and lifecycle manager for the daemon.
|
|
3
|
+
* Services register with the manager and can be started/stopped/queried.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const services = new Map();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Register a service with the daemon manager.
|
|
10
|
+
* @param {string} name - Unique service name (e.g. 'telegram', 'web-shell')
|
|
11
|
+
* @param {{start: Function, stop: Function, status: Function}} handler
|
|
12
|
+
*/
|
|
13
|
+
export function registerService(name, handler) {
|
|
14
|
+
if (services.has(name)) {
|
|
15
|
+
throw new Error(`Service "${name}" is already registered`);
|
|
16
|
+
}
|
|
17
|
+
services.set(name, {
|
|
18
|
+
name,
|
|
19
|
+
handler,
|
|
20
|
+
state: 'stopped',
|
|
21
|
+
startedAt: null,
|
|
22
|
+
error: null,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Unregister a service.
|
|
28
|
+
* @param {string} name
|
|
29
|
+
*/
|
|
30
|
+
export function unregisterService(name) {
|
|
31
|
+
services.delete(name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Start a registered service.
|
|
36
|
+
* @param {string} name
|
|
37
|
+
* @param {object} [opts]
|
|
38
|
+
* @returns {Promise<void>}
|
|
39
|
+
*/
|
|
40
|
+
export async function startService(name, opts = {}) {
|
|
41
|
+
const svc = services.get(name);
|
|
42
|
+
if (!svc) throw new Error(`Unknown service: ${name}`);
|
|
43
|
+
if (svc.state === 'running') return;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
svc.state = 'starting';
|
|
47
|
+
svc.error = null;
|
|
48
|
+
await svc.handler.start(opts);
|
|
49
|
+
svc.state = 'running';
|
|
50
|
+
svc.startedAt = new Date().toISOString();
|
|
51
|
+
} catch (err) {
|
|
52
|
+
svc.state = 'error';
|
|
53
|
+
svc.error = err.message;
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Stop a registered service.
|
|
60
|
+
* @param {string} name
|
|
61
|
+
* @returns {Promise<void>}
|
|
62
|
+
*/
|
|
63
|
+
export async function stopService(name) {
|
|
64
|
+
const svc = services.get(name);
|
|
65
|
+
if (!svc) return;
|
|
66
|
+
if (svc.state !== 'running' && svc.state !== 'starting') return;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await svc.handler.stop();
|
|
70
|
+
} catch {
|
|
71
|
+
// best-effort stop
|
|
72
|
+
}
|
|
73
|
+
svc.state = 'stopped';
|
|
74
|
+
svc.startedAt = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Stop all running services.
|
|
79
|
+
* @returns {Promise<void>}
|
|
80
|
+
*/
|
|
81
|
+
export async function stopAllServices() {
|
|
82
|
+
for (const [name] of services) {
|
|
83
|
+
await stopService(name);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get status of a specific service.
|
|
89
|
+
* @param {string} name
|
|
90
|
+
* @returns {object|null}
|
|
91
|
+
*/
|
|
92
|
+
export function getServiceStatus(name) {
|
|
93
|
+
const svc = services.get(name);
|
|
94
|
+
if (!svc) return null;
|
|
95
|
+
|
|
96
|
+
let extra = {};
|
|
97
|
+
if (svc.handler.status) {
|
|
98
|
+
try {
|
|
99
|
+
extra = svc.handler.status() || {};
|
|
100
|
+
} catch {
|
|
101
|
+
// ignore
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
name: svc.name,
|
|
107
|
+
state: svc.state,
|
|
108
|
+
startedAt: svc.startedAt,
|
|
109
|
+
error: svc.error,
|
|
110
|
+
...extra,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get health summary for all registered services.
|
|
116
|
+
* @returns {Array<object>}
|
|
117
|
+
*/
|
|
118
|
+
export function getAllServiceStatus() {
|
|
119
|
+
const result = [];
|
|
120
|
+
for (const [name] of services) {
|
|
121
|
+
result.push(getServiceStatus(name));
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* List names of all registered services.
|
|
128
|
+
* @returns {string[]}
|
|
129
|
+
*/
|
|
130
|
+
export function listServices() {
|
|
131
|
+
return [...services.keys()];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if a service is registered.
|
|
136
|
+
* @param {string} name
|
|
137
|
+
* @returns {boolean}
|
|
138
|
+
*/
|
|
139
|
+
export function hasService(name) {
|
|
140
|
+
return services.has(name);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Reset the manager (for testing).
|
|
145
|
+
*/
|
|
146
|
+
export function resetManager() {
|
|
147
|
+
services.clear();
|
|
148
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
|
|
5
|
+
const DARKSOL_DIR = join(homedir(), '.darksol');
|
|
6
|
+
const PID_FILE = join(DARKSOL_DIR, 'daemon.pid');
|
|
7
|
+
|
|
8
|
+
function ensureDir() {
|
|
9
|
+
if (!existsSync(DARKSOL_DIR)) mkdirSync(DARKSOL_DIR, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Write the current process PID (or a custom one) to the PID file.
|
|
14
|
+
* @param {number} [pid]
|
|
15
|
+
*/
|
|
16
|
+
export function writePid(pid) {
|
|
17
|
+
ensureDir();
|
|
18
|
+
writeFileSync(PID_FILE, String(pid || process.pid), 'utf8');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Read the stored PID. Returns null if no PID file exists.
|
|
23
|
+
* @returns {number|null}
|
|
24
|
+
*/
|
|
25
|
+
export function readPid() {
|
|
26
|
+
if (!existsSync(PID_FILE)) return null;
|
|
27
|
+
try {
|
|
28
|
+
const raw = readFileSync(PID_FILE, 'utf8').trim();
|
|
29
|
+
const pid = parseInt(raw, 10);
|
|
30
|
+
return Number.isFinite(pid) ? pid : null;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Remove the PID file.
|
|
38
|
+
*/
|
|
39
|
+
export function removePid() {
|
|
40
|
+
try {
|
|
41
|
+
if (existsSync(PID_FILE)) unlinkSync(PID_FILE);
|
|
42
|
+
} catch {
|
|
43
|
+
// ignore - file may already be gone
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if a process with the given PID is alive.
|
|
49
|
+
* @param {number} pid
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
export function isProcessAlive(pid) {
|
|
53
|
+
if (!pid) return false;
|
|
54
|
+
try {
|
|
55
|
+
process.kill(pid, 0);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if the daemon is currently running.
|
|
64
|
+
* Cleans up stale PID files.
|
|
65
|
+
* @returns {{running: boolean, pid: number|null}}
|
|
66
|
+
*/
|
|
67
|
+
export function getDaemonStatus() {
|
|
68
|
+
const pid = readPid();
|
|
69
|
+
if (!pid) return { running: false, pid: null };
|
|
70
|
+
|
|
71
|
+
if (isProcessAlive(pid)) {
|
|
72
|
+
return { running: true, pid };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Stale PID file — process is gone
|
|
76
|
+
removePid();
|
|
77
|
+
return { running: false, pid: null };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { PID_FILE, DARKSOL_DIR };
|