desktop-pet-app 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/out/main/index.js CHANGED
@@ -23,11 +23,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
  const electron = require("electron");
25
25
  const node_path = require("node:path");
26
+ const node_fs = require("node:fs");
26
27
  const path = require("path");
27
28
  const os = require("os");
28
29
  const node_crypto = require("node:crypto");
29
30
  const node_http = require("node:http");
30
- const node_fs = require("node:fs");
31
31
  const node_os = require("node:os");
32
32
  const crypto = require("crypto");
33
33
  const WebSocket = require("ws");
@@ -66,7 +66,20 @@ function levelFromEnv() {
66
66
  const v = (process.env.DESKTOP_PET_LOG_LEVEL ?? "info").toLowerCase();
67
67
  return v === "debug" || v === "warn" || v === "error" ? v : "info";
68
68
  }
69
- const logger = new Logger(levelFromEnv());
69
+ function mainLogDirectory() {
70
+ return node_path.join(electron.app.getPath("userData"), "logs");
71
+ }
72
+ function writeMainLog(line) {
73
+ console.log(line);
74
+ try {
75
+ const directory = mainLogDirectory();
76
+ node_fs.mkdirSync(directory, { recursive: true });
77
+ node_fs.appendFileSync(node_path.join(directory, "main.log"), `${line}
78
+ `, "utf8");
79
+ } catch {
80
+ }
81
+ }
82
+ const logger = new Logger(levelFromEnv(), writeMainLog);
70
83
  const PORT_FILE = process.env.DESKTOP_PET_PORT_FILE?.trim() || path.join(os.homedir(), ".desktop-pet-port");
71
84
  const LOCAL_PET_PROTOCOL_VERSION = "1.0";
72
85
  const LOCAL_PET_MAX_BODY_BYTES = 64 * 1024;
@@ -268,9 +281,11 @@ class PetManager {
268
281
  pets = /* @__PURE__ */ new Map();
269
282
  lastGreetAt = /* @__PURE__ */ new Map();
270
283
  timer;
284
+ petsVisible = true;
271
285
  register(id, win, skin, identity) {
272
286
  this.pets.set(id, { id, win, skin, identity });
273
287
  win.on("closed", () => this.pets.delete(id));
288
+ if (!this.petsVisible) win.hide();
274
289
  if (!this.timer) this.timer = setInterval(() => this.checkProximity(), PROXIMITY_SCAN_MS);
275
290
  logger.info("pet registered", { id, skin, total: this.pets.size });
276
291
  }
@@ -282,6 +297,17 @@ class PetManager {
282
297
  (pet) => !pet.win.isDestroyed() && pet.identity.kind === "primary"
283
298
  );
284
299
  }
300
+ arePetsVisible() {
301
+ return this.petsVisible;
302
+ }
303
+ setPetsVisible(visible) {
304
+ this.petsVisible = visible;
305
+ for (const pet of this.pets.values()) {
306
+ if (pet.win.isDestroyed()) continue;
307
+ if (visible) pet.win.showInactive();
308
+ else pet.win.hide();
309
+ }
310
+ }
285
311
  findRemoteFriend(username) {
286
312
  const normalized = username.trim().toLowerCase();
287
313
  return [...this.pets.values()].find(
@@ -1653,7 +1679,7 @@ async function fallbackChat(operationId) {
1653
1679
  const REQUEST_TIMEOUT_MS = 6e3;
1654
1680
  const DOWNLOAD_TIMEOUT_MS = 10 * 6e4;
1655
1681
  const LEGACY_OFFICIAL_HTTP_HOST = "47.94.20.104";
1656
- const MAC_INSTALLED_APP_EXECUTABLE = "/Applications/Desktop Pet.app/Contents/MacOS/Desktop Pet";
1682
+ const MAC_BUNDLE_ID = "com.xinshu.desktoppet";
1657
1683
  let status = { kind: "idle" };
1658
1684
  function updateCheckURL() {
1659
1685
  const override = process.env.DESKTOP_PET_UPDATE_API?.trim();
@@ -1767,8 +1793,8 @@ async function installDownloadedUpdate() {
1767
1793
  downloaded.artifactPath,
1768
1794
  downloaded.update.sha256,
1769
1795
  process.platform,
1770
- restartExecutableAfterUpdate(),
1771
- process.execPath
1796
+ process.execPath,
1797
+ JSON.stringify(restartArgumentsAfterUpdate())
1772
1798
  ], {
1773
1799
  detached: true,
1774
1800
  stdio: "ignore",
@@ -1783,11 +1809,8 @@ async function installDownloadedUpdate() {
1783
1809
  function supportsInAppInstall() {
1784
1810
  return electron.app.isPackaged || process.env.DESKTOP_PET_NPM_DISTRIBUTION === "1";
1785
1811
  }
1786
- function restartExecutableAfterUpdate() {
1787
- if (!electron.app.isPackaged && process.env.DESKTOP_PET_NPM_DISTRIBUTION === "1" && process.platform === "darwin") {
1788
- return MAC_INSTALLED_APP_EXECUTABLE;
1789
- }
1790
- return process.execPath;
1812
+ function restartArgumentsAfterUpdate() {
1813
+ return !electron.app.isPackaged && process.argv[1] ? [process.argv[1]] : [];
1791
1814
  }
1792
1815
  async function saveAndHash(body, destination, onChunk) {
1793
1816
  const hash = node_crypto.createHash("sha256");
@@ -1838,8 +1861,9 @@ const { dirname, join } = require('node:path')
1838
1861
  const { execFile, spawn } = require('node:child_process')
1839
1862
  const { promisify } = require('node:util')
1840
1863
 
1841
- const [, , parentPidText, sourcePath, expectedSHA256, platform, installedAppExecutable, fallbackExecutable] = process.argv
1864
+ const [, , parentPidText, sourcePath, expectedSHA256, platform, fallbackExecutable, fallbackArgsJSON] = process.argv
1842
1865
  const parentPid = Number(parentPidText)
1866
+ const fallbackArgs = JSON.parse(fallbackArgsJSON || '[]')
1843
1867
  const run = promisify(execFile)
1844
1868
 
1845
1869
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
@@ -1853,10 +1877,21 @@ const sha256 = (path) => new Promise((resolve, reject) => {
1853
1877
  input.on('error', reject)
1854
1878
  input.on('end', () => resolve(hash.digest('hex')))
1855
1879
  })
1856
- const restart = (executable) => {
1880
+ const restartFallback = () => {
1857
1881
  const env = { ...process.env }
1858
1882
  delete env.ELECTRON_RUN_AS_NODE
1859
- spawn(executable, [], { detached: true, stdio: 'ignore', env }).unref()
1883
+ const child = spawn(fallbackExecutable, fallbackArgs, { detached: true, stdio: 'ignore', env })
1884
+ child.on('error', () => undefined)
1885
+ child.unref()
1886
+ }
1887
+ const restartInstalledApp = async () => {
1888
+ // 安装器可能会保留已有应用的位置(开发构建尤其如此),不能假设它在 /Applications。
1889
+ // 让 LaunchServices 按 bundle ID 查找刚安装的实际应用位置。
1890
+ if (platform === 'darwin') {
1891
+ await run('/usr/bin/open', ['-b', '${MAC_BUNDLE_ID}'])
1892
+ return
1893
+ }
1894
+ restartFallback()
1860
1895
  }
1861
1896
  const shellQuote = (value) => "'" + value.replace(/'/g, "'\\''") + "'"
1862
1897
 
@@ -1885,11 +1920,20 @@ async function main() {
1885
1920
  } catch (error) {
1886
1921
  await fs.writeFile(join(dirname(sourcePath), 'last-install-error.log'), String(error)).catch(() => undefined)
1887
1922
  } finally {
1888
- restart(installed ? installedAppExecutable : fallbackExecutable)
1923
+ if (installed) {
1924
+ try {
1925
+ await restartInstalledApp()
1926
+ } catch (error) {
1927
+ await fs.writeFile(join(dirname(sourcePath), 'last-restart-error.log'), String(error)).catch(() => undefined)
1928
+ restartFallback()
1929
+ }
1930
+ } else {
1931
+ restartFallback()
1932
+ }
1889
1933
  }
1890
1934
  }
1891
1935
 
1892
- main().catch(() => restart(fallbackExecutable))
1936
+ main().catch(() => restartFallback())
1893
1937
  `;
1894
1938
  function installerExtension(releaseUrl) {
1895
1939
  const pathname = new URL(releaseUrl).pathname.toLowerCase();
@@ -1999,6 +2043,7 @@ function createPetWindow(skin, options) {
1999
2043
  const x = options.near?.x ?? workArea.x + workArea.width - W - 120;
2000
2044
  const y = options.near?.y ?? workArea.y + workArea.height - H - 60;
2001
2045
  const win = new electron.BrowserWindow({
2046
+ show: petManager.arePetsVisible(),
2002
2047
  width: W,
2003
2048
  height: H,
2004
2049
  x,
@@ -2132,11 +2177,20 @@ function createPetWindow(skin, options) {
2132
2177
  dispatch({ type: "bubble", text: result.message, ttl: 6e3 }, id);
2133
2178
  }
2134
2179
  },
2180
+ {
2181
+ label: "打开日志文件夹",
2182
+ click: () => {
2183
+ void electron.shell.openPath(mainLogDirectory()).then((error) => {
2184
+ if (error) dispatch({ type: "bubble", text: "无法打开日志文件夹", ttl: 6e3 }, id);
2185
+ });
2186
+ }
2187
+ },
2135
2188
  {
2136
2189
  label: updateMenuLabel(),
2137
2190
  submenu: updateMenuItems(id)
2138
2191
  },
2139
2192
  { type: "separator" },
2193
+ { label: "隐藏所有桌面宠物", click: () => petManager.setPetsVisible(false) },
2140
2194
  ...canSendAwayPet(identity) ? [{ label: "送走这只宠物", click: () => win.close() }] : [],
2141
2195
  { label: "退出", click: () => electron.app.quit() }
2142
2196
  ]);
@@ -3046,6 +3100,39 @@ async function dutyLoop(adapter) {
3046
3100
  }
3047
3101
  }
3048
3102
  }
3103
+ const trayIcon1xPath = path.join(__dirname, "./chunks/tray-iconTemplate-BIK86F-0.png");
3104
+ const trayIcon2xPath = path.join(__dirname, "./chunks/tray-iconTemplate@2x-B19HpcaE.png");
3105
+ let tray;
3106
+ function createMenuBar() {
3107
+ if (process.platform !== "darwin" || tray) return;
3108
+ tray = new electron.Tray(createPetTemplateImage());
3109
+ tray.setToolTip("Desktop Pet");
3110
+ tray.on("click", showMenu);
3111
+ tray.on("right-click", showMenu);
3112
+ }
3113
+ function destroyMenuBar() {
3114
+ tray?.destroy();
3115
+ tray = void 0;
3116
+ }
3117
+ function showMenu() {
3118
+ tray?.popUpContextMenu(electron.Menu.buildFromTemplate([
3119
+ { label: `Desktop Pet v${electron.app.getVersion()}`, enabled: false },
3120
+ { type: "separator" },
3121
+ {
3122
+ label: petManager.arePetsVisible() ? "隐藏所有桌面宠物" : "显示所有桌面宠物",
3123
+ click: () => petManager.setPetsVisible(!petManager.arePetsVisible())
3124
+ },
3125
+ { type: "separator" },
3126
+ { label: "退出 Desktop Pet", click: () => electron.app.quit() }
3127
+ ]));
3128
+ }
3129
+ function createPetTemplateImage() {
3130
+ let image = electron.nativeImage.createFromBuffer(node_fs.readFileSync(trayIcon2xPath), { scaleFactor: 2 });
3131
+ if (image.isEmpty()) image = electron.nativeImage.createFromPath(trayIcon1xPath);
3132
+ if (image.isEmpty()) throw new Error("无法加载 macOS 菜单栏图标");
3133
+ image.setTemplateImage(true);
3134
+ return image;
3135
+ }
3049
3136
  const profileDirectory = process.env.DESKTOP_PET_PROFILE?.trim();
3050
3137
  if (profileDirectory) {
3051
3138
  electron.app.setPath("userData", node_path.resolve(profileDirectory));
@@ -3058,6 +3145,7 @@ if (!singleInstance) {
3058
3145
  electron.app.on("second-instance", (_event, argv) => {
3059
3146
  const callback = argv.find((value) => value.startsWith("desktop-pet://"));
3060
3147
  if (callback) handleAccountCallback(callback);
3148
+ petManager.setPetsVisible(true);
3061
3149
  ensurePrimaryPet();
3062
3150
  });
3063
3151
  }
@@ -3068,6 +3156,7 @@ function ensurePrimaryPet() {
3068
3156
  });
3069
3157
  }
3070
3158
  electron.app.whenReady().then(async () => {
3159
+ createMenuBar();
3071
3160
  await startAccountCallbackRouter();
3072
3161
  startEventServer();
3073
3162
  const activated = await activateStoredDevice().catch((err) => {
@@ -3092,9 +3181,10 @@ electron.app.whenReady().then(async () => {
3092
3181
  logger.info("app started", { firstPet: id });
3093
3182
  });
3094
3183
  electron.app.on("window-all-closed", () => {
3095
- electron.app.quit();
3184
+ if (process.platform !== "darwin") electron.app.quit();
3096
3185
  });
3097
3186
  electron.app.on("quit", () => {
3187
+ destroyMenuBar();
3098
3188
  stopAccountCallbackRouter();
3099
3189
  stopRelay();
3100
3190
  stopP2PWindow();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "desktop-pet-app",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "AI desktop pet with MCP support and a self-hosted relay server",
5
5
  "license": "MIT",
6
6
  "keywords": [