agent-yes 1.251.0 → 1.252.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{SUPPORTED_CLIS-CwSokgZr.js → SUPPORTED_CLIS-ZDqi9LbP.js} +3 -3
- package/dist/{SUPPORTED_CLIS-BXJSjxkF.js → SUPPORTED_CLIS-kkxFgTKh.js} +2 -2
- package/dist/{agentShare-BbLkcQm4.js → agentShare-BXLfevNX.js} +3 -3
- package/dist/{callback-8RWpxE4I.js → callback-D63YERtT.js} +3 -3
- package/dist/{callback-C4O66DTj.js → callback-D_gfdf2c.js} +3 -3
- package/dist/cli.js +7 -7
- package/dist/index.js +2 -2
- package/dist/{notifyDaemon-B1IAqhdC.js → notifyDaemon-FhMvFDm8.js} +3 -3
- package/dist/{remotes-DNLZlk9U.js → remotes-DzPup1dS.js} +1 -1
- package/dist/{remotes-BxQnNxQv.js → remotes-a0bGDae9.js} +2 -2
- package/dist/{rustBinary-ByzIf2js.js → rustBinary-BBOpYC-y.js} +21 -3
- package/dist/{schedule-CcBm_AJi.js → schedule-B5w4JNpg.js} +4 -4
- package/dist/{serve-BXvJ0BVz.js → serve-H8y95HWl.js} +83 -18
- package/dist/{setup-DN7g-GlT.js → setup-CRJSyz9Z.js} +2 -2
- package/dist/{subcommands-CIIal0U8.js → subcommands-BGrZZAZ2.js} +2 -2
- package/dist/{subcommands-DeEoYwPl.js → subcommands-DeUbXdTK.js} +21 -16
- package/dist/{terminal-lMeA3l5c.js → terminal-BMHnUWFm.js} +2 -2
- package/dist/{todoCli-DhD3G8Et.js → todoCli-CVYCQrmX.js} +1 -1
- package/dist/{tray-DpswPpBI.js → tray-LxRESrQs.js} +1 -1
- package/dist/trayApp-DELeG-wq.js +5 -0
- package/dist/trayApp-DEmn5LW0.js +100 -0
- package/dist/{ts-D-lKlowc.js → ts-1rQZA3oo.js} +2 -2
- package/dist/{versionChecker-UbAydyGS.js → versionChecker-hMS7Hkpn.js} +2 -2
- package/dist/{webrtcRemote-D9ENKmzx.js → webrtcRemote-DgoZGUr9.js} +1 -1
- package/dist/{widget-w6D-Sn_o.js → widget-D55SUI6o.js} +3 -3
- package/dist/{ws-Ba4q33_l.js → ws-GVmilTGt.js} +3 -3
- package/package.json +1 -1
- package/ts/rustBinary.ts +22 -0
- package/ts/serve.ts +86 -3
- package/ts/subcommands.ts +5 -0
- package/ts/trayApp.spec.ts +282 -0
- package/ts/trayApp.ts +112 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
// trayApp.ts talks to the filesystem (hidden marker), the Rust tray launcher,
|
|
4
|
+
// and spawns processes via the Bun global. Mock all three so the tests are
|
|
5
|
+
// hermetic and run identically under both the bun and node test runtimes (the
|
|
6
|
+
// node job has no `Bun` global at all — we stub it in).
|
|
7
|
+
|
|
8
|
+
const mockFs = vi.hoisted(() => ({ existsSync: vi.fn().mockReturnValue(false) }));
|
|
9
|
+
const mockFsPromises = vi.hoisted(() => ({
|
|
10
|
+
unlink: vi.fn().mockResolvedValue(undefined),
|
|
11
|
+
writeFile: vi.fn().mockResolvedValue(undefined),
|
|
12
|
+
}));
|
|
13
|
+
const mockRustBinary = vi.hoisted(() => ({ findTrayLauncher: vi.fn().mockReturnValue(undefined) }));
|
|
14
|
+
const mockHome = vi.hoisted(() => ({ agentYesHome: vi.fn().mockReturnValue("/tmp/ay-home") }));
|
|
15
|
+
|
|
16
|
+
vi.mock("node:fs", () => ({ existsSync: mockFs.existsSync }));
|
|
17
|
+
vi.mock("node:fs/promises", () => ({
|
|
18
|
+
unlink: mockFsPromises.unlink,
|
|
19
|
+
writeFile: mockFsPromises.writeFile,
|
|
20
|
+
}));
|
|
21
|
+
vi.mock("./rustBinary.ts", () => ({ findTrayLauncher: mockRustBinary.findTrayLauncher }));
|
|
22
|
+
vi.mock("./agentYesHome.ts", () => ({ agentYesHome: mockHome.agentYesHome }));
|
|
23
|
+
|
|
24
|
+
const originalPlatform = process.platform;
|
|
25
|
+
function setPlatform(value: string) {
|
|
26
|
+
Object.defineProperty(process, "platform", { value, configurable: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Stub the Bun global's spawn so launchTray/stopTray never spawn real processes.
|
|
30
|
+
const spawnMock = vi.fn();
|
|
31
|
+
let originalBun: unknown;
|
|
32
|
+
function goodChild() {
|
|
33
|
+
return { unref: vi.fn(), exited: Promise.resolve(0) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function loadTrayApp() {
|
|
37
|
+
return await import("./trayApp.ts");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe("trayApp", () => {
|
|
41
|
+
let stdout: ReturnType<typeof vi.spyOn>;
|
|
42
|
+
let stderr: ReturnType<typeof vi.spyOn>;
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
vi.clearAllMocks();
|
|
46
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
47
|
+
mockFsPromises.unlink.mockResolvedValue(undefined);
|
|
48
|
+
mockFsPromises.writeFile.mockResolvedValue(undefined);
|
|
49
|
+
mockRustBinary.findTrayLauncher.mockReturnValue(undefined);
|
|
50
|
+
mockHome.agentYesHome.mockReturnValue("/tmp/ay-home");
|
|
51
|
+
spawnMock.mockReturnValue(goodChild());
|
|
52
|
+
originalBun = (globalThis as { Bun?: unknown }).Bun;
|
|
53
|
+
(globalThis as { Bun?: unknown }).Bun = { spawn: spawnMock };
|
|
54
|
+
stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
55
|
+
stderr = vi.spyOn(process.stderr, "write").mockReturnValue(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
(globalThis as { Bun?: unknown }).Bun = originalBun;
|
|
60
|
+
setPlatform(originalPlatform);
|
|
61
|
+
stdout.mockRestore();
|
|
62
|
+
stderr.mockRestore();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("trayHiddenMarker / isTrayHidden", () => {
|
|
66
|
+
it("marker sits under agentYesHome()", async () => {
|
|
67
|
+
const { trayHiddenMarker } = await loadTrayApp();
|
|
68
|
+
expect(trayHiddenMarker().replace(/\\/g, "/")).toBe("/tmp/ay-home/tray.hidden");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("isTrayHidden reflects marker presence", async () => {
|
|
72
|
+
const { isTrayHidden } = await loadTrayApp();
|
|
73
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
74
|
+
expect(isTrayHidden()).toBe(true);
|
|
75
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
76
|
+
expect(isTrayHidden()).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("hasDesktop", () => {
|
|
81
|
+
it("true on win32 and darwin", async () => {
|
|
82
|
+
const { hasDesktop } = await loadTrayApp();
|
|
83
|
+
setPlatform("win32");
|
|
84
|
+
expect(hasDesktop()).toBe(true);
|
|
85
|
+
setPlatform("darwin");
|
|
86
|
+
expect(hasDesktop()).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("linux depends on DISPLAY/WAYLAND_DISPLAY", async () => {
|
|
90
|
+
const { hasDesktop } = await loadTrayApp();
|
|
91
|
+
setPlatform("linux");
|
|
92
|
+
const prevD = process.env.DISPLAY;
|
|
93
|
+
const prevW = process.env.WAYLAND_DISPLAY;
|
|
94
|
+
delete process.env.DISPLAY;
|
|
95
|
+
delete process.env.WAYLAND_DISPLAY;
|
|
96
|
+
expect(hasDesktop()).toBe(false);
|
|
97
|
+
process.env.DISPLAY = ":0";
|
|
98
|
+
expect(hasDesktop()).toBe(true);
|
|
99
|
+
if (prevD === undefined) delete process.env.DISPLAY;
|
|
100
|
+
else process.env.DISPLAY = prevD;
|
|
101
|
+
if (prevW === undefined) delete process.env.WAYLAND_DISPLAY;
|
|
102
|
+
else process.env.WAYLAND_DISPLAY = prevW;
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("launchTray", () => {
|
|
107
|
+
it("no-op (false) on a headless session", async () => {
|
|
108
|
+
const { launchTray } = await loadTrayApp();
|
|
109
|
+
setPlatform("linux");
|
|
110
|
+
const prev = process.env.DISPLAY;
|
|
111
|
+
delete process.env.DISPLAY;
|
|
112
|
+
expect(launchTray()).toBe(false);
|
|
113
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
114
|
+
if (prev === undefined) delete process.env.DISPLAY;
|
|
115
|
+
else process.env.DISPLAY = prev;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("no-op (false) when the user hid the tray", async () => {
|
|
119
|
+
const { launchTray } = await loadTrayApp();
|
|
120
|
+
setPlatform("darwin");
|
|
121
|
+
mockFs.existsSync.mockReturnValue(true); // hidden marker present
|
|
122
|
+
expect(launchTray()).toBe(false);
|
|
123
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("no-op (false) when the tray binary is missing", async () => {
|
|
127
|
+
const { launchTray } = await loadTrayApp();
|
|
128
|
+
setPlatform("darwin");
|
|
129
|
+
mockRustBinary.findTrayLauncher.mockReturnValue(undefined);
|
|
130
|
+
expect(launchTray()).toBe(false);
|
|
131
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("spawns detached and returns true when everything lines up", async () => {
|
|
135
|
+
const { launchTray } = await loadTrayApp();
|
|
136
|
+
setPlatform("darwin");
|
|
137
|
+
mockRustBinary.findTrayLauncher.mockReturnValue("/opt/agent-yes-tray");
|
|
138
|
+
const child = goodChild();
|
|
139
|
+
spawnMock.mockReturnValue(child);
|
|
140
|
+
expect(launchTray()).toBe(true);
|
|
141
|
+
expect(spawnMock).toHaveBeenCalledWith(
|
|
142
|
+
["/opt/agent-yes-tray"],
|
|
143
|
+
expect.objectContaining({ detached: true }),
|
|
144
|
+
);
|
|
145
|
+
expect(child.unref).toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("returns false when the spawn throws", async () => {
|
|
149
|
+
const { launchTray } = await loadTrayApp();
|
|
150
|
+
setPlatform("darwin");
|
|
151
|
+
mockRustBinary.findTrayLauncher.mockReturnValue("/opt/agent-yes-tray");
|
|
152
|
+
spawnMock.mockImplementation(() => {
|
|
153
|
+
throw new Error("spawn failed");
|
|
154
|
+
});
|
|
155
|
+
expect(launchTray()).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("cmdTray", () => {
|
|
160
|
+
it("status (default subcommand) prints state and returns 0", async () => {
|
|
161
|
+
const { cmdTray } = await loadTrayApp();
|
|
162
|
+
setPlatform("darwin");
|
|
163
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
164
|
+
mockRustBinary.findTrayLauncher.mockReturnValue("/opt/agent-yes-tray");
|
|
165
|
+
const code = await cmdTray([]);
|
|
166
|
+
expect(code).toBe(0);
|
|
167
|
+
const out = stdout.mock.calls.map((c) => String(c[0])).join("");
|
|
168
|
+
expect(out).toContain("hidden:");
|
|
169
|
+
expect(out).toContain("desktop:");
|
|
170
|
+
expect(out).toContain("/opt/agent-yes-tray");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("status shows hidden + headless + not-found variants", async () => {
|
|
174
|
+
const { cmdTray } = await loadTrayApp();
|
|
175
|
+
setPlatform("linux");
|
|
176
|
+
const prev = process.env.DISPLAY;
|
|
177
|
+
delete process.env.DISPLAY;
|
|
178
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
179
|
+
mockRustBinary.findTrayLauncher.mockReturnValue(undefined);
|
|
180
|
+
expect(await cmdTray(["status"])).toBe(0);
|
|
181
|
+
const out = stdout.mock.calls.map((c) => String(c[0])).join("");
|
|
182
|
+
expect(out).toContain("yes (run");
|
|
183
|
+
expect(out).toContain("headless");
|
|
184
|
+
expect(out).toContain("not found");
|
|
185
|
+
if (prev === undefined) delete process.env.DISPLAY;
|
|
186
|
+
else process.env.DISPLAY = prev;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("show on a headless session errors with code 1", async () => {
|
|
190
|
+
const { cmdTray } = await loadTrayApp();
|
|
191
|
+
setPlatform("linux");
|
|
192
|
+
const prev = process.env.DISPLAY;
|
|
193
|
+
delete process.env.DISPLAY;
|
|
194
|
+
const code = await cmdTray(["show"]);
|
|
195
|
+
expect(code).toBe(1);
|
|
196
|
+
expect(mockFsPromises.unlink).toHaveBeenCalled(); // clears the marker first
|
|
197
|
+
expect(String(stderr.mock.calls[0][0])).toContain("no desktop session");
|
|
198
|
+
if (prev === undefined) delete process.env.DISPLAY;
|
|
199
|
+
else process.env.DISPLAY = prev;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("show with no tray binary errors with code 1", async () => {
|
|
203
|
+
const { cmdTray } = await loadTrayApp();
|
|
204
|
+
setPlatform("darwin");
|
|
205
|
+
mockRustBinary.findTrayLauncher.mockReturnValue(undefined);
|
|
206
|
+
const code = await cmdTray(["show"]);
|
|
207
|
+
expect(code).toBe(1);
|
|
208
|
+
expect(String(stderr.mock.calls[0][0])).toContain("tray binary not found");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("show launches the tray and returns 0", async () => {
|
|
212
|
+
const { cmdTray } = await loadTrayApp();
|
|
213
|
+
setPlatform("darwin");
|
|
214
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
215
|
+
mockRustBinary.findTrayLauncher.mockReturnValue("/opt/agent-yes-tray");
|
|
216
|
+
spawnMock.mockReturnValue(goodChild());
|
|
217
|
+
const code = await cmdTray(["show"]);
|
|
218
|
+
expect(code).toBe(0);
|
|
219
|
+
expect(stdout.mock.calls.map((c) => String(c[0])).join("")).toContain("tray shown");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("show reports 'not launched' when the spawn fails but still returns 0", async () => {
|
|
223
|
+
const { cmdTray } = await loadTrayApp();
|
|
224
|
+
setPlatform("darwin");
|
|
225
|
+
mockRustBinary.findTrayLauncher.mockReturnValue("/opt/agent-yes-tray");
|
|
226
|
+
spawnMock.mockImplementation(() => {
|
|
227
|
+
throw new Error("nope");
|
|
228
|
+
});
|
|
229
|
+
const code = await cmdTray(["show"]);
|
|
230
|
+
expect(code).toBe(0);
|
|
231
|
+
expect(stdout.mock.calls.map((c) => String(c[0])).join("")).toContain("not launched");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("hide writes the marker, kills the tray (win32 taskkill) and returns 0", async () => {
|
|
235
|
+
const { cmdTray } = await loadTrayApp();
|
|
236
|
+
setPlatform("win32");
|
|
237
|
+
const code = await cmdTray(["hide"]);
|
|
238
|
+
expect(code).toBe(0);
|
|
239
|
+
expect(mockFsPromises.writeFile).toHaveBeenCalledWith(
|
|
240
|
+
expect.stringContaining("tray.hidden"),
|
|
241
|
+
expect.any(String),
|
|
242
|
+
);
|
|
243
|
+
expect(spawnMock).toHaveBeenCalledWith(
|
|
244
|
+
expect.arrayContaining(["taskkill", "/IM", "agent-yes-tray.exe", "/F"]),
|
|
245
|
+
expect.anything(),
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("hide uses pkill off-Windows and tolerates a spawn failure", async () => {
|
|
250
|
+
const { cmdTray } = await loadTrayApp();
|
|
251
|
+
setPlatform("linux");
|
|
252
|
+
spawnMock.mockImplementation(() => {
|
|
253
|
+
throw new Error("nothing to kill");
|
|
254
|
+
});
|
|
255
|
+
const code = await cmdTray(["hide"]);
|
|
256
|
+
expect(code).toBe(0); // stopTray swallows the failure
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("show tolerates the marker unlink rejecting", async () => {
|
|
260
|
+
const { cmdTray } = await loadTrayApp();
|
|
261
|
+
setPlatform("darwin");
|
|
262
|
+
mockFsPromises.unlink.mockRejectedValueOnce(new Error("ENOENT"));
|
|
263
|
+
mockRustBinary.findTrayLauncher.mockReturnValue("/opt/agent-yes-tray");
|
|
264
|
+
spawnMock.mockReturnValue(goodChild());
|
|
265
|
+
expect(await cmdTray(["show"])).toBe(0);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("hide tolerates the marker writeFile rejecting", async () => {
|
|
269
|
+
const { cmdTray } = await loadTrayApp();
|
|
270
|
+
setPlatform("linux");
|
|
271
|
+
mockFsPromises.writeFile.mockRejectedValueOnce(new Error("EACCES"));
|
|
272
|
+
expect(await cmdTray(["hide"])).toBe(0);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("unknown subcommand errors with code 1", async () => {
|
|
276
|
+
const { cmdTray } = await loadTrayApp();
|
|
277
|
+
const code = await cmdTray(["wat"]);
|
|
278
|
+
expect(code).toBe(1);
|
|
279
|
+
expect(String(stderr.mock.calls[0][0])).toContain("unknown subcommand");
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|
package/ts/trayApp.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// `ay tray` — control the native agent-yes system-tray companion (the Rust
|
|
2
|
+
// `agent-yes-tray` binary). This supersedes the legacy systray2 tray in tray.ts
|
|
3
|
+
// (which busy-loops under Bun and is disabled by default).
|
|
4
|
+
//
|
|
5
|
+
// The tray auto-shows on desktop sessions when `ay serve` is installed (launched
|
|
6
|
+
// by `ay serve install`). Users can hide it — from its own menu or `ay tray hide`
|
|
7
|
+
// — which persists a marker and quits the process until `ay tray show`.
|
|
8
|
+
//
|
|
9
|
+
// Persistence: `~/.agent-yes/tray.hidden` (present = user hid it). The Rust tray
|
|
10
|
+
// reads the SAME marker (via AGENT_YES_HOME / homedir) to gate whether it shows.
|
|
11
|
+
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { unlink, writeFile } from "node:fs/promises";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { agentYesHome } from "./agentYesHome.ts";
|
|
16
|
+
import { findTrayLauncher } from "./rustBinary.ts";
|
|
17
|
+
|
|
18
|
+
/** Marker whose PRESENCE means the user has hidden the tray. */
|
|
19
|
+
export function trayHiddenMarker(): string {
|
|
20
|
+
return path.join(agentYesHome(), "tray.hidden");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isTrayHidden(): boolean {
|
|
24
|
+
return existsSync(trayHiddenMarker());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A desktop session we can show a tray on. Windows/macOS always; Linux needs a
|
|
28
|
+
* display server. Headless servers → no tray. */
|
|
29
|
+
export function hasDesktop(): boolean {
|
|
30
|
+
if (process.platform === "win32" || process.platform === "darwin") return true;
|
|
31
|
+
return !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Launch the native tray (detached, window-less) if it makes sense to: a desktop
|
|
36
|
+
* session, the tray binary exists, and the user hasn't hidden it. No-op otherwise.
|
|
37
|
+
* The tray also self-gates on `ay serve status`, so this is best-effort. Returns
|
|
38
|
+
* true if a process was spawned.
|
|
39
|
+
*/
|
|
40
|
+
export function launchTray(): boolean {
|
|
41
|
+
if (!hasDesktop() || isTrayHidden()) return false;
|
|
42
|
+
const bin = findTrayLauncher();
|
|
43
|
+
if (!bin) return false;
|
|
44
|
+
try {
|
|
45
|
+
// The tray is a GUI-subsystem binary → no console window even when detached,
|
|
46
|
+
// so no ay-spawn-hidden wrapper is needed here.
|
|
47
|
+
const child = Bun.spawn([bin], {
|
|
48
|
+
detached: true,
|
|
49
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
50
|
+
});
|
|
51
|
+
child.unref();
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Stop any running native tray process(es). */
|
|
59
|
+
async function stopTray(): Promise<void> {
|
|
60
|
+
const argv =
|
|
61
|
+
process.platform === "win32"
|
|
62
|
+
? ["taskkill", "/IM", "agent-yes-tray.exe", "/F"]
|
|
63
|
+
: ["pkill", "-f", "agent-yes-tray"];
|
|
64
|
+
try {
|
|
65
|
+
await Bun.spawn(argv, { stdio: ["ignore", "ignore", "ignore"] }).exited;
|
|
66
|
+
} catch {
|
|
67
|
+
/* nothing running */
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function cmdTray(args: string[]): Promise<number> {
|
|
72
|
+
const sub = args[0] ?? "status";
|
|
73
|
+
const w = (s = "") => process.stdout.write(s + "\n");
|
|
74
|
+
|
|
75
|
+
if (sub === "show") {
|
|
76
|
+
await unlink(trayHiddenMarker()).catch(() => {});
|
|
77
|
+
if (!hasDesktop()) {
|
|
78
|
+
process.stderr.write(
|
|
79
|
+
"ay tray show: no desktop session detected (headless) — nothing to show\n",
|
|
80
|
+
);
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
if (!findTrayLauncher()) {
|
|
84
|
+
process.stderr.write(
|
|
85
|
+
"ay tray show: tray binary not found — build it with\n" +
|
|
86
|
+
" cargo build --release --manifest-path rs-tray/Cargo.toml\n",
|
|
87
|
+
);
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
const ok = launchTray();
|
|
91
|
+
w(ok ? "tray shown." : "tray not launched (already running, or serve not installed).");
|
|
92
|
+
w("hint: the tray auto-shows on desktop sessions whenever `ay serve` is installed.");
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (sub === "hide") {
|
|
97
|
+
await writeFile(trayHiddenMarker(), "hidden by `ay tray hide`\n").catch(() => {});
|
|
98
|
+
await stopTray();
|
|
99
|
+
w("tray hidden — it will stay hidden until `ay tray show`.");
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (sub === "status") {
|
|
104
|
+
w(`hidden: ${isTrayHidden() ? "yes (run `ay tray show` to re-enable)" : "no"}`);
|
|
105
|
+
w(`desktop: ${hasDesktop() ? "yes" : "no (headless)"}`);
|
|
106
|
+
w(`binary: ${findTrayLauncher() ?? "(not found — not built for this platform)"}`);
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
process.stderr.write(`ay tray: unknown subcommand '${sub}' (use show | hide | status)\n`);
|
|
111
|
+
return 1;
|
|
112
|
+
}
|