feat: Windows NSIS installer, embedded icons, GUI auto-launch backend

- Add package/installer.nsi (Full/Server/Client) and toolchain.nsi
- Embed icon.ico (rounded corners) into homed.exe/waiter.exe via .syso
- GUI auto-launches homed.exe from parent dir (Windows only)
- GUI fallback connections.json from app resource dir
- Add initconfig command for config.db seeding
- Replace waiter rawmode* with raw_{unix,windows,other}.go
- Fix .gitignore: /homed instead of homed, add login.json
- Update icon.svg with rounded rect, new mascot.svg
This commit is contained in:
JianFeeeee
2026-07-19 23:46:16 +08:00
parent 7609c39191
commit 0edd7c0b47
20 changed files with 1631 additions and 15 deletions

4
.gitignore vendored
View File

@ -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/

BIN
cmd/gui/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

View File

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
<rect width="400" height="400" fill="#F8FAFC"/>
<rect width="400" height="400" rx="60" ry="60" fill="#F8FAFC"/>
<g transform="translate(200,200)">
<circle cx="0" cy="0" r="105" fill="none" stroke="#E2E8F0" stroke-width="2.5" stroke-dasharray="8,6"/>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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();
}
});

View File

@ -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",

View File

@ -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",

View File

@ -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 += '<div class="msg msg-system"><div class="msg-bubble">' + body + '</div></div>';
} else {
var userAvatar = '<svg viewBox="0 0 24 24" style="width:16px;height:16px" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 4-6 8-6s8 2 8 6"/></svg>';
var aiAvatar = '<img src="/mascot.webp" style="width:28px;height:28px;border-radius:50%;object-fit:cover" alt="小宅">';
var aiAvatar = '<img src="mascot.svg" style="width:28px;height:28px;border-radius:50%;object-fit:cover" alt="' + __('小宅','Agent') + '">';
html += '<div class="msg msg-' + role + '">'
+ '<div class="msg-avatar">' + (role === 'user' ? userAvatar : aiAvatar) + '</div>'
+ '<div class="msg-content"><div class="msg-bubble">' + body + '</div></div>'

View File

@ -45,7 +45,7 @@
<a onclick="switchTab('adapters')" data-i18n="navAdapters">适配器</a>
<a onclick="switchTab('kernel')" data-i18n="navKernel">内核</a>
<div style="margin-left:auto;display:flex;align-items:center;gap:8px">
<span id="conn-status" class="conn-indicator" onclick="openConnManager()" title="点击管理连接">
<span id="conn-status" class="conn-indicator" onclick="openConnManager()" title="点击管理连接 / Click to manage connections">
<span class="status-dot dot-gray" id="conn-dot"></span>
<span id="conn-name-display">未连接</span>
<span style="font-size:10px;margin-left:4px;opacity:0.6"></span>

View File

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
<circle cx="50" cy="50" r="48" fill="#3B82F6"/>
<circle cx="35" cy="40" r="6" fill="white"/>
<circle cx="65" cy="40" r="6" fill="white"/>
<circle cx="35" cy="40" r="3" fill="#1E3A5F"/>
<circle cx="65" cy="40" r="3" fill="#1E3A5F"/>
<path d="M35 65 Q50 80 65 65" stroke="white" stroke-width="3" fill="none" stroke-linecap="round"/>
<ellipse cx="50" cy="58" rx="8" ry="4" fill="#F59E0B"/>
</svg>

After

Width:  |  Height:  |  Size: 496 B

1
cmd/homed/homed.rc Normal file
View File

@ -0,0 +1 @@
1 ICON "E:/program/homeagent/homeagent/build/icon.ico"

BIN
cmd/homed/homed.syso Normal file

Binary file not shown.

67
cmd/initconfig/main.go Normal file
View File

@ -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)
}

11
cmd/waiter/raw_other.go Normal file
View File

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

43
cmd/waiter/raw_unix.go Normal file
View File

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

52
cmd/waiter/raw_windows.go Normal file
View File

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

1
cmd/waiter/waiter.rc Normal file
View File

@ -0,0 +1 @@
1 ICON "E:/program/homeagent/homeagent/build/icon.ico"

BIN
cmd/waiter/waiter.syso Normal file

Binary file not shown.

2
go.mod
View File

@ -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

326
package/installer.nsi Normal file
View File

@ -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

137
package/toolchain.nsi Normal file
View File

@ -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