@walldock/agent 0.1.7 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -44,8 +44,10 @@ const sync_1 = require("./sync");
44
44
  const storage = __importStar(require("./token-storage"));
45
45
  const startup_1 = require("./startup");
46
46
  const pid_1 = require("./pid");
47
+ const tray_1 = require("./tray");
47
48
  // ─── Logging ─────────────────────────────────────────────────────────────────
48
49
  const isDaemon = process.argv.includes('--daemon');
50
+ const isTray = process.argv.includes('--tray');
49
51
  function logDir() {
50
52
  if (process.platform === 'darwin')
51
53
  return path.join(os.homedir(), 'Library', 'Logs');
@@ -61,7 +63,7 @@ async function openLog() {
61
63
  }
62
64
  function log(msg) {
63
65
  const line = `[${new Date().toISOString()}] ${msg}\n`;
64
- if (isDaemon && logStream) {
66
+ if ((isDaemon || isTray) && logStream) {
65
67
  logStream.write(line).catch(() => undefined);
66
68
  }
67
69
  else {
@@ -95,7 +97,7 @@ async function main() {
95
97
  }
96
98
  return;
97
99
  }
98
- if (isDaemon)
100
+ if (isDaemon || isTray)
99
101
  await openLog();
100
102
  // Single-instance guard
101
103
  try {
@@ -106,12 +108,17 @@ async function main() {
106
108
  process.exit(1);
107
109
  }
108
110
  const api = new api_1.AgentApi(process.env['WALLDOCK_API_URL'] ?? undefined);
111
+ let trayHandle = null;
109
112
  const sync = new sync_1.SyncService(api, {
110
113
  onTick(status, lastSyncAt) {
111
- if (status === 'error')
114
+ if (status === 'error') {
112
115
  log('[sync] connection error');
113
- else if (lastSyncAt)
116
+ trayHandle?.updateStatus('connection error');
117
+ }
118
+ else if (lastSyncAt) {
114
119
  log(`[sync] ok at ${lastSyncAt}`);
120
+ trayHandle?.updateStatus('synced ' + new Date(lastSyncAt).toLocaleTimeString());
121
+ }
115
122
  },
116
123
  onLog: log,
117
124
  onInvalidToken() {
@@ -119,8 +126,8 @@ async function main() {
119
126
  log('Device token is no longer valid. Clearing credentials…');
120
127
  await storage.deleteDeviceToken();
121
128
  await storage.clearDeviceIdentity();
122
- if (isDaemon) {
123
- log('Run "walldock-agent" (without --daemon) to re-pair this device.');
129
+ if (isDaemon || isTray) {
130
+ log('Run "walldock-agent" to re-pair this device.');
124
131
  await shutdown();
125
132
  return;
126
133
  }
@@ -147,6 +154,8 @@ async function main() {
147
154
  if (stopping)
148
155
  return;
149
156
  stopping = true;
157
+ trayHandle?.stop();
158
+ trayHandle = null;
150
159
  sync.stop();
151
160
  await (0, pid_1.releaseLock)();
152
161
  log('Walldock Agent stopped.');
@@ -155,14 +164,41 @@ async function main() {
155
164
  }
156
165
  process.on('SIGINT', () => { void shutdown(); });
157
166
  process.on('SIGTERM', () => { void shutdown(); });
167
+ async function maybeStartTray() {
168
+ if (isDaemon)
169
+ return false;
170
+ if (isTray) {
171
+ // Already a detached background process — start the tray in-place
172
+ try {
173
+ trayHandle = (0, tray_1.startTray)(() => { void sync.syncNow(); }, () => { void shutdown(); });
174
+ }
175
+ catch { /* no display — run headlessly */ }
176
+ return false;
177
+ }
178
+ // Console-attached interactive session: re-spawn detached so closing the
179
+ // terminal doesn't kill the agent.
180
+ await (0, pid_1.releaseLock)();
181
+ const { spawn } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
182
+ const child = spawn(process.execPath, [process.argv[1], '--tray'], {
183
+ detached: true,
184
+ stdio: 'ignore',
185
+ windowsHide: true,
186
+ });
187
+ child.unref();
188
+ return true;
189
+ }
158
190
  // Try to resume from stored token
159
191
  log('Walldock Agent starting…');
160
192
  const existing = await (0, auth_1.validateStoredToken)(api);
161
193
  if (existing) {
162
194
  await storage.setDeviceIdentity({ deviceId: existing.deviceId, deviceName: existing.deviceName });
163
195
  log(`Linked as "${existing.deviceName}". Starting sync…`);
196
+ if (await maybeStartTray()) {
197
+ console.log('Walldock Agent is running in the system tray.');
198
+ process.exit(0);
199
+ }
164
200
  sync.start({ token: existing.token, deviceId: existing.deviceId });
165
- return; // sync loop keeps process alive
201
+ return; // sync loop keeps process alive (isTray path)
166
202
  }
167
203
  // No valid token — need to pair
168
204
  if (isDaemon) {
@@ -199,11 +235,14 @@ async function main() {
199
235
  }
200
236
  catch (err) {
201
237
  console.warn(` Could not register for startup: ${err}`);
202
- console.warn(' You can run "walldock-agent --daemon" manually to start syncing.');
238
+ console.warn(' You can run "walldock-agent" manually to start syncing.');
203
239
  }
204
240
  }
205
241
  }
206
- console.log('\nStarting sync. Press Ctrl+C to stop.\n');
242
+ if (await maybeStartTray()) {
243
+ console.log('\nWalldock Agent is running in the system tray.\n');
244
+ process.exit(0);
245
+ }
207
246
  sync.start({ token: result.token, deviceId: result.deviceId });
208
247
  }
209
248
  main().catch(err => {
package/dist/startup.js CHANGED
@@ -47,7 +47,8 @@ const exec = (0, node_util_1.promisify)(node_child_process_1.execFile);
47
47
  async function resolveGlobalBin() {
48
48
  let prefix;
49
49
  try {
50
- const { stdout } = await exec('npm', ['prefix', '-g']);
50
+ const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
51
+ const { stdout } = await exec(npmBin, ['prefix', '-g']);
51
52
  prefix = stdout.trim();
52
53
  }
53
54
  catch {
@@ -82,7 +83,7 @@ function plistContent(binPath) {
82
83
  <key>ProgramArguments</key>
83
84
  <array>
84
85
  <string>${binPath}</string>
85
- <string>--daemon</string>
86
+ <string>--tray</string>
86
87
  </array>
87
88
  <key>RunAtLoad</key>
88
89
  <true/>
@@ -111,7 +112,7 @@ const REG_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';
111
112
  const REG_VALUE = 'WalldockAgent';
112
113
  async function registerWindows() {
113
114
  const binPath = await resolveGlobalBin();
114
- const cmd = `"${binPath}" --daemon`;
115
+ const cmd = `"${binPath}" --tray`;
115
116
  await exec('reg', ['add', REG_KEY, '/v', REG_VALUE, '/t', 'REG_SZ', '/d', cmd, '/f']);
116
117
  }
117
118
  async function unregisterWindows() {
@@ -129,7 +130,7 @@ After=network-online.target
129
130
  Wants=network-online.target
130
131
 
131
132
  [Service]
132
- ExecStart=${binPath} --daemon
133
+ ExecStart=${binPath} --tray
133
134
  Restart=on-failure
134
135
  RestartSec=10s
135
136
 
@@ -141,7 +142,7 @@ function desktopContent(binPath) {
141
142
  return `[Desktop Entry]
142
143
  Type=Application
143
144
  Name=Walldock Agent
144
- Exec=${binPath} --daemon
145
+ Exec=${binPath} --tray
145
146
  Hidden=false
146
147
  NoDisplay=false
147
148
  X-GNOME-Autostart-enabled=true
package/dist/tray.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export interface TrayHandle {
2
+ updateStatus(msg: string): void;
3
+ stop(): void;
4
+ }
5
+ export declare function startTray(onSyncNow: () => void, onQuit: () => void): TrayHandle;
package/dist/tray.js ADDED
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startTray = startTray;
7
+ const systray2_1 = __importDefault(require("systray2"));
8
+ // 32x32 PNG from apps/agent/src-tauri/icons/32x32.png (base64)
9
+ const ICON = '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';
10
+ const IDX_STATUS = 0;
11
+ const IDX_SYNC = 2;
12
+ const IDX_QUIT = 4;
13
+ function startTray(onSyncNow, onQuit) {
14
+ const systray = new systray2_1.default({
15
+ menu: {
16
+ icon: ICON,
17
+ title: '',
18
+ tooltip: 'Walldock Agent',
19
+ items: [
20
+ { title: 'Walldock Agent', tooltip: '', checked: false, enabled: false },
21
+ systray2_1.default.separator,
22
+ { title: 'Sync Now', tooltip: 'Fetch latest wallpapers', checked: false, enabled: true },
23
+ systray2_1.default.separator,
24
+ { title: 'Quit', tooltip: 'Stop Walldock Agent', checked: false, enabled: true },
25
+ ],
26
+ },
27
+ debug: false,
28
+ copyDir: true,
29
+ });
30
+ systray.onClick(action => {
31
+ if (action.seq_id === IDX_SYNC)
32
+ onSyncNow();
33
+ if (action.seq_id === IDX_QUIT)
34
+ onQuit();
35
+ });
36
+ return {
37
+ updateStatus(msg) {
38
+ try {
39
+ void systray.sendAction({
40
+ type: 'update-item',
41
+ item: { title: `Walldock — ${msg}`, tooltip: '', checked: false, enabled: false },
42
+ seq_id: IDX_STATUS,
43
+ });
44
+ }
45
+ catch { /* tray process may have exited */ }
46
+ },
47
+ stop() {
48
+ try {
49
+ systray.kill(false);
50
+ }
51
+ catch { /* ignore */ }
52
+ },
53
+ };
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walldock/agent",
3
- "version": "0.1.7",
3
+ "version": "0.2.1",
4
4
  "description": "Walldock desktop agent — sync wallpapers across all your screens",
5
5
  "license": "MIT",
6
6
  "main": "dist/main.js",
@@ -14,7 +14,8 @@
14
14
  "node": ">=18.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@napi-rs/keyring": "^1.1.5"
17
+ "@napi-rs/keyring": "^1.1.5",
18
+ "systray2": "^2.1.4"
18
19
  },
19
20
  "devDependencies": {
20
21
  "@types/node": "^20.0.0",