omp-conductor 0.8.0 → 0.9.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/src/cli.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  import { closeSync, openSync, readFileSync, readSync, statSync } from "node:fs";
9
9
  import { userInfo } from "node:os";
10
10
  import { dirname, join } from "node:path";
11
- import { runBoard } from "./board.ts";
11
+ import { boardJson, boardSnapshotOnce, runBoard } from "./board.ts";
12
12
  import {
13
13
  applyRetrofit,
14
14
  formatBriefReport,
@@ -92,7 +92,7 @@ usage:
92
92
  omp-conductor stop
93
93
  omp-conductor restart [--now] [--timeout SECONDS] [--port N] [--project NAME]
94
94
  omp-conductor upgrade [--to VERSION] [--project NAME]
95
- omp-conductor board [--project NAME]
95
+ omp-conductor board [--project NAME] [--json]
96
96
  omp-conductor status [--project NAME]
97
97
  omp-conductor ledger [--issue N] [--limit N] [--project NAME]
98
98
  omp-conductor hold [--project NAME]
@@ -141,7 +141,9 @@ usage:
141
141
  and active runs.
142
142
  board open the live keyboard-driven fleet board. It renders queue holds,
143
143
  every run lifecycle stage, recent merges, spend and health; Enter
144
- follows a selected transcript without leaving the board.
144
+ follows a selected transcript without leaving the board. --json (or
145
+ a non-interactive stdin/stdout) prints a one-shot JSON snapshot of
146
+ the same lanes instead.
145
147
  ledger every conductor-verb call and how the daemon decided it: the verb,
146
148
  the arguments, allow or refuse, the named refusal reason, and the
147
149
  resulting sha. Sessions cannot push, open, merge, label or release
@@ -650,9 +652,19 @@ try {
650
652
  break;
651
653
  }
652
654
 
653
- case "board":
654
- await runBoard(flag(argv, "project"));
655
+ case "board": {
656
+ const project = flag(argv, "project");
657
+ // Headless one-shot mode: `--json` asks for it explicitly, and a
658
+ // non-interactive stdin or stdout (pipe, redirect, cron, script) cannot
659
+ // host the key-driven board anyway. The TTY throw inside runBoard stays
660
+ // as a backstop for direct callers.
661
+ if (argv.includes("--json") || !process.stdout.isTTY || !process.stdin.isTTY) {
662
+ process.stdout.write(`${boardJson(await boardSnapshotOnce(project))}\n`);
663
+ break;
664
+ }
665
+ await runBoard(project);
655
666
  break;
667
+ }
656
668
 
657
669
  case "hold": {
658
670
  const r = hold(flag(argv, "project"));
package/src/config.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
16
16
  import { homedir } from "node:os";
17
17
  import { dirname, isAbsolute, join } from "node:path";
18
+ import { backupTimestamp, copyToUniqueBackup } from "./backups.ts";
18
19
  import {
19
20
  AUTHORITY_HOLDERS,
20
21
  BASE_FRESHNESS,
@@ -103,6 +104,11 @@ export function stateDir(): string {
103
104
  return dirname(configPath());
104
105
  }
105
106
 
107
+ /** State-root directory for durable copies of every pre-write config.json. */
108
+ export function configBackupDir(): string {
109
+ return join(stateDir(), "backups", "config");
110
+ }
111
+
106
112
  /**
107
113
  * Where per-run worktrees and bare mirrors live when a project names neither.
108
114
  *
@@ -188,6 +194,13 @@ export function writeConfigRaw(text: string): void {
188
194
  if (created !== undefined) chmodSync(dir, 0o700);
189
195
 
190
196
  const target = configPath();
197
+ // A durable pre-write copy under state keeps the previous bytes recoverable
198
+ // even when the process dies between this moment and the rename, or when a
199
+ // later caller discovers it needs the old file back. Retention is unbounded,
200
+ // matching the brief backups.
201
+ if (existsSync(target)) {
202
+ copyToUniqueBackup(target, configBackupDir(), `config.json.bak-${backupTimestamp()}`);
203
+ }
191
204
  const tmp = join(dir, `.config.json.${process.pid.toString(36)}.${Date.now().toString(36)}.tmp`);
192
205
  try {
193
206
  writeFileSync(tmp, text, { mode: 0o600 });