@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.
@@ -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 };