@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 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 Promise.resolve({ pid: child.pid ?? null });
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 defaultEnsureDaemonBootPersistence(socketPath) {
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 logDir = (0, identity_1.getAgentDaemonLogsDir)();
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
- if (isDaemonLaunchAgentLoaded({ exec: (cmd) => { (0, child_process_1.execSync)(cmd, { stdio: "ignore" }); }, userUid })) {
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: "bootstrapping daemon launch agent for current login session",
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)(`launchctl bootstrap ${launchAgentDomain(userUid)} "${plistPath}"`, { stdio: "ignore" });
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 bootstrapped for current login session",
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 bootstrap daemon launch agent for current login session",
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.552",
3
+ "version": "0.1.0-alpha.553",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",