@ours.network/cli 2.0.2 → 2.0.3

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/README.md CHANGED
@@ -61,6 +61,14 @@ ours daemon stop
61
61
  ours daemon restart
62
62
  ```
63
63
 
64
+ `start` and `restart` wait up to 20 minutes for the new daemon to become ready.
65
+ Override that wait with `--readiness-timeout-ms N` or
66
+ `OURS_DAEMON_READINESS_TIMEOUT_MS`. The flag takes precedence. If the deadline
67
+ is reached, the command exits with an error but never signals or stops the
68
+ spawned daemon: it remains running and may still be completing an upgrade. Use
69
+ `ours daemon status --json` to check it and inspect
70
+ `ours-cli-daemon.log` in the selected state directory for progress.
71
+
64
72
  `stop` and `restart` control the selected daemon regardless of which CLI command
65
73
  or service launcher originally started it. The endpoint must identify itself as
66
74
  an ours daemon for the exact selected state directory; once that validation
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  bootWrapper,
3
3
  wrapperBooted
4
- } from "./chunk-IL724CY2.js";
4
+ } from "./chunk-ZCCCTR27.js";
5
5
  import "./chunk-FXKFSNKS.js";
6
6
  export {
7
7
  bootWrapper,
@@ -13,7 +13,7 @@ import {
13
13
  IDENTITY_PREBIND_EXCEPTIONS,
14
14
  commandSpec,
15
15
  isCommandGroup
16
- } from "./chunk-VA6UQKYN.js";
16
+ } from "./chunk-QKDH6CJR.js";
17
17
 
18
18
  // src/help.ts
19
19
  var pad = (value, width = 32) => value.length >= width ? `${value} ` : value.padEnd(width);
@@ -106,6 +106,7 @@ function nativeDaemonOptions(action) {
106
106
  ["--port N", selected ? "Select daemon port 1\u201365535." : "Checked against the port recorded for this state directory; a disagreement is refused and nothing is written."],
107
107
  ["--state-dir PATH", selected ? "Select the daemon state directory." : "Select the daemon whose unit this is; the unit name derives from it (~/.ours \u2192 ours.service, ~/.ours-tg \u2192 ours-tg.service)."],
108
108
  ["--config PATH", selected ? "Use this daemon config for lifecycle selection." : action === "install-service" ? "Embed this config file\u2019s absolute path in the installed unit." : "Compatibility-only/ignored by uninstall-service."],
109
+ ["--readiness-timeout-ms N", action === "start" || action === "restart" ? "Wait this many milliseconds for readiness (default: 1200000; env: OURS_DAEMON_READINESS_TIMEOUT_MS). A timeout leaves the spawned daemon running." : "Compatibility-only/ignored for this action."],
109
110
  ["--identity NAME", "Compatibility-only/ignored by daemon lifecycle and service commands."],
110
111
  ["--json", action === "serve" ? "Compatibility-only/ignored because serve runs until interrupted." : "Write one JSON result to stdout."],
111
112
  ["--yes", service.has(action) ? "Confirm the service-manager change; not needed with --dry-run." : "Compatibility-only/ignored for this action."],
@@ -15,7 +15,7 @@ var COMMAND_GROUPS = {
15
15
  kind: "daemon",
16
16
  handler: "start",
17
17
  summary: "Start a detached daemon managed by this CLI.",
18
- effects: "Spawns a background process, writes a PID record and log in the selected state directory, and waits up to 20 seconds for readiness.",
18
+ effects: "Spawns a background process, writes a PID record and log in the selected state directory, and waits up to 20 minutes for readiness by default. Reaching the readiness deadline never stops the daemon.",
19
19
  example: "ours daemon start --config /etc/ours/config.json"
20
20
  },
21
21
  stop: {
@@ -29,7 +29,7 @@ var COMMAND_GROUPS = {
29
29
  kind: "daemon",
30
30
  handler: "restart",
31
31
  summary: "Stop and start the selected ours daemon.",
32
- effects: "Stops the selected valid ours daemon, then starts a detached replacement.",
32
+ effects: "Stops the selected valid ours daemon, then starts a detached replacement and waits up to 20 minutes for readiness by default. Reaching the readiness deadline never stops the replacement.",
33
33
  example: "ours daemon restart --config /etc/ours/config.json"
34
34
  },
35
35
  status: {
@@ -11,6 +11,7 @@ import { join, resolve } from "node:path";
11
11
  var MANAGED_PID_FILE = "ours-cli-daemon.json";
12
12
  var LEGACY_MANAGED_PID_FILE = "daemon.pid";
13
13
  var DAEMON_LOG_FILE = "ours-cli-daemon.log";
14
+ var DEFAULT_DAEMON_READINESS_TIMEOUT_MS = 20 * 60 * 1e3;
14
15
  var depsDefault = {
15
16
  fetch: globalThis.fetch,
16
17
  spawn: nodeSpawn,
@@ -98,7 +99,7 @@ function childEnvironment(selection) {
98
99
  async function serveDaemon(selection, managed = false) {
99
100
  if (selection.endpoint !== void 0) throw new Error("--endpoint selects a running daemon and cannot be used with daemon serve");
100
101
  Object.assign(process.env, childEnvironment(selection));
101
- const { startDaemon } = await import("./daemon-A2L6UMGZ.js");
102
+ const { startDaemon } = await import("./daemon-3C5SDTHJ.js");
102
103
  const handle = await startDaemon();
103
104
  if (managed) {
104
105
  const response = await fetch(`http://127.0.0.1:${handle.port}/state-dir`);
@@ -111,8 +112,11 @@ async function serveDaemon(selection, managed = false) {
111
112
  }
112
113
  return await new Promise(() => void 0);
113
114
  }
114
- async function startDaemonManaged(selection, deps = depsDefault) {
115
+ async function startDaemonManaged(selection, deps = depsDefault, readinessTimeoutMs = DEFAULT_DAEMON_READINESS_TIMEOUT_MS) {
115
116
  if (selection.endpoint !== void 0) throw new Error("--endpoint selects an external daemon and cannot be used with daemon start");
117
+ if (!Number.isSafeInteger(readinessTimeoutMs) || readinessTimeoutMs < 1) {
118
+ throw new Error("daemon readiness timeout must be a positive integer number of milliseconds");
119
+ }
116
120
  const config = await resolveSelection(selection);
117
121
  const before = await inspectDaemon(config, deps);
118
122
  if (before.state === "running") return before;
@@ -132,9 +136,9 @@ async function startDaemonManaged(selection, deps = depsDefault) {
132
136
  }
133
137
  if (!child.pid || child.pid <= 1) throw new Error(`daemon start failed; see ${logPath}`);
134
138
  child.unref();
135
- const deadline = deps.now() + 2e4;
139
+ const deadline = deps.now() + readinessTimeoutMs;
136
140
  while (deps.now() < deadline) {
137
- await deps.delay(100);
141
+ await deps.delay(Math.min(100, deadline - deps.now()));
138
142
  const status = await inspectDaemon(config, deps);
139
143
  if (status.state === "running" && status.info?.pid === child.pid) return status;
140
144
  try {
@@ -144,10 +148,13 @@ async function startDaemonManaged(selection, deps = depsDefault) {
144
148
  }
145
149
  }
146
150
  try {
147
- deps.kill(child.pid, "SIGTERM");
151
+ deps.kill(child.pid, 0);
148
152
  } catch {
153
+ throw new Error(`daemon exited before becoming ready; see ${logPath}`);
149
154
  }
150
- throw new Error(`daemon did not become ready within 20 seconds and was stopped; see ${logPath}`);
155
+ throw new Error(
156
+ `daemon is still running but did not become ready within ${readinessTimeoutMs} ms; it may still be starting. Check \`ours daemon status --json\` and ${logPath}`
157
+ );
151
158
  }
152
159
  function removeOwnedPidFile(status) {
153
160
  const record = readManagedRecord(status.stateDir);
@@ -188,6 +195,7 @@ export {
188
195
  MANAGED_PID_FILE,
189
196
  LEGACY_MANAGED_PID_FILE,
190
197
  DAEMON_LOG_FILE,
198
+ DEFAULT_DAEMON_READINESS_TIMEOUT_MS,
191
199
  readLegacyManagedPid,
192
200
  readManagedRecord,
193
201
  writeManagedRecord,
@@ -89,7 +89,7 @@ function createStartupProgressReporter(stateDir, opts = {}) {
89
89
  }
90
90
 
91
91
  // ../../src/runtime/env.ts
92
- var VERSION = true ? "3.0.2" : "0.0.0-dev";
92
+ var VERSION = true ? "3.0.3" : "0.0.0-dev";
93
93
  var CONFIG = loadConfig();
94
94
  var STATE_DIR = CONFIG.stateDir;
95
95
  var BROKER_URL = CONFIG.brokerUrl;
@@ -37,7 +37,7 @@ export declare const COMMAND_GROUPS: {
37
37
  readonly kind: "daemon";
38
38
  readonly handler: "start";
39
39
  readonly summary: "Start a detached daemon managed by this CLI.";
40
- readonly effects: "Spawns a background process, writes a PID record and log in the selected state directory, and waits up to 20 seconds for readiness.";
40
+ readonly effects: "Spawns a background process, writes a PID record and log in the selected state directory, and waits up to 20 minutes for readiness by default. Reaching the readiness deadline never stops the daemon.";
41
41
  readonly example: "ours daemon start --config /etc/ours/config.json";
42
42
  };
43
43
  readonly stop: {
@@ -51,7 +51,7 @@ export declare const COMMAND_GROUPS: {
51
51
  readonly kind: "daemon";
52
52
  readonly handler: "restart";
53
53
  readonly summary: "Stop and start the selected ours daemon.";
54
- readonly effects: "Stops the selected valid ours daemon, then starts a detached replacement.";
54
+ readonly effects: "Stops the selected valid ours daemon, then starts a detached replacement and waits up to 20 minutes for readiness by default. Reaching the readiness deadline never stops the replacement.";
55
55
  readonly example: "ours daemon restart --config /etc/ours/config.json";
56
56
  };
57
57
  readonly status: {
package/dist/commands.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  IDENTITY_PREBIND_EXCEPTIONS,
5
5
  commandSpec,
6
6
  isCommandGroup
7
- } from "./chunk-VA6UQKYN.js";
7
+ } from "./chunk-QKDH6CJR.js";
8
8
  export {
9
9
  COMMAND_GROUPS,
10
10
  DESTRUCTIVE_OPERATIONS,
@@ -204,7 +204,7 @@ async function releaseHeld(lock) {
204
204
  async function bootKernel() {
205
205
  const { lock, acquired } = await ensureLock();
206
206
  try {
207
- const { bootWrapper } = await import("./boot-Y3GXCN6X.js");
207
+ const { bootWrapper } = await import("./boot-BUGOBQGZ.js");
208
208
  await bootWrapper();
209
209
  } catch (error) {
210
210
  if (acquired) await releaseHeld(lock);
@@ -214,7 +214,7 @@ async function bootKernel() {
214
214
  async function startDaemon(opts = {}) {
215
215
  const { lock, acquired } = await ensureLock();
216
216
  try {
217
- const runtime = await import("./server-6BZCHN6Q.js");
217
+ const runtime = await import("./server-TVV2EZZ6.js");
218
218
  const { onRuntimeLoaded, ...daemonOptions } = opts;
219
219
  onRuntimeLoaded?.();
220
220
  const handle = await runtime.startDaemon({
package/dist/help.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  helpRequestPath,
4
4
  renderHelp,
5
5
  renderTopHelp
6
- } from "./chunk-VFAC74XR.js";
6
+ } from "./chunk-NNHFXFLS.js";
7
7
  import "./chunk-MS4TDUZY.js";
8
8
  import "./chunk-6NFATG6P.js";
9
- import "./chunk-VA6UQKYN.js";
9
+ import "./chunk-QKDH6CJR.js";
10
10
  export {
11
11
  helpHint,
12
12
  helpRequestPath,
@@ -5,6 +5,7 @@ export declare const MANAGED_PID_FILE = "ours-cli-daemon.json";
5
5
  /** PID file written by the pre-split @ours.network/mcp CLI. Retained only for cleanup. */
6
6
  export declare const LEGACY_MANAGED_PID_FILE = "daemon.pid";
7
7
  export declare const DAEMON_LOG_FILE = "ours-cli-daemon.log";
8
+ export declare const DEFAULT_DAEMON_READINESS_TIMEOUT_MS: number;
8
9
  interface ManagedRecord {
9
10
  version: 1;
10
11
  owner: '@ours.network/cli';
@@ -34,6 +35,6 @@ export declare function readManagedRecord(stateDir: string): ManagedRecord | nul
34
35
  export declare function writeManagedRecord(stateDir: string, port: number): string;
35
36
  export declare function inspectDaemon(config: ResolvedDaemonConfig, deps?: LifecycleDeps): Promise<DaemonStatus>;
36
37
  export declare function serveDaemon(selection: SelectionOptions, managed?: boolean): Promise<never>;
37
- export declare function startDaemonManaged(selection: SelectionOptions, deps?: LifecycleDeps): Promise<DaemonStatus>;
38
+ export declare function startDaemonManaged(selection: SelectionOptions, deps?: LifecycleDeps, readinessTimeoutMs?: number): Promise<DaemonStatus>;
38
39
  export declare function stopDaemonManaged(config: ResolvedDaemonConfig, deps?: LifecycleDeps): Promise<DaemonStatus>;
39
40
  export {};
package/dist/lifecycle.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  DAEMON_LOG_FILE,
3
+ DEFAULT_DAEMON_READINESS_TIMEOUT_MS,
3
4
  LEGACY_MANAGED_PID_FILE,
4
5
  MANAGED_PID_FILE,
5
6
  inspectDaemon,
@@ -9,10 +10,11 @@ import {
9
10
  startDaemonManaged,
10
11
  stopDaemonManaged,
11
12
  writeManagedRecord
12
- } from "./chunk-DTN7MN44.js";
13
+ } from "./chunk-UIWEBE5K.js";
13
14
  import "./chunk-6K2JBKHI.js";
14
15
  export {
15
16
  DAEMON_LOG_FILE,
17
+ DEFAULT_DAEMON_READINESS_TIMEOUT_MS,
16
18
  LEGACY_MANAGED_PID_FILE,
17
19
  MANAGED_PID_FILE,
18
20
  inspectDaemon,
package/dist/main.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ import { type LifecycleDeps } from './lifecycle.js';
1
2
  import { type CliIo } from './output.js';
2
- export declare function runCli(argv: string[], io?: CliIo): Promise<number>;
3
+ export declare function runCli(argv: string[], io?: CliIo, lifecycleDeps?: LifecycleDeps): Promise<number>;
package/dist/main.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  helpHint,
18
18
  helpRequestPath,
19
19
  renderHelp
20
- } from "./chunk-VFAC74XR.js";
20
+ } from "./chunk-NNHFXFLS.js";
21
21
  import {
22
22
  invokeOperation,
23
23
  isOperationName,
@@ -37,13 +37,14 @@ import {
37
37
  IDENTITY_PREBIND_EXCEPTIONS,
38
38
  commandSpec,
39
39
  isCommandGroup
40
- } from "./chunk-VA6UQKYN.js";
40
+ } from "./chunk-QKDH6CJR.js";
41
41
  import {
42
+ DEFAULT_DAEMON_READINESS_TIMEOUT_MS,
42
43
  inspectDaemon,
43
44
  serveDaemon,
44
45
  startDaemonManaged,
45
46
  stopDaemonManaged
46
- } from "./chunk-DTN7MN44.js";
47
+ } from "./chunk-UIWEBE5K.js";
47
48
  import {
48
49
  attachClient,
49
50
  defaultConfigPath,
@@ -54,9 +55,10 @@ import {
54
55
  import { existsSync, readFileSync } from "node:fs";
55
56
  import { homedir } from "node:os";
56
57
  import { join, resolve } from "node:path";
57
- var CLI_VERSION = false ? "0.0.0-dev" : "2.0.2";
58
+ var CLI_VERSION = false ? "0.0.0-dev" : "2.0.3";
58
59
  var COMMON_VALUES = /* @__PURE__ */ new Set(["--endpoint", "--port", "--state-dir", "--config", "--identity"]);
59
60
  var COMMON_BOOLEANS = /* @__PURE__ */ new Set(["--json", "--yes", "--help"]);
61
+ var DAEMON_READINESS_TIMEOUT_ENV = "OURS_DAEMON_READINESS_TIMEOUT_MS";
60
62
  function selectionFrom(values) {
61
63
  return {
62
64
  endpoint: values["--endpoint"],
@@ -186,11 +188,17 @@ async function runConfig(args, io) {
186
188
  writeResult(io, setupConfig(flags.values["--config"] ?? defaultConfigPath(), patch, flags.booleans.has("--dry-run")), json, "config updated");
187
189
  return 0;
188
190
  }
189
- async function runDaemon(args, io) {
191
+ function daemonReadinessTimeoutMs(values) {
192
+ const flag = values["--readiness-timeout-ms"];
193
+ if (flag !== void 0) return parseInteger(flag, "--readiness-timeout-ms", 1);
194
+ const configured = process.env[DAEMON_READINESS_TIMEOUT_ENV];
195
+ return configured === void 0 ? DEFAULT_DAEMON_READINESS_TIMEOUT_MS : parseInteger(configured, DAEMON_READINESS_TIMEOUT_ENV, 1);
196
+ }
197
+ async function runDaemon(args, io, lifecycleDeps) {
190
198
  const action = args[0];
191
199
  const spec = action === void 0 ? void 0 : commandSpec("daemon", action);
192
200
  if (spec?.kind === "operation") return runGrouped("daemon", args, io);
193
- const values = new Set(COMMON_VALUES);
201
+ const values = /* @__PURE__ */ new Set([...COMMON_VALUES, "--readiness-timeout-ms"]);
194
202
  const booleans = /* @__PURE__ */ new Set([...COMMON_BOOLEANS, "--managed", "--dry-run", "--force"]);
195
203
  const flags = parseFlags(args, values, booleans);
196
204
  if (flags.positionals.length !== 1) throw new CliUsageError("daemon accepts exactly one command");
@@ -198,24 +206,25 @@ async function runDaemon(args, io) {
198
206
  const handler = spec.handler;
199
207
  const selection = selectionFrom(flags.values);
200
208
  const json = flags.booleans.has("--json");
209
+ const readinessTimeoutMs = handler === "start" || handler === "restart" ? daemonReadinessTimeoutMs(flags.values) : void 0;
201
210
  if (handler === "serve") return serveDaemon(selection, flags.booleans.has("--managed"));
202
211
  if (handler === "status") {
203
- const status = await inspectDaemon(await resolveSelection(selection));
212
+ const status = await inspectDaemon(await resolveSelection(selection), lifecycleDeps);
204
213
  writeResult(io, status, json, status.state);
205
214
  return status.state === "running" ? 0 : 3;
206
215
  }
207
216
  if (handler === "start") {
208
- writeResult(io, await startDaemonManaged(selection), json, "started");
217
+ writeResult(io, await startDaemonManaged(selection, lifecycleDeps, readinessTimeoutMs), json, "started");
209
218
  return 0;
210
219
  }
211
220
  if (handler === "stop") {
212
- writeResult(io, await stopDaemonManaged(await resolveSelection(selection)), json, "stopped");
221
+ writeResult(io, await stopDaemonManaged(await resolveSelection(selection), lifecycleDeps), json, "stopped");
213
222
  return 0;
214
223
  }
215
224
  if (handler === "restart") {
216
225
  if (selection.endpoint !== void 0) throw new CliUsageError("--endpoint selects a running daemon and cannot be used with daemon restart");
217
- await stopDaemonManaged(await resolveSelection(selection));
218
- writeResult(io, await startDaemonManaged(selection), json, "restarted");
226
+ await stopDaemonManaged(await resolveSelection(selection), lifecycleDeps);
227
+ writeResult(io, await startDaemonManaged(selection, lifecycleDeps, readinessTimeoutMs), json, "restarted");
219
228
  return 0;
220
229
  }
221
230
  if (handler === "install-service" || handler === "uninstall-service") {
@@ -234,7 +243,7 @@ async function runDaemon(args, io) {
234
243
  const unsupported = handler;
235
244
  throw new Error(`unsupported daemon handler: ${String(unsupported)}`);
236
245
  }
237
- async function runCli(argv, io = processIo) {
246
+ async function runCli(argv, io = processIo, lifecycleDeps) {
238
247
  const wantsJson = argv.includes("--json");
239
248
  try {
240
249
  const helpPath = helpRequestPath(argv);
@@ -249,7 +258,7 @@ async function runCli(argv, io = processIo) {
249
258
  }
250
259
  if (command === "api") return await runApi(args, io);
251
260
  if (isCommandGroup(command)) {
252
- if (command === "daemon") return await runDaemon(args, io);
261
+ if (command === "daemon") return await runDaemon(args, io, lifecycleDeps);
253
262
  if (command === "config") return await runConfig(args, io);
254
263
  return await runGrouped(command, args, io);
255
264
  }
@@ -79,7 +79,7 @@ import {
79
79
  withScope,
80
80
  withScopeAsync,
81
81
  writeTempMetaFile
82
- } from "./chunk-IL724CY2.js";
82
+ } from "./chunk-ZCCCTR27.js";
83
83
  import {
84
84
  buildIdentityFile,
85
85
  writeIdentityFile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/cli",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Transport-neutral operator CLI for the ours shared daemon",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",