@sensigo/realm-cli 0.19.0 → 0.20.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.
@@ -0,0 +1,47 @@
1
+ import { Command } from 'commander';
2
+ export interface SweepOrphansOptions {
3
+ /** Minimum age (ms) a `.tmp` must have to be reaped. Rejected below `FLOOR_MS` — see above. */
4
+ olderThanMs: number;
5
+ /** true (default caller behavior): report only, never unlink. */
6
+ dryRun: boolean;
7
+ /** Injected clock for deterministic age math in tests; defaults to `new Date()`. */
8
+ now?: Date;
9
+ }
10
+ export interface SweepOrphansResult {
11
+ /** Reaped (force mode) or would-be-reaped (dry-run) `.tmp` paths. */
12
+ reaped: string[];
13
+ /** A candidate vanished on its own (lstat or unlink hit ENOENT) — benign, never a failure. */
14
+ already_gone: string[];
15
+ /** A candidate lstat'd to something other than ENOENT/regular-file/symlink (e.g. a directory
16
+ * unexpectedly named `*.tmp`), or a genuine unlink error (permissions, I/O). Loud on purpose —
17
+ * a type mismatch is never silently swallowed, in either dry-run or force mode. */
18
+ failed: Array<{
19
+ path: string;
20
+ error: string;
21
+ }>;
22
+ }
23
+ /**
24
+ * Reaps orphaned atomic-write `.tmp` files older than `options.olderThanMs`. `FLOOR_MS` is
25
+ * checked FIRST, before any filesystem access, so no caller — CLI action, test, or future
26
+ * consumer — can reach a delete without crossing it.
27
+ *
28
+ * Per-candidate rule (uses `lstat`, never `readdir({ withFileTypes: true })` — on WSL/9p
29
+ * `Dirent.d_type` can be `DT_UNKNOWN`, which would make every type check false and the sweep
30
+ * reap nothing):
31
+ * - `lstat` ENOENT (vanished before we could even examine it, or during the later `unlink`) →
32
+ * `already_gone`, in either mode — a benign race, never a failure.
33
+ * - a **symlink** → skipped silently (appears in no bucket). Never `unlink`ed, never followed.
34
+ * - anything else that is **not a regular file** (a directory unexpectedly named `*.tmp`, a
35
+ * socket, …) → `failed`, loud, in either mode — a type mismatch is never an ENOENT-style
36
+ * silent swallow, because it signals something anomalous in `runsDir` worth surfacing even
37
+ * from a dry-run.
38
+ * - a **future mtime** (negative age — clock skew) → skipped silently, never reaped.
39
+ * - a regular file younger than `olderThanMs` → skipped silently (almost certainly an in-flight
40
+ * write's temp).
41
+ * - a regular file older than `olderThanMs` → a reap candidate: in dry-run, its path lands in
42
+ * `reaped` (interpreted as "would reap") without any filesystem mutation; in force mode, it is
43
+ * `unlink`ed — success → `reaped`; ENOENT → `already_gone`; anything else → `failed`.
44
+ */
45
+ export declare function sweepOrphans(runsDir: string, options: SweepOrphansOptions): Promise<SweepOrphansResult>;
46
+ export declare const gcCommand: Command;
47
+ //# sourceMappingURL=gc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gc.d.ts","sourceRoot":"","sources":["../../src/commands/gc.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC,MAAM,WAAW,mBAAmB;IAClC,+FAA+F;IAC/F,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,MAAM,EAAE,OAAO,CAAC;IAChB,oFAAoF;IACpF,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8FAA8F;IAC9F,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;;wFAEoF;IACpF,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAuBD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAsE7B;AA+DD,eAAO,MAAM,SAAS,SAwDlB,CAAC"}
@@ -0,0 +1,240 @@
1
+ // gc command — sweep orphaned atomic-write temps (issue #160, Phase 1: .tmp only).
2
+ //
3
+ // atomicWriteFile (packages/core/src/store/atomic-write.ts) writes a unique sibling temp
4
+ // (`${path}.<pid>.<counter>.tmp`) then POSIX-renames it over the target. A process dying between
5
+ // the write and the rename orphans that temp forever — it is not runId-keyed for the key-pointer
6
+ // case (`keys/<hash>.json.<pid>.*.tmp`), so `realm run purge` (#107), which acts by runId, can
7
+ // never reach it. Temps are invisible to `list()` (no `.json` suffix) but accumulate on disk
8
+ // regardless. Windows never produces a temp at all (`atomicWriteFile` falls back to plain
9
+ // `writeFile` on win32) — this sweep is a documented no-op there, not a design driver.
10
+ //
11
+ // Reaping a `.tmp` is unconditionally safe: if the sweep unlinks a temp mid-rename, the pending
12
+ // `rename` gets ENOENT, `atomicWriteFile`'s own catch best-effort-unlinks its temp + rethrows — the
13
+ // TARGET file is never touched (worst case: a spurious write error surfaces to the writer, never a
14
+ // torn file). Combined with the 1h floor below, an in-flight write's temp (age ≪ floor) is never
15
+ // even selected.
16
+ //
17
+ // Phase 1 = `.tmp` only. `.lock` reaping is deliberately split to #164 (deferred — proper-lockfile
18
+ // self-heals a live-path lock; only a purged-target's lock lingers, which is negligible). Run-less
19
+ // `trace-buffer-*.jsonl` WAL cleanup is #163. Neither is this command's job — see the report footer.
20
+ import { readdir, lstat, unlink, stat } from 'node:fs/promises';
21
+ import { join } from 'node:path';
22
+ import { Command } from 'commander';
23
+ import { WorkflowError } from '@sensigo/realm';
24
+ import { parseDuration } from '../lib/parse-duration.js';
25
+ /**
26
+ * Minimum `--older-than` the sweep will ever honor (1 hour) — a conservative floor for hygiene of
27
+ * non-urgent crash residue, and, forward-consistency-wise, the safety guard the deferred `.lock`
28
+ * reaping (#164) will also require; enforcing it here means #164 inherits an already-tested guard.
29
+ * Temps themselves are safe to reap at any age past a few seconds (see the module doc above) — this
30
+ * floor is conservatism, not the temp-safety mechanism. Module-private: the only way to reach a
31
+ * delete is through `sweepOrphans`, which checks this FIRST, before any filesystem access.
32
+ */
33
+ const FLOOR_MS = 3_600_000;
34
+ /**
35
+ * Every top-level `*.tmp` in `runsDir`, plus one level of recursion into `runsDir/keys/*.tmp` —
36
+ * `keys/` is the ONLY subdirectory any store ever creates in `runsDir` (verified: `JsonFileStore`'s
37
+ * `keysDir()`/`mkdir` calls are the only subdirectory creation anywhere in this codebase's
38
+ * `runsDir` usage). A plain `readdir(runsDir)` sees the `keys` entry itself (no `.json`/`.tmp`
39
+ * suffix, so it's filtered out) but NOT what's inside it — hence the explicit second `readdir`.
40
+ * Nothing else is recursed into; nothing else is globbed (no `*.lock` — that's #164).
41
+ * Tolerates a missing `runsDir` or a missing `keys/` (a fresh install, or one that never wrote a
42
+ * keyed run, legitimately lacks either) — a missing directory yields zero candidates, not a throw.
43
+ */
44
+ async function findTempCandidates(runsDir) {
45
+ const topLevel = await readdir(runsDir).catch(() => []);
46
+ const paths = topLevel.filter((f) => f.endsWith('.tmp')).map((f) => join(runsDir, f));
47
+ const keysDir = join(runsDir, 'keys');
48
+ const keysEntries = await readdir(keysDir).catch(() => []);
49
+ paths.push(...keysEntries.filter((f) => f.endsWith('.tmp')).map((f) => join(keysDir, f)));
50
+ return paths;
51
+ }
52
+ /**
53
+ * Reaps orphaned atomic-write `.tmp` files older than `options.olderThanMs`. `FLOOR_MS` is
54
+ * checked FIRST, before any filesystem access, so no caller — CLI action, test, or future
55
+ * consumer — can reach a delete without crossing it.
56
+ *
57
+ * Per-candidate rule (uses `lstat`, never `readdir({ withFileTypes: true })` — on WSL/9p
58
+ * `Dirent.d_type` can be `DT_UNKNOWN`, which would make every type check false and the sweep
59
+ * reap nothing):
60
+ * - `lstat` ENOENT (vanished before we could even examine it, or during the later `unlink`) →
61
+ * `already_gone`, in either mode — a benign race, never a failure.
62
+ * - a **symlink** → skipped silently (appears in no bucket). Never `unlink`ed, never followed.
63
+ * - anything else that is **not a regular file** (a directory unexpectedly named `*.tmp`, a
64
+ * socket, …) → `failed`, loud, in either mode — a type mismatch is never an ENOENT-style
65
+ * silent swallow, because it signals something anomalous in `runsDir` worth surfacing even
66
+ * from a dry-run.
67
+ * - a **future mtime** (negative age — clock skew) → skipped silently, never reaped.
68
+ * - a regular file younger than `olderThanMs` → skipped silently (almost certainly an in-flight
69
+ * write's temp).
70
+ * - a regular file older than `olderThanMs` → a reap candidate: in dry-run, its path lands in
71
+ * `reaped` (interpreted as "would reap") without any filesystem mutation; in force mode, it is
72
+ * `unlink`ed — success → `reaped`; ENOENT → `already_gone`; anything else → `failed`.
73
+ */
74
+ export async function sweepOrphans(runsDir, options) {
75
+ if (options.olderThanMs < FLOOR_MS) {
76
+ throw new WorkflowError(`--older-than must resolve to at least 1h (got ${options.olderThanMs}ms) — gc refuses to ` +
77
+ `reap crash residue younger than that, even with --force.`, {
78
+ code: 'VALIDATION_INPUT_SCHEMA',
79
+ category: 'VALIDATION',
80
+ agentAction: 'provide_input',
81
+ retryable: false,
82
+ details: { olderThanMs: options.olderThanMs, floorMs: FLOOR_MS },
83
+ });
84
+ }
85
+ const now = options.now ?? new Date();
86
+ const candidatePaths = await findTempCandidates(runsDir);
87
+ const result = { reaped: [], already_gone: [], failed: [] };
88
+ const toReap = [];
89
+ for (const path of candidatePaths) {
90
+ let info;
91
+ try {
92
+ info = await lstat(path);
93
+ }
94
+ catch (err) {
95
+ if (err.code === 'ENOENT') {
96
+ result.already_gone.push(path); // vanished between readdir and lstat
97
+ }
98
+ else {
99
+ result.failed.push({ path, error: err instanceof Error ? err.message : String(err) });
100
+ }
101
+ continue;
102
+ }
103
+ if (info.isSymbolicLink())
104
+ continue; // never unlink or follow a symlink — silent skip
105
+ if (!info.isFile()) {
106
+ result.failed.push({
107
+ path,
108
+ error: 'expected a regular file but found a different type (unexpected for a *.tmp name)',
109
+ });
110
+ continue;
111
+ }
112
+ const ageMs = now.getTime() - info.mtime.getTime();
113
+ if (ageMs < 0)
114
+ continue; // future mtime (clock skew) — skip, never reap
115
+ if (ageMs <= options.olderThanMs)
116
+ continue; // too fresh — most likely an in-flight write's temp
117
+ toReap.push(path);
118
+ }
119
+ if (options.dryRun) {
120
+ result.reaped = toReap;
121
+ return result;
122
+ }
123
+ for (const path of toReap) {
124
+ try {
125
+ await unlink(path);
126
+ result.reaped.push(path);
127
+ }
128
+ catch (err) {
129
+ if (err.code === 'ENOENT') {
130
+ result.already_gone.push(path); // vanished between lstat and unlink
131
+ }
132
+ else {
133
+ result.failed.push({ path, error: err instanceof Error ? err.message : String(err) });
134
+ }
135
+ }
136
+ }
137
+ return result;
138
+ }
139
+ /** Best-effort total bytes for a known list of paths — reporting-only, never gates reaping.
140
+ * Called against a fresh dry-run preview, so the paths are still on disk when stat'd. */
141
+ async function statPathBytes(paths) {
142
+ let total = 0;
143
+ await Promise.all(paths.map(async (p) => {
144
+ try {
145
+ const info = await stat(p);
146
+ total += info.size;
147
+ }
148
+ catch {
149
+ // vanished between the preview pass and this stat — best-effort, ignore.
150
+ }
151
+ }));
152
+ return total;
153
+ }
154
+ function formatBytes(n) {
155
+ if (n < 1024)
156
+ return `${n} B`;
157
+ if (n < 1024 * 1024)
158
+ return `${(n / 1024).toFixed(1)} KB`;
159
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
160
+ }
161
+ /** So an operator doesn't distrust the tool when `runsDir` still holds residue gc deliberately
162
+ * does not touch — printed on every report, dry-run or force, empty or not. */
163
+ const NOT_REAPED_FOOTER = 'gc does NOT reap orphaned .lock dirs (deferred — issue #164) or run-less trace-buffer-*.jsonl ' +
164
+ 'WAL files (issue #163). Their presence in runsDir is expected and not a sign gc is broken.';
165
+ /** Prints the dry-run / force report shared by both code paths. `previewBytes` always comes from
166
+ * the initial (always non-destructive) preview pass — see the action below for why. */
167
+ function printGcReport(result, previewBytes, dryRun) {
168
+ const nothingToReport = result.reaped.length === 0 && result.already_gone.length === 0 && result.failed.length === 0;
169
+ if (nothingToReport) {
170
+ console.log('No orphaned .tmp files found to reap.');
171
+ }
172
+ else if (dryRun) {
173
+ console.log(`${result.reaped.length} orphaned .tmp file(s) WOULD be reaped (${formatBytes(previewBytes)} to free):`);
174
+ for (const p of result.reaped)
175
+ console.log(` • ${p}`);
176
+ if (result.already_gone.length > 0) {
177
+ console.log(`(${result.already_gone.length} candidate(s) already vanished on their own.)`);
178
+ }
179
+ }
180
+ else {
181
+ console.log(`Reaped ${result.reaped.length} orphaned .tmp file(s) (${formatBytes(previewBytes)} freed). ` +
182
+ `${result.already_gone.length} already gone, ${result.failed.length} failed.`);
183
+ }
184
+ for (const f of result.failed) {
185
+ console.error(` ✗ ${f.path}: ${f.error}`);
186
+ }
187
+ if (dryRun && !nothingToReport) {
188
+ console.log('\nRe-run with --force to actually delete.');
189
+ }
190
+ console.log(`\n${NOT_REAPED_FOOTER}`);
191
+ }
192
+ export const gcCommand = new Command('gc')
193
+ .description('Sweep orphaned atomic-write .tmp files — crash residue from a process that died mid-write (dry-run by default)')
194
+ .requiredOption('--older-than <duration>', 'Reap temps idle at least this long (minimum 1h; e.g. 1h, 6h, 30d)')
195
+ .option('--force', 'Actually delete (without this, gc only reports what WOULD be reaped)')
196
+ .action(async (opts) => {
197
+ let olderThanMs;
198
+ try {
199
+ olderThanMs = parseDuration(opts.olderThan);
200
+ }
201
+ catch (err) {
202
+ console.error(err instanceof Error ? err.message : String(err));
203
+ process.exit(1);
204
+ return;
205
+ }
206
+ // Defense-in-depth over sweepOrphans's own floor check — reject before touching the
207
+ // filesystem at all, with a CLI-friendly message naming the flag the operator just typed.
208
+ if (olderThanMs < FLOOR_MS) {
209
+ console.error(`--older-than must be at least 1h (got '${opts.olderThan}'). gc refuses to reap crash ` +
210
+ `residue younger than that, even with --force.`);
211
+ process.exit(1);
212
+ return;
213
+ }
214
+ const { JsonFileStore } = await import('@sensigo/realm');
215
+ const runsDir = new JsonFileStore().runsDirPath;
216
+ const now = new Date();
217
+ try {
218
+ // Always preview first — this NEVER mutates, in either mode — because it is the only
219
+ // reliable moment to `stat()` the candidates for the report's byte total: a force-mode
220
+ // reap deletes the files before sweepOrphans returns, so statting them afterward is
221
+ // impossible. The preview and the (optional) real pass share the same olderThanMs/now, so
222
+ // the candidate set is consistent bar a narrow, benign concurrent-activity window — which
223
+ // already_gone exists to absorb.
224
+ const preview = await sweepOrphans(runsDir, { olderThanMs, dryRun: true, now });
225
+ const bytes = await statPathBytes(preview.reaped);
226
+ if (opts.force !== true) {
227
+ printGcReport(preview, bytes, true);
228
+ return;
229
+ }
230
+ const result = await sweepOrphans(runsDir, { olderThanMs, dryRun: false, now });
231
+ printGcReport(result, bytes, false);
232
+ if (result.failed.length > 0)
233
+ process.exit(1);
234
+ }
235
+ catch (err) {
236
+ console.error(err instanceof Error ? err.message : String(err));
237
+ process.exit(1);
238
+ }
239
+ });
240
+ //# sourceMappingURL=gc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gc.js","sourceRoot":"","sources":["../../src/commands/gc.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"commands-registry.d.ts","sourceRoot":"","sources":["../src/commands-registry.ts"],"names":[],"mappings":"AA4BA,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,+BAQ5B,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,WAAW,+BAavB,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,+BAM5B,CAAC"}
1
+ {"version":3,"file":"commands-registry.d.ts","sourceRoot":"","sources":["../src/commands-registry.ts"],"names":[],"mappings":"AA6BA,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,+BAQ5B,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,WAAW,+BAcvB,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,+BAM5B,CAAC"}
@@ -9,6 +9,7 @@ import { abandonCommand } from './commands/abandon.js';
9
9
  import { reclaimCommand } from './commands/reclaim.js';
10
10
  import { cleanupCommand } from './commands/cleanup.js';
11
11
  import { purgeCommand } from './commands/purge.js';
12
+ import { gcCommand } from './commands/gc.js';
12
13
  import { reconcileCommand } from './commands/reconcile.js';
13
14
  import { attemptsCommand } from './commands/attempts.js';
14
15
  import { respondCommand } from './commands/respond.js';
@@ -47,6 +48,7 @@ export const runCommands = [
47
48
  respondCommand,
48
49
  cleanupCommand,
49
50
  purgeCommand,
51
+ gcCommand,
50
52
  reconcileCommand,
51
53
  attemptsCommand,
52
54
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"commands-registry.js","sourceRoot":"","sources":["../src/commands-registry.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,+EAA+E;AAC/E,6CAA6C;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,WAAW;IACX,eAAe;IACf,eAAe;IACf,YAAY;IACZ,UAAU;IACV,WAAW;IACX,cAAc;CACf,CAAC;AAEF,gEAAgE;AAChE,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,WAAW;IACX,cAAc;IACd,aAAa;IACb,WAAW;IACX,aAAa;IACb,cAAc;IACd,cAAc;IACd,cAAc;IACd,cAAc;IACd,YAAY;IACZ,gBAAgB;IAChB,eAAe;CAChB,CAAC;AAEF,sDAAsD;AACtD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,aAAa;CACd,CAAC"}
1
+ {"version":3,"file":"commands-registry.js","sourceRoot":"","sources":["../src/commands-registry.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,+EAA+E;AAC/E,6CAA6C;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,WAAW;IACX,eAAe;IACf,eAAe;IACf,YAAY;IACZ,UAAU;IACV,WAAW;IACX,cAAc;CACf,CAAC;AAEF,gEAAgE;AAChE,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,WAAW;IACX,cAAc;IACd,aAAa;IACb,WAAW;IACX,aAAa;IACb,cAAc;IACd,cAAc;IACd,cAAc;IACd,cAAc;IACd,YAAY;IACZ,SAAS;IACT,gBAAgB;IAChB,eAAe;CAChB,CAAC;AAEF,sDAAsD;AACtD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,aAAa;CACd,CAAC"}
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import 'dotenv/config';
4
4
  import { Command } from 'commander';
5
5
  import { workflowCommands, runCommands, topLevelCommands } from './commands-registry.js';
6
6
  const program = new Command();
7
- program.name('realm').description('Realm workflow engine CLI').version('0.19.0');
7
+ program.name('realm').description('Realm workflow engine CLI').version('0.20.0');
8
8
  // realm workflow — operations on workflow definitions
9
9
  const workflowCmd = new Command('workflow').description('Manage workflow definitions');
10
10
  for (const cmd of workflowCommands)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sensigo/realm-cli",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -61,9 +61,9 @@
61
61
  "vitest": "^4.1.0"
62
62
  },
63
63
  "dependencies": {
64
- "@sensigo/realm": "^0.19.0",
65
- "@sensigo/realm-mcp": "^0.19.0",
66
- "@sensigo/realm-testing": "^0.19.0",
64
+ "@sensigo/realm": "^0.20.0",
65
+ "@sensigo/realm-mcp": "^0.20.0",
66
+ "@sensigo/realm-testing": "^0.20.0",
67
67
  "@modelcontextprotocol/sdk": "1.29.0",
68
68
  "chalk": "^5.0.0",
69
69
  "commander": "^14.0.3",