@ouro.bot/cli 0.1.0-alpha.552 → 0.1.0-alpha.553
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/changelog.json +6 -0
- package/dist/heart/daemon/cli-defaults.js +72 -42
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.553",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Daemon startup on macOS now starts through launchd before opening the daemon socket when the LaunchAgent is available, preventing `ouro up` from leaving both a manual daemon and a launchd daemon alive after current-session bootstrap."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.552",
|
|
6
12
|
"changes": [
|
|
@@ -77,7 +77,10 @@ const cli_parse_1 = require("./cli-parse");
|
|
|
77
77
|
const provider_discovery_1 = require("./provider-discovery");
|
|
78
78
|
const provider_credentials_1 = require("../provider-credentials");
|
|
79
79
|
// ── Default implementations ──
|
|
80
|
-
function defaultStartDaemonProcess(socketPath) {
|
|
80
|
+
async function defaultStartDaemonProcess(socketPath) {
|
|
81
|
+
const launchdStarted = await startDaemonProcessViaLaunchd(socketPath);
|
|
82
|
+
if (launchdStarted)
|
|
83
|
+
return launchdStarted;
|
|
81
84
|
const entry = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
|
|
82
85
|
// Redirect stdio to /dev/null via file descriptors — using 'ignore' causes EPIPE
|
|
83
86
|
// when the daemon's logging system writes to stderr after the parent exits.
|
|
@@ -89,7 +92,7 @@ function defaultStartDaemonProcess(socketPath) {
|
|
|
89
92
|
});
|
|
90
93
|
child.unref();
|
|
91
94
|
// Don't close fds — the child process needs them. They'll be cleaned up when the parent exits.
|
|
92
|
-
return
|
|
95
|
+
return { pid: child.pid ?? null };
|
|
93
96
|
}
|
|
94
97
|
function defaultWriteStdout(text) {
|
|
95
98
|
process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
|
|
@@ -224,6 +227,32 @@ function currentUserUid() {
|
|
|
224
227
|
function launchAgentDomain(userUid = currentUserUid()) {
|
|
225
228
|
return `gui/${userUid}`;
|
|
226
229
|
}
|
|
230
|
+
function writeDaemonBootPlist(socketPath) {
|
|
231
|
+
const homeDir = os.homedir();
|
|
232
|
+
const writeDeps = {
|
|
233
|
+
writeFile: (filePath, content) => fs.writeFileSync(filePath, content, "utf-8"),
|
|
234
|
+
mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
|
|
235
|
+
homeDir,
|
|
236
|
+
};
|
|
237
|
+
const entryPath = resolveDaemonBootEntryPath(homeDir);
|
|
238
|
+
/* v8 ignore next -- covered via mock in daemon-cli-defaults.test.ts; v8 on CI attributes the real fs.existsSync branch to the non-mock load @preserve */
|
|
239
|
+
if (!fs.existsSync(entryPath)) {
|
|
240
|
+
(0, runtime_1.emitNervesEvent)({
|
|
241
|
+
level: "warn",
|
|
242
|
+
component: "daemon",
|
|
243
|
+
event: "daemon.entry_path_missing",
|
|
244
|
+
message: "entryPath does not exist on disk — plist may point to a stale location. Run 'ouro daemon install' from the correct location.",
|
|
245
|
+
meta: { entryPath },
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return (0, launchd_1.writeLaunchAgentPlist)(writeDeps, {
|
|
249
|
+
nodePath: process.execPath,
|
|
250
|
+
entryPath,
|
|
251
|
+
socketPath,
|
|
252
|
+
logDir: (0, identity_1.getAgentDaemonLogsDir)(),
|
|
253
|
+
envPath: process.env.PATH,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
227
256
|
function isDaemonLaunchAgentLoaded(deps) {
|
|
228
257
|
const userUid = deps?.userUid ?? currentUserUid();
|
|
229
258
|
const exec = deps?.exec ?? ((cmd) => { (0, child_process_1.execSync)(cmd, { stdio: "ignore" }); });
|
|
@@ -235,6 +264,16 @@ function isDaemonLaunchAgentLoaded(deps) {
|
|
|
235
264
|
return false;
|
|
236
265
|
}
|
|
237
266
|
}
|
|
267
|
+
function readDaemonLaunchAgentPid() {
|
|
268
|
+
try {
|
|
269
|
+
const output = (0, child_process_1.execSync)(`launchctl print ${launchAgentDomain()}/${launchd_1.DAEMON_PLIST_LABEL}`, { encoding: "utf-8" });
|
|
270
|
+
const match = output.match(/^\s*pid = (\d+)/m);
|
|
271
|
+
return match ? Number(match[1]) : null;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
238
277
|
async function waitForBootstrappedDaemonSocket(socketPath, deps = {}) {
|
|
239
278
|
const checkSocketAlive = deps.checkSocketAlive ?? socket_client_1.checkDaemonSocketAlive;
|
|
240
279
|
const now = deps.now ?? Date.now;
|
|
@@ -267,69 +306,60 @@ async function waitForBootstrappedDaemonSocket(socketPath, deps = {}) {
|
|
|
267
306
|
meta: { socketPath },
|
|
268
307
|
});
|
|
269
308
|
}
|
|
270
|
-
async function
|
|
309
|
+
async function startDaemonProcessViaLaunchd(socketPath) {
|
|
271
310
|
if (process.platform !== "darwin") {
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
const homeDir = os.homedir();
|
|
275
|
-
const writeDeps = {
|
|
276
|
-
writeFile: (filePath, content) => fs.writeFileSync(filePath, content, "utf-8"),
|
|
277
|
-
mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
|
|
278
|
-
homeDir,
|
|
279
|
-
};
|
|
280
|
-
const entryPath = resolveDaemonBootEntryPath(homeDir);
|
|
281
|
-
/* v8 ignore next -- covered via mock in daemon-cli-defaults.test.ts; v8 on CI attributes the real fs.existsSync branch to the non-mock load @preserve */
|
|
282
|
-
if (!fs.existsSync(entryPath)) {
|
|
283
|
-
(0, runtime_1.emitNervesEvent)({
|
|
284
|
-
level: "warn",
|
|
285
|
-
component: "daemon",
|
|
286
|
-
event: "daemon.entry_path_missing",
|
|
287
|
-
message: "entryPath does not exist on disk — plist may point to a stale location. Run 'ouro daemon install' from the correct location.",
|
|
288
|
-
meta: { entryPath },
|
|
289
|
-
});
|
|
311
|
+
return null;
|
|
290
312
|
}
|
|
291
|
-
const
|
|
292
|
-
const plistPath = (0, launchd_1.writeLaunchAgentPlist)(writeDeps, {
|
|
293
|
-
nodePath: process.execPath,
|
|
294
|
-
entryPath,
|
|
295
|
-
socketPath,
|
|
296
|
-
logDir,
|
|
297
|
-
envPath: process.env.PATH,
|
|
298
|
-
});
|
|
313
|
+
const plistPath = writeDaemonBootPlist(socketPath);
|
|
299
314
|
const userUid = currentUserUid();
|
|
300
|
-
|
|
301
|
-
(0, runtime_1.emitNervesEvent)({
|
|
302
|
-
component: "daemon",
|
|
303
|
-
event: "daemon.launchd_bootstrap_skipped_loaded",
|
|
304
|
-
message: "daemon launch agent already loaded",
|
|
305
|
-
meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL },
|
|
306
|
-
});
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
315
|
+
const domain = launchAgentDomain(userUid);
|
|
309
316
|
try {
|
|
310
317
|
(0, runtime_1.emitNervesEvent)({
|
|
311
318
|
component: "daemon",
|
|
312
319
|
event: "daemon.launchd_bootstrap_start",
|
|
313
|
-
message: "
|
|
320
|
+
message: "starting daemon launch agent for current login session",
|
|
314
321
|
meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL },
|
|
315
322
|
});
|
|
316
|
-
(0, child_process_1.execSync)(
|
|
323
|
+
if (isDaemonLaunchAgentLoaded({ exec: (cmd) => { (0, child_process_1.execSync)(cmd, { stdio: "ignore" }); }, userUid })) {
|
|
324
|
+
(0, child_process_1.execSync)(`launchctl kickstart -k ${domain}/${launchd_1.DAEMON_PLIST_LABEL}`, { stdio: "ignore" });
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
(0, child_process_1.execSync)(`launchctl bootstrap ${domain} "${plistPath}"`, { stdio: "ignore" });
|
|
328
|
+
}
|
|
317
329
|
(0, runtime_1.emitNervesEvent)({
|
|
318
330
|
component: "daemon",
|
|
319
331
|
event: "daemon.launchd_bootstrap_end",
|
|
320
|
-
message: "daemon launch agent
|
|
332
|
+
message: "daemon launch agent started for current login session",
|
|
321
333
|
meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL },
|
|
322
334
|
});
|
|
323
335
|
await waitForBootstrappedDaemonSocket(socketPath);
|
|
336
|
+
return { pid: readDaemonLaunchAgentPid() };
|
|
324
337
|
}
|
|
325
338
|
catch (error) {
|
|
326
339
|
(0, runtime_1.emitNervesEvent)({
|
|
327
340
|
level: "warn",
|
|
328
341
|
component: "daemon",
|
|
329
342
|
event: "daemon.launchd_bootstrap_error",
|
|
330
|
-
message: "failed to
|
|
343
|
+
message: "failed to start daemon launch agent for current login session",
|
|
331
344
|
meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL, error: error instanceof Error ? error.message : String(error) },
|
|
332
345
|
});
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async function defaultEnsureDaemonBootPersistence(socketPath) {
|
|
350
|
+
if (process.platform !== "darwin") {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const plistPath = writeDaemonBootPlist(socketPath);
|
|
354
|
+
const userUid = currentUserUid();
|
|
355
|
+
if (isDaemonLaunchAgentLoaded({ exec: (cmd) => { (0, child_process_1.execSync)(cmd, { stdio: "ignore" }); }, userUid })) {
|
|
356
|
+
(0, runtime_1.emitNervesEvent)({
|
|
357
|
+
component: "daemon",
|
|
358
|
+
event: "daemon.launchd_bootstrap_skipped_loaded",
|
|
359
|
+
message: "daemon launch agent already loaded",
|
|
360
|
+
meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL },
|
|
361
|
+
});
|
|
362
|
+
return;
|
|
333
363
|
}
|
|
334
364
|
}
|
|
335
365
|
function defaultPrepareDaemonRuntimeReplacement() {
|