diff --git a/.gitignore b/.gitignore index 06c7b2a..423fffa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -homed +/homed /waiter *.exe *.log @@ -15,6 +15,8 @@ internal/meta/ *.hmap dev/ +login.json + # GUI cmd/gui/node_modules/ cmd/gui/dist/ diff --git a/cmd/gui/icon.ico b/cmd/gui/icon.ico new file mode 100644 index 0000000..e121e63 Binary files /dev/null and b/cmd/gui/icon.ico differ diff --git a/cmd/gui/icon.svg b/cmd/gui/icon.svg index e8214ec..66dfe03 100644 --- a/cmd/gui/icon.svg +++ b/cmd/gui/icon.svg @@ -1,5 +1,5 @@ - + diff --git a/cmd/gui/main.js b/cmd/gui/main.js index a47f36c..4270e12 100644 --- a/cmd/gui/main.js +++ b/cmd/gui/main.js @@ -1,9 +1,14 @@ -const { app, BrowserWindow, ipcMain, dialog } = require('electron'); +const { app, BrowserWindow, ipcMain, dialog, Menu } = require('electron'); const path = require('path'); const fs = require('fs'); +const { spawn } = require('child_process'); +const http = require('http'); const CONNECTIONS_FILE = path.join(app.getPath('userData'), 'connections.json'); +let homedProcess = null; +let mainWindow; + function loadConnections() { try { if (fs.existsSync(CONNECTIONS_FILE)) { @@ -12,6 +17,18 @@ function loadConnections() { } catch (e) { console.error('Failed to load connections:', e); } + // Fallback: check app resource dir (installer writes fallback copy there) + try { + const fallback = path.join(__dirname, 'connections.json'); + if (fs.existsSync(fallback)) { + const data = JSON.parse(fs.readFileSync(fallback, 'utf-8')); + saveConnections(data); + console.log('Imported connections from app resource dir'); + return data; + } + } catch (e) { + console.error('Fallback connections load failed:', e); + } return { connections: [], currentId: null }; } @@ -23,9 +40,69 @@ function saveConnections(data) { } } -let mainWindow; +function findHomed() { + if (process.platform !== 'win32') return null; + const exeDir = path.dirname(app.getPath('exe')); + const p = path.resolve(exeDir, '..', 'homed.exe'); + return fs.existsSync(p) ? p : null; +} + +function isServerRunning() { + return new Promise((resolve) => { + const req = http.get('http://localhost:8080/', () => resolve(true)); + req.on('error', () => resolve(false)); + req.setTimeout(2000, () => { req.destroy(); resolve(false); }); + }); +} + +function waitForServer(maxWait = 8000) { + return new Promise((resolve) => { + const start = Date.now(); + const check = () => { + isServerRunning().then((running) => { + if (running) return resolve(true); + if (Date.now() - start > maxWait) return resolve(false); + setTimeout(check, 300); + }); + }; + check(); + }); +} + +function startHomed() { + const homedBin = findHomed(); + if (!homedBin) { + console.log('homed.exe not found near GUI, skipping auto-launch'); + return; + } + const dataDir = path.resolve(path.dirname(homedBin), 'data'); + console.log('Starting homed:', homedBin, '-data', dataDir); + homedProcess = spawn(homedBin, ['-data', dataDir], { + stdio: 'ignore', + detached: false, + windowsHide: true, + }); + homedProcess.on('error', (err) => { + console.error('homed start failed:', err.message); + homedProcess = null; + }); + homedProcess.on('exit', (code) => { + console.log('homed exited with code', code); + homedProcess = null; + }); +} + +function stopHomed() { + if (homedProcess) { + homedProcess.kill(); + homedProcess = null; + } +} function createWindow() { + const menu = Menu.buildFromTemplate([]); + Menu.setApplicationMenu(menu); + mainWindow = new BrowserWindow({ width: 1280, height: 860, @@ -93,12 +170,28 @@ ipcMain.handle('connections:setCurrent', (_, id) => { return data; }); -app.whenReady().then(createWindow); +app.whenReady().then(async () => { + const running = await isServerRunning(); + if (!running) { + startHomed(); + const started = await waitForServer(); + if (started) { + console.log('homed started successfully'); + } else { + console.error('homed failed to start within timeout'); + } + } + createWindow(); +}); + +app.on('before-quit', stopHomed); app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); app.on('activate', () => { - if (mainWindow === null) createWindow(); + if (mainWindow === null) { + createWindow(); + } }); diff --git a/cmd/gui/package-lock.json b/cmd/gui/package-lock.json index 0f06951..4e10aae 100644 --- a/cmd/gui/package-lock.json +++ b/cmd/gui/package-lock.json @@ -13,9 +13,29 @@ "devDependencies": { "asar": "^3.2.0", "electron-builder": "^26.15.3", - "electron-packager": "^17.1.2" + "electron-packager": "^17.1.2", + "icojs": "^0.23.0", + "sharp": "^0.35.3" } }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@canvas/image-data": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@canvas/image-data/-/image-data-1.1.0.tgz", + "integrity": "sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==", + "dev": true, + "license": "MIT" + }, "node_modules/@electron/asar": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", @@ -367,6 +387,544 @@ "node": ">= 10.0.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", + "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", + "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", + "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", + "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", + "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", + "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", + "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", + "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", + "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", + "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", + "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", + "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", + "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", + "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", + "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", + "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", + "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", + "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", + "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", + "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", + "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -536,6 +1094,31 @@ "node": ">=10" } }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -1278,6 +1861,13 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "node_modules/bmp-ts": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/bmp-ts/-/bmp-ts-1.0.9.tgz", + "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", + "dev": true, + "license": "MIT" + }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -1640,6 +2230,35 @@ } } }, + "node_modules/decode-bmp": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/decode-bmp/-/decode-bmp-0.2.1.tgz", + "integrity": "sha512-NiOaGe+GN0KJqi2STf24hfMkFitDUaIoUU3eKvP/wAbLe8o6FuW5n/x7MHPR0HKvBokp6MQY/j7w8lewEeVCIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@canvas/image-data": "^1.0.0", + "to-data-view": "^1.1.0" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/decode-ico": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/decode-ico/-/decode-ico-0.4.1.tgz", + "integrity": "sha512-69NZfbKIzux1vBOd31al3XnMnH+2mqDhEgLdpygErm4d60N+UwA5Sq5WFjmEDQzumgB9fElojGwWG0vybVfFmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@canvas/image-data": "^1.0.0", + "decode-bmp": "^0.2.0", + "to-data-view": "^1.1.0" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -1721,6 +2340,16 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -2301,6 +2930,25 @@ } } }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmmirror.com/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/filelist": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", @@ -2827,6 +3475,44 @@ "node": ">= 14" } }, + "node_modules/icojs": { + "version": "0.23.0", + "resolved": "https://registry.npmmirror.com/icojs/-/icojs-0.23.0.tgz", + "integrity": "sha512-l2r+WSRnwF7OAUcyVBh8N5y8j0usPYs9QQPskO1fXygxpLV+l+SeqLtklxUz1WVF3Ao+gGzoY7sh9jZRy04WmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bmp-ts": "^1.0.9", + "decode-ico": "^0.4.1", + "file-type": "^21.3.4", + "jpeg-js": "^0.4.4", + "pngjs": "^7.0.0" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2951,6 +3637,13 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmmirror.com/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/js-yaml": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", @@ -3609,6 +4302,16 @@ "node": ">=10.4.0" } }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/postject": { "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", @@ -3963,6 +4666,69 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sharp": { + "version": "0.35.3", + "resolved": "https://registry.npmmirror.com/sharp/-/sharp-0.35.3.tgz", + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.3", + "@img/sharp-darwin-x64": "0.35.3", + "@img/sharp-freebsd-wasm32": "0.35.3", + "@img/sharp-libvips-darwin-arm64": "1.3.2", + "@img/sharp-libvips-darwin-x64": "1.3.2", + "@img/sharp-libvips-linux-arm": "1.3.2", + "@img/sharp-libvips-linux-arm64": "1.3.2", + "@img/sharp-libvips-linux-ppc64": "1.3.2", + "@img/sharp-libvips-linux-riscv64": "1.3.2", + "@img/sharp-libvips-linux-s390x": "1.3.2", + "@img/sharp-libvips-linux-x64": "1.3.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", + "@img/sharp-libvips-linuxmusl-x64": "1.3.2", + "@img/sharp-linux-arm": "0.35.3", + "@img/sharp-linux-arm64": "0.35.3", + "@img/sharp-linux-ppc64": "0.35.3", + "@img/sharp-linux-riscv64": "0.35.3", + "@img/sharp-linux-s390x": "0.35.3", + "@img/sharp-linux-x64": "0.35.3", + "@img/sharp-linuxmusl-arm64": "0.35.3", + "@img/sharp-linuxmusl-x64": "0.35.3", + "@img/sharp-webcontainers-wasm32": "0.35.3", + "@img/sharp-win32-arm64": "0.35.3", + "@img/sharp-win32-ia32": "0.35.3", + "@img/sharp-win32-x64": "0.35.3" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4146,6 +4912,23 @@ "node": ">=0.8.0" } }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmmirror.com/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -4309,6 +5092,32 @@ "tmp": "^0.2.0" } }, + "node_modules/to-data-view": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/to-data-view/-/to-data-view-1.1.0.tgz", + "integrity": "sha512-1eAdufMg6mwgmlojAx3QeMnzB/BTVp7Tbndi3U7ftcT2zCZadjxkkmLmd97zmaxWi+sgGcgWrokmpEoy0Dn0vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmmirror.com/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", @@ -4358,6 +5167,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/undici": { "version": "6.27.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", diff --git a/cmd/gui/package.json b/cmd/gui/package.json index df6fae0..dedb7d7 100644 --- a/cmd/gui/package.json +++ b/cmd/gui/package.json @@ -13,7 +13,9 @@ "devDependencies": { "asar": "^3.2.0", "electron-builder": "^26.15.3", - "electron-packager": "^17.1.2" + "electron-packager": "^17.1.2", + "icojs": "^0.23.0", + "sharp": "^0.35.3" }, "build": { "appId": "com.homeagent.gui", diff --git a/cmd/gui/renderer/app.js b/cmd/gui/renderer/app.js index 1736c58..4e6a2ac 100644 --- a/cmd/gui/renderer/app.js +++ b/cmd/gui/renderer/app.js @@ -32,7 +32,57 @@ window._i18n = { navAdapters: ['适配器','Adapters'], navKernel: ['内核','Kernel'], navLogout: ['退出登录','Logout'], - themeToggle: ['切换亮色/暗色模式','Toggle theme'] + themeToggle: ['切换亮色/暗色模式','Toggle theme'], + clickManage: ['点击管理连接','Click to manage connections'], + secondsAgo: ['秒前','s ago'], + minutesAgo: ['分钟前','min ago'], + hoursAgo: ['小时前','h ago'], + noConnection: ['未连接','Not connected'], + agentAvatar: ['小宅','Agent'], + waitingAI: ['等待AI回复...','Waiting for AI...'], + noResponse: ['(无响应)','(no response)'], + error: ['错误: ','Error: '], + requestFailed: ['请求失败: ','Request failed: '], + send: ['发送','Send'], + queryFailed: ['查询失败: ','Query failed: '], + searchFailed: ['搜索失败: ','Search failed: '], + getFailed: ['获取失败: ','Get failed: '], + createFailed: ['创建失败','Create failed'], + createFailedWith: ['创建失败: ','Create failed: '], + nameContentEmpty: ['名称和内容不能为空','Name and content cannot be empty'], + knowledgeCreated: ['知识「','Knowledge "'], + knowledgeCreatedEnd: ['」已创建','" created'], + noContext: ['无上下文','No context'], + noSessions: ['暂无终端会话','No terminal sessions'], + noHistory: ['暂无命令记录','No command history'], + running: ['运行中','Running'], + closed: ['已关闭','Closed'], + command: ['命令','Command'], + status: ['状态','Status'], + created: ['创建时间','Created'], + uptime: ['运行时长','Uptime'], + output: ['输出预览','Output'], + time: ['时间','Time'], + actions: ['操作','Actions'], + name: ['名称','Name'], + description: ['描述','Description'], + version: ['版本','Version'], + details: ['详情','Details'], + close: ['关闭','Close'], + install: ['安装','Install'], + installPlugin: ['安装插件','Install Plugin'], + packageUrl: ['.hmap 包下载 URL','Package URL'], + uploadHmap: ['选择 .hmap 文件上传','Upload .hmap file'], + loadedPlugins: ['已加载插件','Loaded Plugins'], + noLoadedPlugins: ['暂无已加载插件','No loaded plugins'], + loaded: ['已加载','Loaded'], + builtin: ['内置','Built-in'], + unload: ['卸载','Unload'], + installedExternal: ['已安装外部插件','Installed Plugins'], + pluginDetails: ['插件详情','Plugin Details'], + registeredTools: ['已注册工具','Registered Tools'], + systemOps: ['系统操作','System Operations'], + reloadPlugins: ['重载插件','Reload Plugins'], }; function __(zh, en) { return state.lang === 'en' ? en : zh } @@ -84,10 +134,10 @@ function escHtml(s) { function timeAgo(t) { var s = Math.floor((Date.now() - new Date(t).getTime()) / 1000); - if (s < 60) return s + '秒前'; + if (s < 60) return s + __('秒前','s ago'); var m = Math.floor(s / 60); - if (m < 60) return m + '分钟前'; - return Math.floor(m / 60) + '小时前'; + if (m < 60) return m + __('分钟前','min ago'); + return Math.floor(m / 60) + __('小时前','h ago'); } function toast(m, isError) { @@ -325,7 +375,7 @@ function renderChat() { html += '
' + body + '
'; } else { var userAvatar = ''; - var aiAvatar = '小宅'; + var aiAvatar = '' + __('小宅','Agent') + ''; html += '
' + '
' + (role === 'user' ? userAvatar : aiAvatar) + '
' + '
' + body + '
' diff --git a/cmd/gui/renderer/index.html b/cmd/gui/renderer/index.html index 15ce857..d08778d 100644 --- a/cmd/gui/renderer/index.html +++ b/cmd/gui/renderer/index.html @@ -45,7 +45,7 @@ 适配器 内核
- + 未连接 diff --git a/cmd/gui/renderer/mascot.svg b/cmd/gui/renderer/mascot.svg new file mode 100644 index 0000000..838dd1b --- /dev/null +++ b/cmd/gui/renderer/mascot.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/cmd/homed/homed.rc b/cmd/homed/homed.rc new file mode 100644 index 0000000..af6a395 --- /dev/null +++ b/cmd/homed/homed.rc @@ -0,0 +1 @@ +1 ICON "E:/program/homeagent/homeagent/build/icon.ico" diff --git a/cmd/homed/homed.syso b/cmd/homed/homed.syso new file mode 100644 index 0000000..0a1e60e Binary files /dev/null and b/cmd/homed/homed.syso differ diff --git a/cmd/initconfig/main.go b/cmd/initconfig/main.go new file mode 100644 index 0000000..4000048 --- /dev/null +++ b/cmd/initconfig/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "crypto/rand" + "database/sql" + "encoding/hex" + "flag" + "fmt" + "os" + + _ "github.com/mattn/go-sqlite3" +) + +func randomSecret(n int) string { + b := make([]byte, n) + rand.Read(b) + return hex.EncodeToString(b) +} + +func main() { + dataDir := flag.String("data", "", "data directory") + webuiUsername := flag.String("username", "admin", "webui username") + webuiPassword := flag.String("password", "", "webui password (auto-generated if empty)") + webuiApiKey := flag.String("apikey", "", "api key (auto-generated if empty)") + flag.Parse() + + if *dataDir == "" { + fmt.Fprintln(os.Stderr, "-data is required") + os.Exit(1) + } + + os.MkdirAll(*dataDir, 0755) + + dbPath := *dataDir + "/config.db" + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + fmt.Fprintf(os.Stderr, "open db: %v\n", err) + os.Exit(1) + } + defer db.Close() + + db.Exec("PRAGMA journal_mode=WAL") + + db.Exec(`CREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, value TEXT NOT NULL)`) + db.Exec(`INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)`, "webui.listen_addr", ":8080") + + pw := *webuiPassword + if pw == "" { + pw = randomSecret(12) + } + apiKey := *webuiApiKey + if apiKey == "" { + apiKey = randomSecret(16) + } + + pt := "config_webui" + db.Exec(fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY, value TEXT NOT NULL)`, pt)) + ws := fmt.Sprintf(`INSERT OR REPLACE INTO %s (key, value) VALUES (?, ?)`, pt) + db.Exec(ws, "api_key", apiKey) + db.Exec(ws, "username", *webuiUsername) + db.Exec(ws, "password", pw) + db.Exec(ws, "session_ttl_hours", "24") + + fmt.Printf("API_KEY=%s\n", apiKey) + fmt.Printf("WEBUI_USERNAME=%s\n", *webuiUsername) + fmt.Printf("WEBUI_PASSWORD=%s\n", pw) +} diff --git a/cmd/waiter/raw_other.go b/cmd/waiter/raw_other.go new file mode 100644 index 0000000..89d0063 --- /dev/null +++ b/cmd/waiter/raw_other.go @@ -0,0 +1,11 @@ +//go:build !linux && !windows + +package main + +func setRawMode(fd int) (func(), error) { + return func() {}, nil +} + +func isTerminal(fd int) bool { + return false +} diff --git a/cmd/waiter/raw_unix.go b/cmd/waiter/raw_unix.go new file mode 100644 index 0000000..debf14d --- /dev/null +++ b/cmd/waiter/raw_unix.go @@ -0,0 +1,43 @@ +//go:build linux + +package main + +import ( + "fmt" + "os" + "syscall" + "unsafe" +) + +func setRawMode(fd int) (func(), error) { + if fd == 0 { + fd = int(os.Stdin.Fd()) + } + if !isTerminal(fd) { + return func() {}, nil + } + var oldState syscall.Termios + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TCGETS, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 { + return func() {}, fmt.Errorf("tcgets: %v", err) + } + newState := oldState + newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON + newState.Oflag &^= syscall.OPOST + newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN + newState.Cflag &^= syscall.CSIZE | syscall.PARENB + newState.Cflag |= syscall.CS8 + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TCSETS, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { + return func() {}, fmt.Errorf("tcset: %v", err) + } + return func() { + syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TCSETS, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0) + }, nil +} + +func isTerminal(fd int) bool { + var t syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TCGETS, uintptr(unsafe.Pointer(&t)), 0, 0, 0) + return err == 0 +} diff --git a/cmd/waiter/raw_windows.go b/cmd/waiter/raw_windows.go new file mode 100644 index 0000000..8d2f769 --- /dev/null +++ b/cmd/waiter/raw_windows.go @@ -0,0 +1,52 @@ +//go:build windows + +package main + +import ( + "os" + "syscall" + "unsafe" +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procSetConsoleMode = kernel32.NewProc("SetConsoleMode") + procGetStdHandle = kernel32.NewProc("GetStdHandle") +) + +const ( + stdInputHandle = ^uint32(9) + 1 // -10 + enableVirtualTerminalProcessing = 0x0004 + enableProcessedOutput = 0x0001 + enableWrapAtEOLOutput = 0x0002 + disableNewlineAutoReturn = 0x0008 +) + +func setRawMode(fd int) (func(), error) { + if fd == 0 { + fd = int(os.Stdin.Fd()) + } + if !isTerminal(fd) { + return func() {}, nil + } + // Enable virtual terminal processing for ANSI escape sequences on Windows + h, _, _ := procGetStdHandle.Call(uintptr(^uint32(10) + 1)) // STD_OUTPUT_HANDLE = -11 + if h != 0 && h != ^uintptr(0) { + var mode uint32 + procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&mode))) + newMode := mode | enableVirtualTerminalProcessing | enableProcessedOutput | enableWrapAtEOLOutput + procSetConsoleMode.Call(h, uintptr(newMode)) + } + return func() {}, nil +} + +func isTerminal(fd int) bool { + var mode uint32 + h, _, _ := procGetStdHandle.Call(uintptr(^uint32(10) + 1)) // STD_OUTPUT_HANDLE = -11 + if h == 0 || h == ^uintptr(0) { + return false + } + ret, _, _ := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&mode))) + return ret != 0 +} diff --git a/cmd/waiter/waiter.rc b/cmd/waiter/waiter.rc new file mode 100644 index 0000000..af6a395 --- /dev/null +++ b/cmd/waiter/waiter.rc @@ -0,0 +1 @@ +1 ICON "E:/program/homeagent/homeagent/build/icon.ico" diff --git a/cmd/waiter/waiter.syso b/cmd/waiter/waiter.syso new file mode 100644 index 0000000..0a1e60e Binary files /dev/null and b/cmd/waiter/waiter.syso differ diff --git a/go.mod b/go.mod index d4662da..a824350 100644 --- a/go.mod +++ b/go.mod @@ -12,4 +12,4 @@ require github.com/yanyiwu/gojieba v1.4.7 require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0-20260708004841-e9bdcf9304b0 // direct -replace gitcode.com/JianFeeeee/homeagent-sdk => /tmp/opencode/homeagent-sdk-repo +replace gitcode.com/JianFeeeee/homeagent-sdk => E:\program\homeagent\homeagentsdk diff --git a/package/installer.nsi b/package/installer.nsi new file mode 100644 index 0000000..3e41704 --- /dev/null +++ b/package/installer.nsi @@ -0,0 +1,326 @@ +!include "MUI2.nsh" +!include "nsDialogs.nsh" +!include "LogicLib.nsh" +!include "WinVer.nsh" +!include "x64.nsh" + +!ifndef VARIANT + !define VARIANT "full" +!endif + +!define PRODUCT_NAME "HomeAgent" +!define PRODUCT_PUBLISHER "HomeAgent Team" +!define PRODUCT_VERSION "0.7.1" + +!if "${VARIANT}" == "full" + !define PRODUCT_DISPLAY_NAME "HomeAgent 完整版" + !define OUTPUT_FILE "HomeAgent_v${PRODUCT_VERSION}_Full_win64.exe" + !define HAS_CORE 1 + !define HAS_WAITER 1 + !define HAS_GUI 1 + !define HAS_CREDENTIALS 1 +!else if "${VARIANT}" == "server" + !define PRODUCT_DISPLAY_NAME "HomeAgent 服务端" + !define OUTPUT_FILE "HomeAgent_v${PRODUCT_VERSION}_Server_win64.exe" + !define HAS_CORE 1 + !define HAS_WAITER 0 + !define HAS_GUI 0 + !define HAS_CREDENTIALS 1 +!else if "${VARIANT}" == "client" + !define PRODUCT_DISPLAY_NAME "HomeAgent 客户端" + !define OUTPUT_FILE "HomeAgent_v${PRODUCT_VERSION}_Client_win64.exe" + !define HAS_CORE 0 + !define HAS_WAITER 1 + !define HAS_GUI 1 + !define HAS_CREDENTIALS 0 +!else + !error "Unknown variant: ${VARIANT}" +!endif + +Name "${PRODUCT_DISPLAY_NAME}" +OutFile "${OUTPUT_FILE}" +InstallDir "$PROGRAMFILES64\${PRODUCT_NAME}" +InstallDirRegKey HKLM "Software\${PRODUCT_NAME}" "" +RequestExecutionLevel admin +BrandingText "HomeAgent Installer" +SetCompressor /SOLID lzma +ShowInstDetails show +ShowUninstDetails show + +Var apiKey +Var webuiUsername +Var webuiPassword +Var hwndApiKey +Var hwndUsername +Var hwndPassword +Var autoStart +Var startNow +Var hwndAutoStart +Var hwndStartNow + +Function GenKey + nsExec::ExecToStack 'powershell -NoProfile -C "[System.Guid]::NewGuid().ToString($\'N$\')"' + Pop $0 + Pop $1 + ${If} $1 == "" + StrCpy $1 "homeagent" + ${Else} + StrCpy $1 $1 32 + ${EndIf} + Push $1 +FunctionEnd + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_DIRECTORY + +!if "${HAS_CREDENTIALS}" == "1" +Page custom pageApiKeys pageApiKeysLeave +Page custom pageCredentials pageCredentialsLeave +!endif + +!if "${HAS_CORE}" == "1" +Page custom pageStartupOptions pageStartupOptionsLeave +!endif + +!insertmacro MUI_PAGE_INSTFILES + +Page custom pageFinishSummary + +!insertmacro MUI_LANGUAGE "SimpChinese" +!insertmacro MUI_LANGUAGE "English" + +Function .onInit + !insertmacro MUI_LANGDLL_DISPLAY + StrCpy $autoStart "1" + StrCpy $startNow "1" +!if "${HAS_CREDENTIALS}" == "0" + Call GenKey + Pop $apiKey +!endif +FunctionEnd + +!if "${HAS_CORE}" == "1" + +Function pageStartupOptions + !insertmacro MUI_HEADER_TEXT "启动选项" "设置 HomeAgent 后端的启动方式" + nsDialogs::Create 1018 + Pop $0 + ${If} $0 == error + Abort + ${EndIf} + ${NSD_CreateLabel} 0 5u 100% 20u "HomeAgent 后端 (homed) 是持续运行的服务进程。$\r$\n请选择启动方式:" + Pop $0 + ${NSD_CreateCheckBox} 10u 35u 100% 12u "开机自动启动后端 (添加到注册表启动项)" + Pop $hwndAutoStart + ${If} $autoStart == "1" + ${NSD_Check} $hwndAutoStart + ${EndIf} + ${NSD_CreateCheckBox} 10u 55u 100% 12u "安装完成后立即启动后端" + Pop $hwndStartNow + ${If} $startNow == "1" + ${NSD_Check} $hwndStartNow + ${EndIf} + ${NSD_CreateLabel} 10u 80u 100% 20u "如果选择开机自启动,homed 将在每次登录 Windows 时自动运行。$\r$\n你也可以稍后从开始菜单手动启动。" + Pop $0 + nsDialogs::Show +FunctionEnd + +Function pageStartupOptionsLeave + ${NSD_GetState} $hwndAutoStart $autoStart + ${NSD_GetState} $hwndStartNow $startNow +FunctionEnd + +!endif + +!if "${HAS_CREDENTIALS}" == "1" + +Function pageApiKeys + !insertmacro MUI_HEADER_TEXT "生成 API 密钥" "请复制此密钥,安装完成后将无法再次查看" + nsDialogs::Create 1018 + Pop $0 + ${If} $0 == error + Abort + ${EndIf} + Call GenKey + Pop $apiKey + ${NSD_CreateLabel} 0 5u 100% 12u "API Key (用于 WebUI 和 GUI 认证):" + Pop $0 + ${NSD_CreateText} 0 20u 300u 12u $apiKey + Pop $hwndApiKey + ${NSD_CreateLabel} 0 45u 100% 30u "请用鼠标选中文本框中的密钥并复制 (Ctrl+C)。$\r$\n此密钥同时用于:$\r$\n • Web 管理界面 (http://localhost:8080) 的 API 认证$\r$\n • 桌面 GUI 应用的自动连接配置" + Pop $0 + nsDialogs::Show +FunctionEnd + +Function pageApiKeysLeave + ${NSD_GetText} $hwndApiKey $apiKey +FunctionEnd + +Function pageCredentials + !insertmacro MUI_HEADER_TEXT "WebUI 登录设置" "设置 Web 管理界面的登录账号和密码" + nsDialogs::Create 1018 + Pop $0 + ${If} $0 == error + Abort + ${EndIf} + StrCpy $webuiUsername "admin" + ${NSD_CreateLabel} 0 5u 70u 12u "用户名:" + Pop $0 + ${NSD_CreateText} 85u 5u 180u 12u $webuiUsername + Pop $hwndUsername + ${NSD_CreateLabel} 0 25u 70u 12u "密码:" + Pop $0 + ${NSD_CreatePassword} 85u 25u 180u 12u "" + Pop $hwndPassword + ${NSD_CreateLabel} 0 50u 100% 20u "这些凭证用于登录 Web 管理界面 http://localhost:8080" + Pop $0 + nsDialogs::Show +FunctionEnd + +Function pageCredentialsLeave + ${NSD_GetText} $hwndUsername $webuiUsername + ${NSD_GetText} $hwndPassword $webuiPassword + ${If} $webuiPassword == "" + StrCpy $webuiPassword "homeagent" + ${EndIf} +FunctionEnd + +!endif + +Function pageFinishSummary + !insertmacro MUI_HEADER_TEXT "安装完成" "以下为安装的关键信息,请截图或记录" + nsDialogs::Create 1018 + Pop $0 + ${If} $0 == error + Abort + ${EndIf} + ${NSD_CreateLabel} 0 5u 100% 12u "API Key: $apiKey" + Pop $0 + ${NSD_CreateLabel} 0 20u 100% 12u "GUI 预配置: 已自动写入连接配置" + Pop $0 + ${NSD_CreateLabel} 0 35u 100% 12u "WebUI 地址: http://localhost:8080" + Pop $0 +!if "${HAS_CREDENTIALS}" == "1" + ${NSD_CreateLabel} 0 50u 100% 12u "WebUI 用户名: $webuiUsername" + Pop $0 + ${NSD_CreateLabel} 0 65u 100% 12u "WebUI 密码: $webuiPassword" + Pop $0 +!endif + nsDialogs::Show +FunctionEnd + +Section "Install" SEC_INSTALL + SetOutPath "$INSTDIR" + CreateDirectory "$INSTDIR\data" + CreateDirectory "$INSTDIR\data\log" + CreateDirectory "$INSTDIR\data\plugins" + CreateDirectory "$INSTDIR\data\adapters" + +!if "${HAS_CORE}" == "1" + File "initconfig.exe" + File "homed.exe" +!endif + +!if "${HAS_WAITER}" == "1" + File "waiter.exe" +!endif + +!if "${HAS_GUI}" == "1" + SetOutPath "$INSTDIR\homeagent-gui-win32-x64" + File /r "homeagent-gui-win32-x64\*.*" + SetOutPath "$INSTDIR" +!endif + +!if "${HAS_CORE}" == "1" + DetailPrint "初始化配置数据库..." + nsExec::Exec '"$INSTDIR\initconfig.exe" -data "$INSTDIR\data" -username "$webuiUsername" -password "$webuiPassword" -apikey "$apiKey"' + Pop $0 + ${If} $0 != 0 + DetailPrint "警告: 数据库初始化可能未成功完成" + ${EndIf} +!endif + +!if "${HAS_GUI}" == "1" + DetailPrint "配置 GUI 连接..." + CreateDirectory "$APPDATA\homeagent-gui" + FileOpen $0 "$APPDATA\homeagent-gui\connections.json" w + FileWrite $0 '{$\r$\n "connections": [$\r$\n {$\r$\n "id": "local",$\r$\n "name": "本地",$\r$\n "url": "http://localhost:8080",$\r$\n "apiKey": "$apiKey"$\r$\n }$\r$\n ],$\r$\n "currentId": "local"$\r$\n}' + FileClose $0 + ; 同时写入 GUI 包目录作为备用(适配 UAC 提升后 $APPDATA 异常的情况) + CreateDirectory "$INSTDIR\homeagent-gui-win32-x64\resources\app" + FileOpen $0 "$INSTDIR\homeagent-gui-win32-x64\resources\app\connections.json" w + FileWrite $0 '{$\r$\n "connections": [$\r$\n {$\r$\n "id": "local",$\r$\n "name": "本地",$\r$\n "url": "http://localhost:8080",$\r$\n "apiKey": "$apiKey"$\r$\n }$\r$\n ],$\r$\n "currentId": "local"$\r$\n}' + FileClose $0 +!endif + +!if "${HAS_WAITER}" == "1" + DetailPrint "配置 CLI 连接..." + FileOpen $0 "$INSTDIR\waiter.yaml" w + FileWrite $0 "socket: $\"$INSTDIR\data\cli.sock$\"$\r$\napi_key: $apiKey$\r$\ndefault: local$\r$\nconnections:$\r$\n - name: local$\r$\n socket: $\"$INSTDIR\data\cli.sock$\"$\r$\n api_key: $apiKey$\r$\n" + FileClose $0 +!endif + + DetailPrint "创建快捷方式..." + CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}" +!if "${HAS_CORE}" == "1" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\HomeAgent Server.lnk" "$INSTDIR\homed.exe" '-data "$INSTDIR\data"' "$INSTDIR\homed.exe" 0 +!endif +!if "${HAS_WAITER}" == "1" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\HomeAgent CLI.lnk" "$INSTDIR\waiter.exe" "" "$INSTDIR\waiter.exe" 0 +!endif +!if "${HAS_GUI}" == "1" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\HomeAgent GUI.lnk" "$INSTDIR\homeagent-gui-win32-x64\homeagent-gui.exe" "" "$INSTDIR\homeagent-gui-win32-x64\homeagent-gui.exe" 0 +!endif + + DetailPrint "设置环境变量..." + WriteRegStr HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "HOMEAGENT_DATA" "$INSTDIR\data" + WriteRegStr HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "HOMEAGENT_SOCKET" "$INSTDIR\data\cli.sock" + +!if "${HAS_CORE}" == "1" + ${If} $autoStart == "1" + DetailPrint "设置开机自启动..." + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "HomeAgent" '"$INSTDIR\homed.exe" -data "$INSTDIR\data"' + ${EndIf} +!endif + + DetailPrint "写入注册表..." + WriteRegStr HKLM "Software\${PRODUCT_NAME}" "" "$INSTDIR" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayName" "${PRODUCT_DISPLAY_NAME}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "UninstallString" "$INSTDIR\Uninstall.exe" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "InstallLocation" "$INSTDIR" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "Publisher" "${PRODUCT_PUBLISHER}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayVersion" "${PRODUCT_VERSION}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "NoModify" 1 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "NoRepair" 1 + + WriteUninstaller "$INSTDIR\Uninstall.exe" + +!if "${HAS_CORE}" == "1" + ${If} $startNow == "1" + DetailPrint "启动 HomeAgent 后端..." + Exec '"$INSTDIR\homed.exe" -data "$INSTDIR\data"' + ${EndIf} +!endif +SectionEnd + +Section "Uninstall" +!if "${HAS_CORE}" == "1" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "HomeAgent" +!endif + Delete "$INSTDIR\Uninstall.exe" + Delete "$INSTDIR\initconfig.exe" + Delete "$INSTDIR\homed.exe" + Delete "$INSTDIR\waiter.exe" + Delete "$INSTDIR\waiter.yaml" + RMDir /r "$INSTDIR\data" + RMDir /r "$INSTDIR\homeagent-gui-win32-x64" + RMDir "$INSTDIR" + Delete "$SMPROGRAMS\${PRODUCT_NAME}\HomeAgent Server.lnk" + Delete "$SMPROGRAMS\${PRODUCT_NAME}\HomeAgent CLI.lnk" + Delete "$SMPROGRAMS\${PRODUCT_NAME}\HomeAgent GUI.lnk" + RMDir "$SMPROGRAMS\${PRODUCT_NAME}" + DeleteRegValue HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "HOMEAGENT_DATA" + DeleteRegValue HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "HOMEAGENT_SOCKET" + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" + DeleteRegKey HKLM "Software\${PRODUCT_NAME}" +SectionEnd diff --git a/package/toolchain.nsi b/package/toolchain.nsi new file mode 100644 index 0000000..5e3685d --- /dev/null +++ b/package/toolchain.nsi @@ -0,0 +1,137 @@ +!include "MUI2.nsh" +!include "nsDialogs.nsh" +!include "LogicLib.nsh" +!include "x64.nsh" +!include "WinVer.nsh" + +!define PRODUCT_NAME "HomeAgent Toolchain" +!define PRODUCT_PUBLISHER "HomeAgent Team" +!define PRODUCT_VERSION "0.7.1" +!define PRODUCT_DISPLAY_NAME "HomeAgent 工具链" +!define OUTPUT_FILE "HomeAgent_v${PRODUCT_VERSION}_Toolchain_win64.exe" +!define SDK_VERSION "v0.7.1" + +Name "${PRODUCT_DISPLAY_NAME} v${PRODUCT_VERSION}" +OutFile "${OUTPUT_FILE}" +InstallDir "$PROGRAMFILES64\${PRODUCT_NAME}" +InstallDirRegKey HKLM "Software\${PRODUCT_NAME}" "" +RequestExecutionLevel admin +BrandingText "HomeAgent Toolchain Installer" +SetCompressor /SOLID lzma +ShowInstDetails show +ShowUninstDetails show + +Var hasGit +Var sdkInstallOk + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_DIRECTORY +Page custom pageConfirm pageConfirmLeave +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_LANGUAGE "SimpChinese" +!insertmacro MUI_LANGUAGE "English" + +Function .onInit + !insertmacro MUI_LANGDLL_DISPLAY + StrCpy $hasGit "0" + StrCpy $sdkInstallOk "0" +FunctionEnd + +Function pageConfirm + !insertmacro MUI_HEADER_TEXT "确认安装" "将安装 HomeAgent 工具链并自动下载 SDK ${SDK_VERSION}" + nsDialogs::Create 1018 + Pop $0 + ${If} $0 == error + Abort + ${EndIf} + ${NSD_CreateLabel} 0 5u 100% 12u "将安装以下组件:" + Pop $0 + ${NSD_CreateLabel} 15u 20u 100% 12u "• plugindev.exe — 插件开发工具" + Pop $0 + ${NSD_CreateLabel} 15u 35u 100% 12u "• SDK ${SDK_VERSION} — 将从远程仓库自动下载" + Pop $0 + ${NSD_CreateLabel} 0 60u 100% 20u "SDK 需要 Git 客户端。如果未安装 Git,请先安装:$\r$\nhttps://git-scm.com/downloads" + Pop $0 + nsDialogs::Show +FunctionEnd + +Function pageConfirmLeave +FunctionEnd + +Section "Install" SEC_INSTALL + SetOutPath "$INSTDIR" + + DetailPrint "复制工具链文件..." + File "plugindev.exe" + + DetailPrint "创建快捷方式..." + CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\plugindev.lnk" "$INSTDIR\plugindev.exe" "" "$INSTDIR\plugindev.exe" 0 + + DetailPrint "配置环境变量..." + ; Add to system PATH + ReadRegStr $0 HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "PATH" + ${If} $0 != "" + ${If} $0 != "*$INSTDIR*" + StrCpy $0 "$0;$INSTDIR" + WriteRegStr HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "PATH" $0 + ${EndIf} + ${Else} + WriteRegStr HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "PATH" "$INSTDIR" + ${EndIf} + WriteRegStr HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "HOMEAGENT_SDK_DIR" "$INSTDIR\sdk" + WriteRegStr HKLM "Software\${PRODUCT_NAME}" "" "$INSTDIR" + + DetailPrint "检测 Git 客户端..." + nsExec::ExecToStack '"git" --version' + Pop $0 + Pop $1 + ${If} $0 == 0 + StrCpy $hasGit "1" + DetailPrint "Git 已安装: $1" + ${Else} + DetailPrint "未检测到 Git,将跳过 SDK 自动下载" + DetailPrint "安装完成后请手动运行: plugindev sdk install ${SDK_VERSION}" + ${EndIf} + + ${If} $hasGit == "1" + DetailPrint "正在下载 SDK ${SDK_VERSION}..." + nsExec::ExecToStack '"$INSTDIR\plugindev.exe" sdk install ${SDK_VERSION}' + Pop $0 + Pop $1 + ${If} $0 == 0 + StrCpy $sdkInstallOk "1" + DetailPrint "SDK ${SDK_VERSION} 下载完成" + DetailPrint "正在激活 SDK ${SDK_VERSION}..." + nsExec::Exec '"$INSTDIR\plugindev.exe" sdk use ${SDK_VERSION}' + Pop $0 + ${Else} + DetailPrint "SDK 下载失败 (错误码: $0)" + DetailPrint "请手动运行: plugindev sdk install ${SDK_VERSION}" + ${EndIf} + ${EndIf} + + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayName" "${PRODUCT_DISPLAY_NAME} v${PRODUCT_VERSION}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "UninstallString" "$INSTDIR\Uninstall.exe" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "InstallLocation" "$INSTDIR" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "Publisher" "${PRODUCT_PUBLISHER}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayVersion" "${PRODUCT_VERSION}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "NoModify" 1 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "NoRepair" 1 + + WriteUninstaller "$INSTDIR\Uninstall.exe" +SectionEnd + +Section "Uninstall" + Delete "$INSTDIR\Uninstall.exe" + Delete "$INSTDIR\plugindev.exe" + RMDir /r "$INSTDIR\sdk" + RMDir "$INSTDIR" + Delete "$SMPROGRAMS\${PRODUCT_NAME}\plugindev.lnk" + RMDir "$SMPROGRAMS\${PRODUCT_NAME}" + DeleteRegValue HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "HOMEAGENT_SDK_DIR" + DeleteRegKey HKLM "Software\Microsoft\CurrentVersion\Uninstall\${PRODUCT_NAME}" + DeleteRegKey HKLM "Software\${PRODUCT_NAME}" +SectionEnd