@markus-global/cli 0.5.10 → 0.6.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/dist/tray.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Cross-platform system tray controller for Markus.
3
+ * Provides start/stop controls and quick access to the Web UI.
4
+ *
5
+ * Launched by the desktop shortcut / .app instead of `markus start` directly.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=tray.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tray.d.ts","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
package/dist/tray.js ADDED
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Cross-platform system tray controller for Markus.
3
+ * Provides start/stop controls and quick access to the Web UI.
4
+ *
5
+ * Launched by the desktop shortcut / .app instead of `markus start` directly.
6
+ */
7
+ import { spawn, exec } from 'node:child_process';
8
+ import { readFileSync, existsSync, mkdirSync } from 'node:fs';
9
+ import { resolve, dirname } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { platform, homedir } from 'node:os';
12
+ import SysTrayModule from 'systray2';
13
+ // systray2 is CJS; default export is the class
14
+ const SysTray = SysTrayModule.default || SysTrayModule;
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ const WEB_UI_PORT = 8056;
17
+ const WEB_UI_URL = `http://localhost:${WEB_UI_PORT}`;
18
+ // Resolve paths relative to the binary distribution layout:
19
+ // bin/tray.mjs (this file)
20
+ // bin/node (bundled node)
21
+ // bin/markus.mjs
22
+ const BIN_DIR = __dirname;
23
+ const APP_DIR = resolve(BIN_DIR, '..');
24
+ const NODE_BIN = resolve(BIN_DIR, platform() === 'win32' ? 'node.exe' : 'node');
25
+ const MARKUS_MJS = resolve(BIN_DIR, 'markus.mjs');
26
+ const LOG_DIR = resolve(homedir(), '.markus', 'logs');
27
+ function loadIconBase64() {
28
+ const candidates = [
29
+ resolve(APP_DIR, 'logo.png'),
30
+ resolve(APP_DIR, 'markus.ico'),
31
+ ];
32
+ for (const p of candidates) {
33
+ if (existsSync(p)) {
34
+ return readFileSync(p).toString('base64');
35
+ }
36
+ }
37
+ // Fallback: empty icon (systray2 will show a default)
38
+ return '';
39
+ }
40
+ function openBrowser(url) {
41
+ const sys = platform();
42
+ const cmd = sys === 'darwin' ? `open "${url}"`
43
+ : sys === 'win32' ? `start "" "${url}"`
44
+ : `xdg-open "${url}"`;
45
+ exec(cmd, () => { });
46
+ }
47
+ // ── Server process management ────────────────────────────────────────────────
48
+ let serverProcess = null;
49
+ let serverRunning = false;
50
+ function startServer() {
51
+ if (serverRunning)
52
+ return;
53
+ mkdirSync(LOG_DIR, { recursive: true });
54
+ serverProcess = spawn(NODE_BIN, [MARKUS_MJS, 'start'], {
55
+ stdio: 'ignore',
56
+ detached: false,
57
+ env: { ...process.env, NO_BROWSER: '1' },
58
+ });
59
+ serverRunning = true;
60
+ updateTrayMenu();
61
+ serverProcess.on('exit', (code) => {
62
+ serverRunning = false;
63
+ serverProcess = null;
64
+ updateTrayMenu();
65
+ if (code && code !== 0) {
66
+ console.error(`Markus server exited with code ${code}`);
67
+ }
68
+ });
69
+ serverProcess.on('error', (err) => {
70
+ serverRunning = false;
71
+ serverProcess = null;
72
+ updateTrayMenu();
73
+ console.error('Failed to start Markus server:', err.message);
74
+ });
75
+ // Auto-open browser after a short delay
76
+ setTimeout(() => {
77
+ if (serverRunning)
78
+ openBrowser(WEB_UI_URL);
79
+ }, 3000);
80
+ }
81
+ function stopServer() {
82
+ if (!serverProcess)
83
+ return;
84
+ serverProcess.kill('SIGTERM');
85
+ // Force kill after 5 seconds if still alive
86
+ const forceTimer = setTimeout(() => {
87
+ if (serverProcess && !serverProcess.killed) {
88
+ serverProcess.kill('SIGKILL');
89
+ }
90
+ }, 5000);
91
+ serverProcess.on('exit', () => clearTimeout(forceTimer));
92
+ }
93
+ // ── Tray setup ───────────────────────────────────────────────────────────────
94
+ const SEPARATOR = { title: '<SEPARATOR>', tooltip: '', enabled: true };
95
+ const ITEM_OPEN_UI = { title: 'Open Web UI', tooltip: 'Open Markus in browser', enabled: true };
96
+ const ITEM_START = { title: 'Start Server', tooltip: 'Start Markus server', enabled: true };
97
+ const ITEM_STOP = { title: 'Stop Server', tooltip: 'Stop Markus server', enabled: false };
98
+ const ITEM_QUIT = { title: 'Quit Markus', tooltip: 'Stop server and quit', enabled: true };
99
+ let tray = null;
100
+ function buildMenu() {
101
+ return {
102
+ icon: loadIconBase64(),
103
+ title: '',
104
+ tooltip: serverRunning ? 'Markus (running)' : 'Markus (stopped)',
105
+ items: [
106
+ { ...ITEM_OPEN_UI, enabled: serverRunning },
107
+ SEPARATOR,
108
+ { ...ITEM_START, enabled: !serverRunning },
109
+ { ...ITEM_STOP, enabled: serverRunning },
110
+ SEPARATOR,
111
+ ITEM_QUIT,
112
+ ],
113
+ };
114
+ }
115
+ function updateTrayMenu() {
116
+ if (!tray)
117
+ return;
118
+ tray.sendAction({ type: 'update-menu', menu: buildMenu() });
119
+ }
120
+ async function main() {
121
+ tray = new SysTray({ menu: buildMenu(), copyDir: false });
122
+ await tray.ready();
123
+ tray.onClick(async (action) => {
124
+ const title = action.item.title;
125
+ if (title === ITEM_OPEN_UI.title) {
126
+ openBrowser(WEB_UI_URL);
127
+ }
128
+ else if (title === ITEM_START.title) {
129
+ startServer();
130
+ }
131
+ else if (title === ITEM_STOP.title) {
132
+ stopServer();
133
+ }
134
+ else if (title === ITEM_QUIT.title) {
135
+ stopServer();
136
+ // Wait briefly for server to stop, then exit
137
+ setTimeout(async () => {
138
+ if (tray)
139
+ await tray.kill(false);
140
+ process.exit(0);
141
+ }, 1000);
142
+ }
143
+ });
144
+ tray.onError((err) => {
145
+ console.error('Tray error:', err.message);
146
+ });
147
+ // Auto-start the server on launch
148
+ startServer();
149
+ }
150
+ main().catch((err) => {
151
+ console.error('Tray failed to start:', err);
152
+ process.exit(1);
153
+ });
154
+ //# sourceMappingURL=tray.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tray.js","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"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"}
package/dist/tray.mjs ADDED
@@ -0,0 +1,130 @@
1
+ import { createRequire } from 'module';
2
+ const require = createRequire(import.meta.url);
3
+
4
+ // src/tray.ts
5
+ import { spawn, exec } from "node:child_process";
6
+ import { readFileSync, existsSync, mkdirSync } from "node:fs";
7
+ import { resolve, dirname } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { platform, homedir } from "node:os";
10
+ import SysTrayModule from "systray2";
11
+ var SysTray = SysTrayModule.default || SysTrayModule;
12
+ var __dirname = dirname(fileURLToPath(import.meta.url));
13
+ var WEB_UI_PORT = 8056;
14
+ var WEB_UI_URL = `http://localhost:${WEB_UI_PORT}`;
15
+ var BIN_DIR = __dirname;
16
+ var APP_DIR = resolve(BIN_DIR, "..");
17
+ var NODE_BIN = resolve(BIN_DIR, platform() === "win32" ? "node.exe" : "node");
18
+ var MARKUS_MJS = resolve(BIN_DIR, "markus.mjs");
19
+ var LOG_DIR = resolve(homedir(), ".markus", "logs");
20
+ function loadIconBase64() {
21
+ const candidates = [
22
+ resolve(APP_DIR, "logo.png"),
23
+ resolve(APP_DIR, "markus.ico")
24
+ ];
25
+ for (const p of candidates) {
26
+ if (existsSync(p)) {
27
+ return readFileSync(p).toString("base64");
28
+ }
29
+ }
30
+ return "";
31
+ }
32
+ function openBrowser(url) {
33
+ const sys = platform();
34
+ const cmd = sys === "darwin" ? `open "${url}"` : sys === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
35
+ exec(cmd, () => {
36
+ });
37
+ }
38
+ var serverProcess = null;
39
+ var serverRunning = false;
40
+ function startServer() {
41
+ if (serverRunning) return;
42
+ mkdirSync(LOG_DIR, { recursive: true });
43
+ serverProcess = spawn(NODE_BIN, [MARKUS_MJS, "start"], {
44
+ stdio: "ignore",
45
+ detached: false,
46
+ env: { ...process.env, NO_BROWSER: "1" }
47
+ });
48
+ serverRunning = true;
49
+ updateTrayMenu();
50
+ serverProcess.on("exit", (code) => {
51
+ serverRunning = false;
52
+ serverProcess = null;
53
+ updateTrayMenu();
54
+ if (code && code !== 0) {
55
+ console.error(`Markus server exited with code ${code}`);
56
+ }
57
+ });
58
+ serverProcess.on("error", (err) => {
59
+ serverRunning = false;
60
+ serverProcess = null;
61
+ updateTrayMenu();
62
+ console.error("Failed to start Markus server:", err.message);
63
+ });
64
+ setTimeout(() => {
65
+ if (serverRunning) openBrowser(WEB_UI_URL);
66
+ }, 3e3);
67
+ }
68
+ function stopServer() {
69
+ if (!serverProcess) return;
70
+ serverProcess.kill("SIGTERM");
71
+ const forceTimer = setTimeout(() => {
72
+ if (serverProcess && !serverProcess.killed) {
73
+ serverProcess.kill("SIGKILL");
74
+ }
75
+ }, 5e3);
76
+ serverProcess.on("exit", () => clearTimeout(forceTimer));
77
+ }
78
+ var SEPARATOR = { title: "<SEPARATOR>", tooltip: "", enabled: true };
79
+ var ITEM_OPEN_UI = { title: "Open Web UI", tooltip: "Open Markus in browser", enabled: true };
80
+ var ITEM_START = { title: "Start Server", tooltip: "Start Markus server", enabled: true };
81
+ var ITEM_STOP = { title: "Stop Server", tooltip: "Stop Markus server", enabled: false };
82
+ var ITEM_QUIT = { title: "Quit Markus", tooltip: "Stop server and quit", enabled: true };
83
+ var tray = null;
84
+ function buildMenu() {
85
+ return {
86
+ icon: loadIconBase64(),
87
+ title: "",
88
+ tooltip: serverRunning ? "Markus (running)" : "Markus (stopped)",
89
+ items: [
90
+ { ...ITEM_OPEN_UI, enabled: serverRunning },
91
+ SEPARATOR,
92
+ { ...ITEM_START, enabled: !serverRunning },
93
+ { ...ITEM_STOP, enabled: serverRunning },
94
+ SEPARATOR,
95
+ ITEM_QUIT
96
+ ]
97
+ };
98
+ }
99
+ function updateTrayMenu() {
100
+ if (!tray) return;
101
+ tray.sendAction({ type: "update-menu", menu: buildMenu() });
102
+ }
103
+ async function main() {
104
+ tray = new SysTray({ menu: buildMenu(), copyDir: false });
105
+ await tray.ready();
106
+ tray.onClick(async (action) => {
107
+ const title = action.item.title;
108
+ if (title === ITEM_OPEN_UI.title) {
109
+ openBrowser(WEB_UI_URL);
110
+ } else if (title === ITEM_START.title) {
111
+ startServer();
112
+ } else if (title === ITEM_STOP.title) {
113
+ stopServer();
114
+ } else if (title === ITEM_QUIT.title) {
115
+ stopServer();
116
+ setTimeout(async () => {
117
+ if (tray) await tray.kill(false);
118
+ process.exit(0);
119
+ }, 1e3);
120
+ }
121
+ });
122
+ tray.onError((err) => {
123
+ console.error("Tray error:", err.message);
124
+ });
125
+ startServer();
126
+ }
127
+ main().catch((err) => {
128
+ console.error("Tray failed to start:", err);
129
+ process.exit(1);
130
+ });