terminalos 0.4.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,17 @@
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
+ <meta
7
+ http-equiv="Content-Security-Policy"
8
+ content="default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: https: http: file:; frame-src localfile:; connect-src 'self' ws://localhost:*"
9
+ />
10
+ <title>terminalOS</title>
11
+ <script type="module" crossorigin src="./assets/index-DfoqUTmD.js"></script>
12
+ <link rel="stylesheet" crossorigin href="./assets/index-CfXPiaFw.css">
13
+ </head>
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+ </html>
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FsWatcher = void 0;
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const chokidar_1 = __importDefault(require("chokidar"));
10
+ // Polyfill DOMMatrix for pdfjs-dist (pdf-parse dep) in Node.js/Electron main process
11
+ if (typeof globalThis.DOMMatrix === 'undefined') {
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ ;
14
+ globalThis.DOMMatrix = class DOMMatrix {
15
+ a = 1;
16
+ b = 0;
17
+ c = 0;
18
+ d = 1;
19
+ e = 0;
20
+ f = 0;
21
+ m11 = 1;
22
+ m12 = 0;
23
+ m13 = 0;
24
+ m14 = 0;
25
+ m21 = 0;
26
+ m22 = 1;
27
+ m23 = 0;
28
+ m24 = 0;
29
+ m31 = 0;
30
+ m32 = 0;
31
+ m33 = 1;
32
+ m34 = 0;
33
+ m41 = 0;
34
+ m42 = 0;
35
+ m43 = 0;
36
+ m44 = 1;
37
+ is2D = true;
38
+ isIdentity = true;
39
+ constructor(_init) { }
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ static fromFloat32Array() { return new globalThis.DOMMatrix(); }
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ static fromFloat64Array() { return new globalThis.DOMMatrix(); }
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ static fromMatrix() { return new globalThis.DOMMatrix(); }
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ multiply() { return new globalThis.DOMMatrix(); }
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ translate() { return new globalThis.DOMMatrix(); }
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ scale() { return new globalThis.DOMMatrix(); }
52
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
+ rotate() { return new globalThis.DOMMatrix(); }
54
+ toFloat32Array() { return new Float32Array(16); }
55
+ toFloat64Array() { return new Float64Array(16); }
56
+ toString() { return 'matrix(1, 0, 0, 1, 0, 0)'; }
57
+ };
58
+ }
59
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
60
+ const pdfParse = require('pdf-parse');
61
+ const mammoth_1 = __importDefault(require("mammoth"));
62
+ async function getContentSize(entryPath, ext) {
63
+ try {
64
+ if (ext === 'pdf') {
65
+ const buffer = await promises_1.default.readFile(entryPath);
66
+ const result = await pdfParse(buffer);
67
+ return result.text.length;
68
+ }
69
+ if (ext === 'docx') {
70
+ const buffer = await promises_1.default.readFile(entryPath);
71
+ const result = await mammoth_1.default.extractRawText({ buffer });
72
+ return result.value.length;
73
+ }
74
+ }
75
+ catch {
76
+ // fallback: no contentSize, frontend will use size
77
+ }
78
+ return undefined;
79
+ }
80
+ class FsWatcher {
81
+ win;
82
+ watcher = null;
83
+ watchRoot = null;
84
+ constructor(win) {
85
+ this.win = win;
86
+ }
87
+ async readDir(dirPath) {
88
+ // Validate path to prevent traversal
89
+ const resolved = path_1.default.resolve(dirPath);
90
+ const entries = await promises_1.default.readdir(resolved, { withFileTypes: true });
91
+ const result = [];
92
+ await Promise.all(entries.map(async (entry) => {
93
+ const entryPath = path_1.default.join(resolved, entry.name);
94
+ const isDirectory = entry.isDirectory();
95
+ const ext = isDirectory ? '' : path_1.default.extname(entry.name).slice(1).toLowerCase();
96
+ const stat = isDirectory ? null : await promises_1.default.stat(entryPath).catch(() => null);
97
+ const contentSize = isDirectory ? undefined : await getContentSize(entryPath, ext);
98
+ result.push({
99
+ name: entry.name,
100
+ path: entryPath,
101
+ isDirectory,
102
+ ext,
103
+ size: stat?.size,
104
+ contentSize,
105
+ });
106
+ }));
107
+ // Sort: directories first, then files, both alphabetically
108
+ result.sort((a, b) => {
109
+ if (a.isDirectory && !b.isDirectory)
110
+ return -1;
111
+ if (!a.isDirectory && b.isDirectory)
112
+ return 1;
113
+ return a.name.localeCompare(b.name);
114
+ });
115
+ return result;
116
+ }
117
+ async readFile(filePath) {
118
+ const resolved = path_1.default.resolve(filePath);
119
+ return promises_1.default.readFile(resolved, 'utf8');
120
+ }
121
+ async writeFile(filePath, content) {
122
+ const resolved = path_1.default.resolve(filePath);
123
+ await promises_1.default.writeFile(resolved, content, 'utf8');
124
+ }
125
+ async mkdir(dirPath) {
126
+ const resolved = path_1.default.resolve(dirPath);
127
+ await promises_1.default.mkdir(resolved, { recursive: true });
128
+ }
129
+ async rename(srcPath, destPath) {
130
+ const src = path_1.default.resolve(srcPath);
131
+ const dest = path_1.default.resolve(destPath);
132
+ await promises_1.default.rename(src, dest);
133
+ }
134
+ async copyExternal(srcPath, destDir) {
135
+ const src = path_1.default.resolve(srcPath);
136
+ const dest = path_1.default.join(path_1.default.resolve(destDir), path_1.default.basename(src));
137
+ await promises_1.default.cp(src, dest, { recursive: true });
138
+ }
139
+ async delete(targetPath) {
140
+ const resolved = path_1.default.resolve(targetPath);
141
+ await promises_1.default.rm(resolved, { recursive: true, force: true });
142
+ }
143
+ setWatchRoot(rootPath) {
144
+ const resolved = path_1.default.resolve(rootPath);
145
+ if (this.watchRoot === resolved)
146
+ return;
147
+ this.watcher?.close();
148
+ this.watchRoot = resolved;
149
+ this.watcher = chokidar_1.default.watch(resolved, {
150
+ ignoreInitial: true,
151
+ ignored: [
152
+ /(^|[\/\\])\../, // hidden files
153
+ /node_modules/,
154
+ /\.git/,
155
+ /dist/,
156
+ /build/,
157
+ ],
158
+ depth: 5,
159
+ awaitWriteFinish: {
160
+ stabilityThreshold: 100,
161
+ pollInterval: 100,
162
+ },
163
+ });
164
+ const emit = (type, filePath) => {
165
+ if (!this.win.isDestroyed()) {
166
+ this.win.webContents.send('fs:watch', { type, path: filePath });
167
+ }
168
+ };
169
+ this.watcher
170
+ .on('add', (p) => emit('add', p))
171
+ .on('addDir', (p) => emit('addDir', p))
172
+ .on('change', (p) => emit('change', p))
173
+ .on('unlink', (p) => emit('unlink', p))
174
+ .on('unlinkDir', (p) => emit('unlinkDir', p));
175
+ }
176
+ close() {
177
+ this.watcher?.close();
178
+ this.watcher = null;
179
+ }
180
+ }
181
+ exports.FsWatcher = FsWatcher;
@@ -0,0 +1,183 @@
1
+ import { BrowserWindow } from 'electron'
2
+ import fs from 'fs/promises'
3
+ import path from 'path'
4
+ import chokidar, { FSWatcher } from 'chokidar'
5
+ // Polyfill DOMMatrix for pdfjs-dist (pdf-parse dep) in Node.js/Electron main process
6
+ if (typeof globalThis.DOMMatrix === 'undefined') {
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ ;(globalThis as any).DOMMatrix = class DOMMatrix {
9
+ a = 1; b = 0; c = 0; d = 1; e = 0; f = 0
10
+ m11 = 1; m12 = 0; m13 = 0; m14 = 0
11
+ m21 = 0; m22 = 1; m23 = 0; m24 = 0
12
+ m31 = 0; m32 = 0; m33 = 1; m34 = 0
13
+ m41 = 0; m42 = 0; m43 = 0; m44 = 1
14
+ is2D = true; isIdentity = true
15
+ constructor(_init?: number[] | string) {}
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ static fromFloat32Array() { return new (globalThis as any).DOMMatrix() }
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ static fromFloat64Array() { return new (globalThis as any).DOMMatrix() }
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ static fromMatrix() { return new (globalThis as any).DOMMatrix() }
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ multiply() { return new (globalThis as any).DOMMatrix() }
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ translate() { return new (globalThis as any).DOMMatrix() }
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ scale() { return new (globalThis as any).DOMMatrix() }
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ rotate() { return new (globalThis as any).DOMMatrix() }
30
+ toFloat32Array() { return new Float32Array(16) }
31
+ toFloat64Array() { return new Float64Array(16) }
32
+ toString() { return 'matrix(1, 0, 0, 1, 0, 0)' }
33
+ }
34
+ }
35
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
36
+ const pdfParse = require('pdf-parse') as (buf: Buffer) => Promise<{ text: string }>
37
+ import mammoth from 'mammoth'
38
+
39
+ interface FsEntry {
40
+ name: string
41
+ path: string
42
+ isDirectory: boolean
43
+ ext: string
44
+ size?: number
45
+ contentSize?: number
46
+ }
47
+
48
+ async function getContentSize(entryPath: string, ext: string): Promise<number | undefined> {
49
+ try {
50
+ if (ext === 'pdf') {
51
+ const buffer = await fs.readFile(entryPath)
52
+ const result = await pdfParse(buffer)
53
+ return result.text.length
54
+ }
55
+ if (ext === 'docx') {
56
+ const buffer = await fs.readFile(entryPath)
57
+ const result = await mammoth.extractRawText({ buffer })
58
+ return result.value.length
59
+ }
60
+ } catch {
61
+ // fallback: no contentSize, frontend will use size
62
+ }
63
+ return undefined
64
+ }
65
+
66
+ export class FsWatcher {
67
+ private win: BrowserWindow
68
+ private watcher: FSWatcher | null = null
69
+ private watchRoot: string | null = null
70
+
71
+ constructor(win: BrowserWindow) {
72
+ this.win = win
73
+ }
74
+
75
+ async readDir(dirPath: string): Promise<FsEntry[]> {
76
+ // Validate path to prevent traversal
77
+ const resolved = path.resolve(dirPath)
78
+
79
+ const entries = await fs.readdir(resolved, { withFileTypes: true })
80
+ const result: FsEntry[] = []
81
+
82
+ await Promise.all(entries.map(async (entry) => {
83
+ const entryPath = path.join(resolved, entry.name)
84
+ const isDirectory = entry.isDirectory()
85
+ const ext = isDirectory ? '' : path.extname(entry.name).slice(1).toLowerCase()
86
+ const stat = isDirectory ? null : await fs.stat(entryPath).catch(() => null)
87
+ const contentSize = isDirectory ? undefined : await getContentSize(entryPath, ext)
88
+
89
+ result.push({
90
+ name: entry.name,
91
+ path: entryPath,
92
+ isDirectory,
93
+ ext,
94
+ size: stat?.size,
95
+ contentSize,
96
+ })
97
+ }))
98
+
99
+ // Sort: directories first, then files, both alphabetically
100
+ result.sort((a, b) => {
101
+ if (a.isDirectory && !b.isDirectory) return -1
102
+ if (!a.isDirectory && b.isDirectory) return 1
103
+ return a.name.localeCompare(b.name)
104
+ })
105
+
106
+ return result
107
+ }
108
+
109
+ async readFile(filePath: string): Promise<string> {
110
+ const resolved = path.resolve(filePath)
111
+ return fs.readFile(resolved, 'utf8')
112
+ }
113
+
114
+ async writeFile(filePath: string, content: string): Promise<void> {
115
+ const resolved = path.resolve(filePath)
116
+ await fs.writeFile(resolved, content, 'utf8')
117
+ }
118
+
119
+ async mkdir(dirPath: string): Promise<void> {
120
+ const resolved = path.resolve(dirPath)
121
+ await fs.mkdir(resolved, { recursive: true })
122
+ }
123
+
124
+ async rename(srcPath: string, destPath: string): Promise<void> {
125
+ const src = path.resolve(srcPath)
126
+ const dest = path.resolve(destPath)
127
+ await fs.rename(src, dest)
128
+ }
129
+
130
+ async copyExternal(srcPath: string, destDir: string): Promise<void> {
131
+ const src = path.resolve(srcPath)
132
+ const dest = path.join(path.resolve(destDir), path.basename(src))
133
+ await fs.cp(src, dest, { recursive: true })
134
+ }
135
+
136
+ async delete(targetPath: string): Promise<void> {
137
+ const resolved = path.resolve(targetPath)
138
+ await fs.rm(resolved, { recursive: true, force: true })
139
+ }
140
+
141
+ setWatchRoot(rootPath: string): void {
142
+ const resolved = path.resolve(rootPath)
143
+
144
+ if (this.watchRoot === resolved) return
145
+
146
+ this.watcher?.close()
147
+ this.watchRoot = resolved
148
+
149
+ this.watcher = chokidar.watch(resolved, {
150
+ ignoreInitial: true,
151
+ ignored: [
152
+ /(^|[\/\\])\../, // hidden files
153
+ /node_modules/,
154
+ /\.git/,
155
+ /dist/,
156
+ /build/,
157
+ ],
158
+ depth: 5,
159
+ awaitWriteFinish: {
160
+ stabilityThreshold: 100,
161
+ pollInterval: 100,
162
+ },
163
+ })
164
+
165
+ const emit = (type: string, filePath: string) => {
166
+ if (!this.win.isDestroyed()) {
167
+ this.win.webContents.send('fs:watch', { type, path: filePath })
168
+ }
169
+ }
170
+
171
+ this.watcher
172
+ .on('add', (p) => emit('add', p))
173
+ .on('addDir', (p) => emit('addDir', p))
174
+ .on('change', (p) => emit('change', p))
175
+ .on('unlink', (p) => emit('unlink', p))
176
+ .on('unlinkDir', (p) => emit('unlinkDir', p))
177
+ }
178
+
179
+ close(): void {
180
+ this.watcher?.close()
181
+ this.watcher = null
182
+ }
183
+ }
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const electron_1 = require("electron");
40
+ const path_1 = __importDefault(require("path"));
41
+ const window_state_1 = require("./window-state");
42
+ const pty_manager_1 = require("./pty-manager");
43
+ const fs_watcher_1 = require("./fs-watcher");
44
+ const versions_manager_1 = require("./versions-manager");
45
+ // Must be called before app.ready
46
+ electron_1.protocol.registerSchemesAsPrivileged([
47
+ { scheme: 'localfile', privileges: { secure: true, supportFetchAPI: true, stream: true } },
48
+ ]);
49
+ const isDev = process.env.NODE_ENV === 'development' || !electron_1.app.isPackaged;
50
+ let mainWindow = null;
51
+ let ptyManager;
52
+ let fsWatcher;
53
+ let versionsManager;
54
+ function semverGt(a, b) {
55
+ const pa = a.split('.').map(Number);
56
+ const pb = b.split('.').map(Number);
57
+ for (let i = 0; i < 3; i++) {
58
+ if ((pa[i] ?? 0) > (pb[i] ?? 0))
59
+ return true;
60
+ if ((pa[i] ?? 0) < (pb[i] ?? 0))
61
+ return false;
62
+ }
63
+ return false;
64
+ }
65
+ function createWindow() {
66
+ electron_1.protocol.handle('localfile', (request) => {
67
+ const filePath = new URL(request.url).pathname;
68
+ return electron_1.net.fetch('file://' + filePath);
69
+ });
70
+ const windowState = new window_state_1.WindowState();
71
+ const bounds = windowState.get();
72
+ mainWindow = new electron_1.BrowserWindow({
73
+ x: bounds.x,
74
+ y: bounds.y,
75
+ width: bounds.width,
76
+ height: bounds.height,
77
+ minWidth: 640,
78
+ minHeight: 400,
79
+ frame: false,
80
+ titleBarStyle: 'hidden',
81
+ titleBarOverlay: false,
82
+ trafficLightPosition: { x: 12, y: 9 },
83
+ backgroundColor: '#090909',
84
+ show: false,
85
+ webPreferences: {
86
+ preload: path_1.default.join(__dirname, 'preload.js'),
87
+ contextIsolation: true,
88
+ nodeIntegration: false,
89
+ sandbox: false,
90
+ spellcheck: false,
91
+ backgroundThrottling: false,
92
+ },
93
+ });
94
+ ptyManager = new pty_manager_1.PtyManager(mainWindow);
95
+ fsWatcher = new fs_watcher_1.FsWatcher(mainWindow);
96
+ versionsManager = new versions_manager_1.VersionsManager();
97
+ if (isDev) {
98
+ mainWindow.loadURL('http://localhost:5173');
99
+ mainWindow.webContents.openDevTools();
100
+ }
101
+ else {
102
+ mainWindow.loadFile(path_1.default.join(__dirname, '../build/index.html'));
103
+ }
104
+ mainWindow.once('ready-to-show', () => {
105
+ mainWindow?.show();
106
+ });
107
+ mainWindow.on('resize', () => {
108
+ windowState.save(mainWindow);
109
+ });
110
+ mainWindow.on('move', () => {
111
+ windowState.save(mainWindow);
112
+ });
113
+ mainWindow.on('closed', () => {
114
+ mainWindow = null;
115
+ });
116
+ setupIpcHandlers();
117
+ }
118
+ function setupIpcHandlers() {
119
+ // PTY handlers
120
+ electron_1.ipcMain.handle('pty:create', async (_, opts) => {
121
+ return ptyManager.create(opts);
122
+ });
123
+ electron_1.ipcMain.on('pty:write', (_, sessionId, data) => {
124
+ ptyManager.write(sessionId, data);
125
+ });
126
+ electron_1.ipcMain.on('pty:resize', (_, sessionId, cols, rows) => {
127
+ ptyManager.resize(sessionId, cols, rows);
128
+ });
129
+ electron_1.ipcMain.handle('pty:kill', async (_, sessionId) => {
130
+ return ptyManager.kill(sessionId);
131
+ });
132
+ // FS handlers
133
+ electron_1.ipcMain.handle('fs:openFolder', async () => {
134
+ const result = await electron_1.dialog.showOpenDialog(mainWindow, {
135
+ properties: ['openDirectory'],
136
+ });
137
+ if (result.canceled || result.filePaths.length === 0)
138
+ return null;
139
+ return result.filePaths[0];
140
+ });
141
+ electron_1.ipcMain.handle('fs:readDir', async (_, dirPath) => {
142
+ return fsWatcher.readDir(dirPath);
143
+ });
144
+ electron_1.ipcMain.handle('fs:readFile', async (_, filePath) => {
145
+ return fsWatcher.readFile(filePath);
146
+ });
147
+ electron_1.ipcMain.handle('fs:writeFile', async (_, filePath, content) => {
148
+ return fsWatcher.writeFile(filePath, content);
149
+ });
150
+ electron_1.ipcMain.handle('fs:mkdir', async (_, dirPath) => {
151
+ return fsWatcher.mkdir(dirPath);
152
+ });
153
+ electron_1.ipcMain.handle('fs:rename', async (_, src, dest) => {
154
+ return fsWatcher.rename(src, dest);
155
+ });
156
+ electron_1.ipcMain.handle('fs:copyExternal', async (_, srcPath, destDir) => {
157
+ return fsWatcher.copyExternal(srcPath, destDir);
158
+ });
159
+ electron_1.ipcMain.handle('fs:delete', async (_, targetPath) => {
160
+ return fsWatcher.delete(targetPath);
161
+ });
162
+ electron_1.ipcMain.handle('fs:writeBinaryFile', async (_, filePath, data) => {
163
+ const { promises: fs } = await Promise.resolve().then(() => __importStar(require('fs')));
164
+ await fs.writeFile(filePath, Buffer.from(data));
165
+ });
166
+ electron_1.ipcMain.on('fs:setWatchRoot', (_, rootPath) => {
167
+ fsWatcher.setWatchRoot(rootPath);
168
+ });
169
+ // App handlers
170
+ electron_1.ipcMain.handle('app:getVersion', async () => {
171
+ return electron_1.app.getVersion();
172
+ });
173
+ electron_1.ipcMain.handle('app:getGitBranch', async (_, cwd) => {
174
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
175
+ try {
176
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd, encoding: 'utf8' }).trim();
177
+ return branch;
178
+ }
179
+ catch {
180
+ return null;
181
+ }
182
+ });
183
+ electron_1.ipcMain.handle('app:checkForUpdates', async () => {
184
+ try {
185
+ const res = await electron_1.net.fetch('https://api.github.com/repos/vbfs/terminalOS/releases/latest', {
186
+ headers: { 'User-Agent': 'aiTerm-updater' },
187
+ });
188
+ if (!res.ok)
189
+ return null;
190
+ const data = await res.json();
191
+ const latest = data.tag_name.replace(/^v/, '');
192
+ const current = electron_1.app.getVersion();
193
+ return semverGt(latest, current) ? { version: latest, url: data.html_url } : null;
194
+ }
195
+ catch {
196
+ return null;
197
+ }
198
+ });
199
+ // Window controls
200
+ electron_1.ipcMain.on('window:minimize', () => mainWindow?.minimize());
201
+ electron_1.ipcMain.on('window:maximize', () => {
202
+ if (mainWindow?.isMaximized()) {
203
+ mainWindow.unmaximize();
204
+ }
205
+ else {
206
+ mainWindow?.maximize();
207
+ }
208
+ });
209
+ electron_1.ipcMain.on('window:close', () => mainWindow?.close());
210
+ // Shell operations
211
+ electron_1.ipcMain.on('shell:openPath', (_, filePath) => {
212
+ electron_1.shell.showItemInFolder(filePath);
213
+ });
214
+ electron_1.ipcMain.on('shell:openInFinder', (_, folderPath) => {
215
+ electron_1.shell.openPath(folderPath);
216
+ });
217
+ electron_1.ipcMain.on('shell:openExternal', (_, url) => {
218
+ electron_1.shell.openExternal(url);
219
+ });
220
+ // Version history handlers
221
+ electron_1.ipcMain.handle('fs:versions:save', async (_, filePath, content) => {
222
+ return versionsManager.saveVersion(filePath, content);
223
+ });
224
+ electron_1.ipcMain.handle('fs:versions:list', async (_, filePath) => {
225
+ return versionsManager.listVersions(filePath);
226
+ });
227
+ electron_1.ipcMain.handle('fs:versions:get', async (_, filePath, versionId) => {
228
+ return versionsManager.getVersion(filePath, versionId);
229
+ });
230
+ }
231
+ electron_1.app.whenReady().then(createWindow);
232
+ electron_1.app.on('before-quit', () => {
233
+ ptyManager?.killAll();
234
+ fsWatcher?.close();
235
+ });
236
+ electron_1.app.on('window-all-closed', () => {
237
+ if (process.platform !== 'darwin') {
238
+ electron_1.app.quit();
239
+ }
240
+ });
241
+ electron_1.app.on('activate', () => {
242
+ if (electron_1.BrowserWindow.getAllWindows().length === 0) {
243
+ createWindow();
244
+ }
245
+ });