@gajae-gateway/cli 0.3.0 → 0.3.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.d.ts CHANGED
@@ -13,7 +13,7 @@ export declare function socketPath(home?: string | undefined): string;
13
13
  * never after, so the list is checked up front.
14
14
  */
15
15
  export declare const COMMANDS: readonly ["status", "shutdown", "chat", "daemon", "sessions", "ops", "memory", "monitors", "work", "services", "migrate", "update", "setup"];
16
- export declare const CLI_USAGE = "usage: gajaeway [--version] [--socket PATH] status|shutdown|chat|daemon run|sessions list [--json] [--fields a,b,c] [--limit N] [--offset N]|sessions inspect <originKey-or-index>|memory audit|memory search <query>|monitors ... (settle <eventId>|retry <eventId>|test <id> [--type T] [--payload J] [--wait[=SECONDS]])|work run|start <name> [--cwd DIR] [--repo PATH|URL [--base REF]] [--resume] [--model ID|--preset NAME] [--notify originKey (start only)] <text>|work status <name>|work steer <name> <text>|work retire [--force] <name>|work retire --all-dead|work jobs [--limit N] [--offset N]|ops backup <path>|ops deliveries [--since ISO] [--state S] [--limit N] [--json]|ops redeliver <deliveryId>|ops redeliver --since <iso>|ops cycle [--json]|ops integrity|ops restart-stack [--status]|services install|repair --bin-dir DIR [--launch-agents-dir DIR] [--unit-dir DIR] [--platform darwin|linux]|migrate [--source PATH] [--target PATH] [--dry-run]|update [--check] [--force] [--bin-dir DIR] [--no-restart]|setup [--from-env] [--adapters discord,slack,telegram] [--owner ID] [--discord-app-id ID]|setup --status|setup --broker-agent [--from DIR] (work run waits for a response; caller timeout does not end the attempt); cron timezone is an IANA zone and defaults to the gateway host's local timezone";
16
+ export declare const CLI_USAGE = "usage: gajaeway [--version] [--socket PATH] status|shutdown|chat|daemon run|sessions list [--json] [--fields a,b,c] [--limit N] [--offset N]|sessions inspect <originKey-or-index>|memory audit|memory search <query>|monitors ... (settle <eventId>|retry <eventId>|test <id> [--type T] [--payload J] [--wait[=SECONDS]])|work run|start <name> [--cwd DIR] [--repo PATH|URL [--base REF]] [--resume] [--model ID|--preset NAME] [--notify originKey (start only)] <text>|work status <name>|work steer <name> <text>|work retire [--force] <name>|work retire --all-dead|work jobs [--limit N] [--offset N]|ops backup <path>|ops deliveries [--since ISO] [--state S] [--limit N] [--json]|ops redeliver <deliveryId>|ops redeliver --since <iso>|ops cycle [--limit N] [--offset N] [--json]|ops integrity|ops restart-stack [--status]|services install|repair --bin-dir DIR [--launch-agents-dir DIR] [--unit-dir DIR] [--platform darwin|linux]|migrate [--source PATH] [--target PATH] [--dry-run]|update [--check] [--force] [--bin-dir DIR] [--no-restart]|setup [--from-env] [--adapters discord,slack,telegram] [--owner ID] [--discord-app-id ID]|setup --status|setup --broker-agent [--from DIR] (work run waits for a response; caller timeout does not end the attempt); cron timezone is an IANA zone and defaults to the gateway host's local timezone";
17
17
  /** Usage errors exit 2, as `gajaeway-gateway` does; 1 stays a runtime failure. */
18
18
  export declare const USAGE_EXIT_CODE = 2;
19
19
  /** The usage text when `command` cannot be dispatched, undefined when it can. */
@@ -44,16 +44,6 @@ export interface ParsedServicesArgs {
44
44
  readonly platform?: ServicePlatform;
45
45
  }
46
46
  export declare function parseServicesArgs(args: readonly string[]): ParsedServicesArgs;
47
- /**
48
- * Operator runtime-cycle view (`gajaeway ops cycle`).
49
- *
50
- * Rendering contract:
51
- * - The aggregate phase line always prints.
52
- * - Any gate prints under `gates:` and forces exit code 1 — a gated cycle is
53
- * never reported as healthy, so scripting cannot mistake it for idle.
54
- * - `--json` emits the typed OpsCycleResult verbatim; the exit-code contract
55
- * is identical.
56
- */
57
47
  export declare function renderCycle(cycle: OpsCycleResult): string[];
58
48
  /**
59
49
  * Exit-code contract for `gajaeway ops cycle`: 0 only when the projection is
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
package/dist/main.js CHANGED
@@ -3,12 +3,12 @@
3
3
 
4
4
  // src/main.ts
5
5
  import { isAbsolute, resolve } from "path";
6
- import { LOOPBACK_ORIGIN, originKey as originKey2, WORK_JOBS_MAX_LIMIT } from "@gajae-gateway/protocol";
6
+ import { LOOPBACK_ORIGIN, OPS_CYCLE_MAX_LIMIT, originKey as originKey2, WORK_JOBS_MAX_LIMIT } from "@gajae-gateway/protocol";
7
7
  import { GajaewayClient } from "@gajae-gateway/sdk";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@gajae-gateway/cli",
11
- version: "0.3.0",
11
+ version: "0.3.1",
12
12
  type: "module",
13
13
  description: "Owner CLI for the gajaeway gateway: status, sessions, chat, monitors, and worker lanes over the gateway socket. Requires the Bun runtime (uses Bun.file/Bun.spawn).",
14
14
  license: "AGPL-3.0-only",
@@ -3336,7 +3336,7 @@ var COMMANDS = [
3336
3336
  "update",
3337
3337
  "setup"
3338
3338
  ];
3339
- var CLI_USAGE = "usage: gajaeway [--version] [--socket PATH] status|shutdown|chat|daemon run|sessions list [--json] [--fields a,b,c] [--limit N] [--offset N]|sessions inspect <originKey-or-index>|memory audit|memory search <query>|monitors ... (settle <eventId>|retry <eventId>|test <id> [--type T] [--payload J] [--wait[=SECONDS]])|work run|start <name> [--cwd DIR] [--repo PATH|URL [--base REF]] [--resume] [--model ID|--preset NAME] [--notify originKey (start only)] <text>|work status <name>|work steer <name> <text>|work retire [--force] <name>|work retire --all-dead|work jobs [--limit N] [--offset N]|ops backup <path>|ops deliveries [--since ISO] [--state S] [--limit N] [--json]|ops redeliver <deliveryId>|ops redeliver --since <iso>|ops cycle [--json]|ops integrity|ops restart-stack [--status]|services install|repair --bin-dir DIR [--launch-agents-dir DIR] [--unit-dir DIR] [--platform darwin|linux]|migrate [--source PATH] [--target PATH] [--dry-run]|update [--check] [--force] [--bin-dir DIR] [--no-restart]|setup [--from-env] [--adapters discord,slack,telegram] [--owner ID] [--discord-app-id ID]|setup --status|setup --broker-agent [--from DIR] (work run waits for a response; caller timeout does not end the attempt); cron timezone is an IANA zone and defaults to the gateway host's local timezone";
3339
+ var CLI_USAGE = "usage: gajaeway [--version] [--socket PATH] status|shutdown|chat|daemon run|sessions list [--json] [--fields a,b,c] [--limit N] [--offset N]|sessions inspect <originKey-or-index>|memory audit|memory search <query>|monitors ... (settle <eventId>|retry <eventId>|test <id> [--type T] [--payload J] [--wait[=SECONDS]])|work run|start <name> [--cwd DIR] [--repo PATH|URL [--base REF]] [--resume] [--model ID|--preset NAME] [--notify originKey (start only)] <text>|work status <name>|work steer <name> <text>|work retire [--force] <name>|work retire --all-dead|work jobs [--limit N] [--offset N]|ops backup <path>|ops deliveries [--since ISO] [--state S] [--limit N] [--json]|ops redeliver <deliveryId>|ops redeliver --since <iso>|ops cycle [--limit N] [--offset N] [--json]|ops integrity|ops restart-stack [--status]|services install|repair --bin-dir DIR [--launch-agents-dir DIR] [--unit-dir DIR] [--platform darwin|linux]|migrate [--source PATH] [--target PATH] [--dry-run]|update [--check] [--force] [--bin-dir DIR] [--no-restart]|setup [--from-env] [--adapters discord,slack,telegram] [--owner ID] [--discord-app-id ID]|setup --status|setup --broker-agent [--from DIR] (work run waits for a response; caller timeout does not end the attempt); cron timezone is an IANA zone and defaults to the gateway host's local timezone";
3340
3340
  var USAGE_EXIT_CODE = 2;
3341
3341
  function usageFor(command) {
3342
3342
  return command !== undefined && COMMANDS.includes(command) ? undefined : CLI_USAGE;
@@ -3534,7 +3534,13 @@ function waitForMonitorStage(client, eventId, timeoutSeconds) {
3534
3534
  timer = setTimeout(finish, timeoutSeconds * 1000);
3535
3535
  });
3536
3536
  }
3537
+ var OPS_CYCLE_PAGING_UNSUPPORTED = "ops.cycle paging is unsupported by the running gateway; restart the gateway after updating it";
3538
+ function hasCyclePagingMetadata(cycle) {
3539
+ return Number.isSafeInteger(cycle.sessionsTotal) && cycle.sessionsTotal >= 0 && Number.isSafeInteger(cycle.sessionsOffset) && cycle.sessionsOffset >= 0;
3540
+ }
3537
3541
  function renderCycle(cycle) {
3542
+ if (!hasCyclePagingMetadata(cycle))
3543
+ throw new Error(OPS_CYCLE_PAGING_UNSUPPORTED);
3538
3544
  const lines = [];
3539
3545
  lines.push(`phase: ${cycle.phase}`);
3540
3546
  if (cycle.gates.length > 0)
@@ -3567,13 +3573,16 @@ function renderCycle(cycle) {
3567
3573
  lines.push(`monitors: ${cycle.monitorEvents.length ? cycle.monitorEvents.map((m) => `${m.stage}=${m.count}`).join(" ") : "none"}`);
3568
3574
  if (cycle.monitorAuthoringLost.length > 0)
3569
3575
  lines.push(`monitor authoring lost: ${cycle.monitorAuthoringLost.map((m) => `${m.eventType}=${m.consecutive} (last ${m.lastFiredAt})`).join(" ")}`);
3570
- if (cycle.sessions.length > 0) {
3571
- lines.push("sessions:");
3576
+ if (cycle.sessionsTotal > 0 && cycle.sessions.length === 0) {
3577
+ lines.push(`sessions: none at offset ${cycle.sessionsOffset} of ${cycle.sessionsTotal}`);
3578
+ } else if (cycle.sessions.length > 0) {
3579
+ const shownEnd = cycle.sessionsOffset + cycle.sessions.length;
3580
+ lines.push(`sessions: ${cycle.sessionsOffset + 1}-${shownEnd} of ${cycle.sessionsTotal}${shownEnd < cycle.sessionsTotal ? ` (next: --offset ${shownEnd})` : ""}`);
3572
3581
  lines.push("INDEX ORIGIN EPOCH SESSION PENDING UNSETTLED OLDEST");
3573
3582
  for (const [index, session] of cycle.sessions.entries()) {
3574
3583
  const sessionId = session.sessionId === "" ? "(rebinding)" : session.sessionId.slice(0, 11);
3575
3584
  const oldest = session.oldestUnsettledAgeMs === null ? "-" : `${Math.round(session.oldestUnsettledAgeMs / 1000)}s`;
3576
- lines.push(`${String(index).padEnd(6)} ${session.originKey.padEnd(43)} ${String(session.epoch).padEnd(6)} ${sessionId.padEnd(12)} ${String(session.pendingInbound).padEnd(8)} ${String(session.unsettledDeliveries).padEnd(10)} ${oldest}`);
3585
+ lines.push(`${String(cycle.sessionsOffset + index).padEnd(6)} ${session.originKey.padEnd(43)} ${String(session.epoch).padEnd(6)} ${sessionId.padEnd(12)} ${String(session.pendingInbound).padEnd(8)} ${String(session.unsettledDeliveries).padEnd(10)} ${oldest}`);
3577
3586
  }
3578
3587
  }
3579
3588
  return lines;
@@ -3593,7 +3602,40 @@ function renderSlo(result) {
3593
3602
  function sloExitCode(result) {
3594
3603
  return result.ok ? 0 : 1;
3595
3604
  }
3596
- var OPS_USAGE = "usage: gajaeway ops backup <path>|deliveries [--since ISO] [--state S] [--limit N] [--json]|redeliver <deliveryId>|redeliver --since <iso>|slo [--days N] [--json]|cycle [--json]|integrity|restart-stack [--status]";
3605
+ var OPS_USAGE = "usage: gajaeway ops backup <path>|deliveries [--since ISO] [--state S] [--limit N] [--json]|redeliver <deliveryId>|redeliver --since <iso>|slo [--days N] [--json]|cycle [--limit N] [--offset N] [--json]|integrity|restart-stack [--status]";
3606
+ var OPS_CYCLE_USAGE = "usage: gajaeway ops cycle [--limit N] [--offset N] [--json]";
3607
+ function parseOpsCycleArgs(args) {
3608
+ const params = {};
3609
+ const seen = new Set;
3610
+ let json = false;
3611
+ for (let index = 0;index < args.length; index++) {
3612
+ const flag = args[index];
3613
+ if (flag === "--json") {
3614
+ if (json)
3615
+ throw new Error(OPS_CYCLE_USAGE);
3616
+ json = true;
3617
+ continue;
3618
+ }
3619
+ if (flag !== "--limit" && flag !== "--offset")
3620
+ throw new Error(OPS_CYCLE_USAGE);
3621
+ if (seen.has(flag))
3622
+ throw new Error(OPS_CYCLE_USAGE);
3623
+ seen.add(flag);
3624
+ const value = args[++index];
3625
+ if (!value || value.startsWith("--") || !/^\d+$/.test(value))
3626
+ throw new Error(OPS_CYCLE_USAGE);
3627
+ const number = Number(value);
3628
+ if (!Number.isSafeInteger(number))
3629
+ throw new Error(OPS_CYCLE_USAGE);
3630
+ if (flag === "--limit") {
3631
+ if (number < 1 || number > OPS_CYCLE_MAX_LIMIT)
3632
+ throw new Error(OPS_CYCLE_USAGE);
3633
+ params.limit = number;
3634
+ } else
3635
+ params.offset = number;
3636
+ }
3637
+ return { params, json };
3638
+ }
3597
3639
  function parseOpsSloArgs(args) {
3598
3640
  let days = 7;
3599
3641
  let json = false;
@@ -3841,6 +3883,7 @@ async function main(args = process.argv.slice(2), options = {}) {
3841
3883
  ${OPS_USAGE}`);
3842
3884
  const deliveries = command === "deliveries" ? parseOpsDeliveriesArgs(parsed.rest.slice(1)) : undefined;
3843
3885
  const slo = command === "slo" ? parseOpsSloArgs(parsed.rest.slice(1)) : undefined;
3886
+ const cycle = command === "cycle" ? parseOpsCycleArgs(parsed.rest.slice(1)) : undefined;
3844
3887
  if (command === "restart-stack") {
3845
3888
  const usage2 = "usage: gajaeway ops restart-stack [--status]";
3846
3889
  const home = gatewayHome();
@@ -3896,16 +3939,16 @@ ${OPS_USAGE}`);
3896
3939
  console.log(JSON.stringify(await client.request("ops.redeliver", redeliverParams)));
3897
3940
  else if (command === "integrity")
3898
3941
  console.log(JSON.stringify(await client.request("ops.integrity")));
3899
- else if (command === "cycle") {
3900
- const cycle = await client.opsCycle();
3901
- if (parsed.rest[1] === "--json")
3902
- console.log(JSON.stringify(cycle));
3903
- else if (parsed.rest[1])
3904
- throw new Error("usage: gajaeway ops cycle [--json]");
3942
+ else if (cycle) {
3943
+ const result = await client.opsCycle(cycle.params);
3944
+ if (!hasCyclePagingMetadata(result))
3945
+ throw new Error(OPS_CYCLE_PAGING_UNSUPPORTED);
3946
+ if (cycle.json)
3947
+ console.log(JSON.stringify(result));
3905
3948
  else
3906
- for (const line of renderCycle(cycle))
3949
+ for (const line of renderCycle(result))
3907
3950
  console.log(line);
3908
- process.exitCode = cycleExitCode(cycle);
3951
+ process.exitCode = cycleExitCode(result);
3909
3952
  } else if (slo) {
3910
3953
  const result = await client.opsSlo(slo.params);
3911
3954
  if (slo.json)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gajae-gateway/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "description": "Owner CLI for the gajaeway gateway: status, sessions, chat, monitors, and worker lanes over the gateway socket. Requires the Bun runtime (uses Bun.file/Bun.spawn).",
6
6
  "license": "AGPL-3.0-only",
@@ -41,8 +41,8 @@
41
41
  "prepublishOnly": "bun run build"
42
42
  },
43
43
  "dependencies": {
44
- "@gajae-gateway/sdk": "0.3.0",
45
- "@gajae-gateway/protocol": "0.3.0"
44
+ "@gajae-gateway/sdk": "0.3.1",
45
+ "@gajae-gateway/protocol": "0.3.1"
46
46
  },
47
47
  "engines": {
48
48
  "bun": ">=1.4.0"
package/src/main.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { isAbsolute, join, resolve } from "node:path";
3
+ import { isAbsolute, resolve } from "node:path";
4
4
 
5
5
  import type {
6
6
  MonitorEventOperatorResult,
@@ -8,6 +8,7 @@ import type {
8
8
  MonitorRecord,
9
9
  MonitorScheduleProjection,
10
10
  MonitorSpec,
11
+ OpsCycleParams,
11
12
  OpsCycleResult,
12
13
  OpsDeliveriesParams,
13
14
  OpsDeliveriesResult,
@@ -22,7 +23,7 @@ import type {
22
23
  WorkStatusResult,
23
24
  WorkSteerResult,
24
25
  } from "@gajae-gateway/protocol";
25
- import { LOOPBACK_ORIGIN, originKey, WORK_JOBS_MAX_LIMIT } from "@gajae-gateway/protocol";
26
+ import { LOOPBACK_ORIGIN, OPS_CYCLE_MAX_LIMIT, originKey, WORK_JOBS_MAX_LIMIT } from "@gajae-gateway/protocol";
26
27
 
27
28
  import { GajaewayClient } from "@gajae-gateway/sdk";
28
29
  import pkg from "../package.json";
@@ -85,7 +86,7 @@ export const COMMANDS = [
85
86
  ] as const;
86
87
 
87
88
  export const CLI_USAGE =
88
- "usage: gajaeway [--version] [--socket PATH] status|shutdown|chat|daemon run|sessions list [--json] [--fields a,b,c] [--limit N] [--offset N]|sessions inspect <originKey-or-index>|memory audit|memory search <query>|monitors ... (settle <eventId>|retry <eventId>|test <id> [--type T] [--payload J] [--wait[=SECONDS]])|work run|start <name> [--cwd DIR] [--repo PATH|URL [--base REF]] [--resume] [--model ID|--preset NAME] [--notify originKey (start only)] <text>|work status <name>|work steer <name> <text>|work retire [--force] <name>|work retire --all-dead|work jobs [--limit N] [--offset N]|ops backup <path>|ops deliveries [--since ISO] [--state S] [--limit N] [--json]|ops redeliver <deliveryId>|ops redeliver --since <iso>|ops cycle [--json]|ops integrity|ops restart-stack [--status]|services install|repair --bin-dir DIR [--launch-agents-dir DIR] [--unit-dir DIR] [--platform darwin|linux]|migrate [--source PATH] [--target PATH] [--dry-run]|update [--check] [--force] [--bin-dir DIR] [--no-restart]|setup [--from-env] [--adapters discord,slack,telegram] [--owner ID] [--discord-app-id ID]|setup --status|setup --broker-agent [--from DIR] (work run waits for a response; caller timeout does not end the attempt); cron timezone is an IANA zone and defaults to the gateway host's local timezone";
89
+ "usage: gajaeway [--version] [--socket PATH] status|shutdown|chat|daemon run|sessions list [--json] [--fields a,b,c] [--limit N] [--offset N]|sessions inspect <originKey-or-index>|memory audit|memory search <query>|monitors ... (settle <eventId>|retry <eventId>|test <id> [--type T] [--payload J] [--wait[=SECONDS]])|work run|start <name> [--cwd DIR] [--repo PATH|URL [--base REF]] [--resume] [--model ID|--preset NAME] [--notify originKey (start only)] <text>|work status <name>|work steer <name> <text>|work retire [--force] <name>|work retire --all-dead|work jobs [--limit N] [--offset N]|ops backup <path>|ops deliveries [--since ISO] [--state S] [--limit N] [--json]|ops redeliver <deliveryId>|ops redeliver --since <iso>|ops cycle [--limit N] [--offset N] [--json]|ops integrity|ops restart-stack [--status]|services install|repair --bin-dir DIR [--launch-agents-dir DIR] [--unit-dir DIR] [--platform darwin|linux]|migrate [--source PATH] [--target PATH] [--dry-run]|update [--check] [--force] [--bin-dir DIR] [--no-restart]|setup [--from-env] [--adapters discord,slack,telegram] [--owner ID] [--discord-app-id ID]|setup --status|setup --broker-agent [--from DIR] (work run waits for a response; caller timeout does not end the attempt); cron timezone is an IANA zone and defaults to the gateway host's local timezone";
89
90
 
90
91
  /** Usage errors exit 2, as `gajaeway-gateway` does; 1 stays a runtime failure. */
91
92
  export const USAGE_EXIT_CODE = 2;
@@ -309,10 +310,24 @@ function waitForMonitorStage(client: GajaewayClient, eventId: string, timeoutSec
309
310
  * - The aggregate phase line always prints.
310
311
  * - Any gate prints under `gates:` and forces exit code 1 — a gated cycle is
311
312
  * never reported as healthy, so scripting cannot mistake it for idle.
313
+ * - Session details are paged; gates and census totals cover the full home.
312
314
  * - `--json` emits the typed OpsCycleResult verbatim; the exit-code contract
313
315
  * is identical.
314
316
  */
317
+ const OPS_CYCLE_PAGING_UNSUPPORTED =
318
+ "ops.cycle paging is unsupported by the running gateway; restart the gateway after updating it";
319
+
320
+ function hasCyclePagingMetadata(cycle: OpsCycleResult): boolean {
321
+ return (
322
+ Number.isSafeInteger(cycle.sessionsTotal) &&
323
+ cycle.sessionsTotal >= 0 &&
324
+ Number.isSafeInteger(cycle.sessionsOffset) &&
325
+ cycle.sessionsOffset >= 0
326
+ );
327
+ }
328
+
315
329
  export function renderCycle(cycle: OpsCycleResult): string[] {
330
+ if (!hasCyclePagingMetadata(cycle)) throw new Error(OPS_CYCLE_PAGING_UNSUPPORTED);
316
331
  const lines: string[] = [];
317
332
  lines.push(`phase: ${cycle.phase}`);
318
333
  if (cycle.gates.length > 0) lines.push(`gates: ${cycle.gates.join(", ")}`);
@@ -363,15 +378,20 @@ export function renderCycle(cycle: OpsCycleResult): string[] {
363
378
  lines.push(
364
379
  `monitor authoring lost: ${cycle.monitorAuthoringLost.map((m) => `${m.eventType}=${m.consecutive} (last ${m.lastFiredAt})`).join(" ")}`,
365
380
  );
366
- if (cycle.sessions.length > 0) {
367
- lines.push("sessions:");
381
+ if (cycle.sessionsTotal > 0 && cycle.sessions.length === 0) {
382
+ lines.push(`sessions: none at offset ${cycle.sessionsOffset} of ${cycle.sessionsTotal}`);
383
+ } else if (cycle.sessions.length > 0) {
384
+ const shownEnd = cycle.sessionsOffset + cycle.sessions.length;
385
+ lines.push(
386
+ `sessions: ${cycle.sessionsOffset + 1}-${shownEnd} of ${cycle.sessionsTotal}${shownEnd < cycle.sessionsTotal ? ` (next: --offset ${shownEnd})` : ""}`,
387
+ );
368
388
  lines.push("INDEX ORIGIN EPOCH SESSION PENDING UNSETTLED OLDEST");
369
389
  for (const [index, session] of cycle.sessions.entries()) {
370
390
  const sessionId = session.sessionId === "" ? "(rebinding)" : session.sessionId.slice(0, 11);
371
391
  const oldest =
372
392
  session.oldestUnsettledAgeMs === null ? "-" : `${Math.round(session.oldestUnsettledAgeMs / 1000)}s`;
373
393
  lines.push(
374
- `${String(index).padEnd(6)} ${session.originKey.padEnd(43)} ${String(session.epoch).padEnd(6)} ${sessionId.padEnd(12)} ${String(session.pendingInbound).padEnd(8)} ${String(session.unsettledDeliveries).padEnd(10)} ${oldest}`,
394
+ `${String(cycle.sessionsOffset + index).padEnd(6)} ${session.originKey.padEnd(43)} ${String(session.epoch).padEnd(6)} ${sessionId.padEnd(12)} ${String(session.pendingInbound).padEnd(8)} ${String(session.unsettledDeliveries).padEnd(10)} ${oldest}`,
375
395
  );
376
396
  }
377
397
  }
@@ -410,7 +430,36 @@ export function sloExitCode(result: OpsSloResult): number {
410
430
  }
411
431
 
412
432
  const OPS_USAGE =
413
- "usage: gajaeway ops backup <path>|deliveries [--since ISO] [--state S] [--limit N] [--json]|redeliver <deliveryId>|redeliver --since <iso>|slo [--days N] [--json]|cycle [--json]|integrity|restart-stack [--status]";
433
+ "usage: gajaeway ops backup <path>|deliveries [--since ISO] [--state S] [--limit N] [--json]|redeliver <deliveryId>|redeliver --since <iso>|slo [--days N] [--json]|cycle [--limit N] [--offset N] [--json]|integrity|restart-stack [--status]";
434
+
435
+ const OPS_CYCLE_USAGE = "usage: gajaeway ops cycle [--limit N] [--offset N] [--json]";
436
+
437
+ function parseOpsCycleArgs(args: readonly string[]): { params: OpsCycleParams; json: boolean } {
438
+ const params: { limit?: number; offset?: number } = {};
439
+ const seen = new Set<string>();
440
+ let json = false;
441
+ for (let index = 0; index < args.length; index++) {
442
+ const flag = args[index];
443
+ if (flag === "--json") {
444
+ if (json) throw new Error(OPS_CYCLE_USAGE);
445
+ json = true;
446
+ continue;
447
+ }
448
+ if (flag !== "--limit" && flag !== "--offset") throw new Error(OPS_CYCLE_USAGE);
449
+ if (seen.has(flag)) throw new Error(OPS_CYCLE_USAGE);
450
+ seen.add(flag);
451
+ const value = args[++index];
452
+ if (!value || value.startsWith("--") || !/^\d+$/.test(value)) throw new Error(OPS_CYCLE_USAGE);
453
+ const number = Number(value);
454
+ if (!Number.isSafeInteger(number)) throw new Error(OPS_CYCLE_USAGE);
455
+ if (flag === "--limit") {
456
+ if (number < 1 || number > OPS_CYCLE_MAX_LIMIT) throw new Error(OPS_CYCLE_USAGE);
457
+ params.limit = number;
458
+ } else params.offset = number;
459
+ }
460
+ return { params, json };
461
+ }
462
+
414
463
  function parseOpsSloArgs(args: readonly string[]): { params: OpsSloParams; json: boolean } {
415
464
  let days = 7;
416
465
  let json = false;
@@ -700,6 +749,7 @@ export async function main(args = process.argv.slice(2), options: MainOptions =
700
749
  throw new Error(`unknown ops command: ${command ?? "(missing)"}\n${OPS_USAGE}`);
701
750
  const deliveries = command === "deliveries" ? parseOpsDeliveriesArgs(parsed.rest.slice(1)) : undefined;
702
751
  const slo = command === "slo" ? parseOpsSloArgs(parsed.rest.slice(1)) : undefined;
752
+ const cycle = command === "cycle" ? parseOpsCycleArgs(parsed.rest.slice(1)) : undefined;
703
753
  if (command === "restart-stack") {
704
754
  // Deliberately socket-free: the reason to run this is a gateway that
705
755
  // has to come back, so it must not need the gateway to answer first.
@@ -752,14 +802,14 @@ export async function main(args = process.argv.slice(2), options: MainOptions =
752
802
  } else if (redeliverParams)
753
803
  console.log(JSON.stringify(await client.request("ops.redeliver", redeliverParams)));
754
804
  else if (command === "integrity") console.log(JSON.stringify(await client.request("ops.integrity")));
755
- else if (command === "cycle") {
756
- const cycle = await client.opsCycle();
757
- if (parsed.rest[1] === "--json") console.log(JSON.stringify(cycle));
758
- else if (parsed.rest[1]) throw new Error("usage: gajaeway ops cycle [--json]");
759
- else for (const line of renderCycle(cycle)) console.log(line);
805
+ else if (cycle) {
806
+ const result = await client.opsCycle(cycle.params);
807
+ if (!hasCyclePagingMetadata(result)) throw new Error(OPS_CYCLE_PAGING_UNSUPPORTED);
808
+ if (cycle.json) console.log(JSON.stringify(result));
809
+ else for (const line of renderCycle(result)) console.log(line);
760
810
  // Fail-closed: a gated/degraded cycle exits non-zero even though the
761
811
  // request itself succeeded, so scripts can never read it as healthy.
762
- process.exitCode = cycleExitCode(cycle);
812
+ process.exitCode = cycleExitCode(result);
763
813
  } else if (slo) {
764
814
  const result = await client.opsSlo(slo.params);
765
815
  if (slo.json) console.log(JSON.stringify(result));