@bli-cockpit/cli 0.2.93 → 0.2.95
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/commands/public-root.js +1 -1
- package/dist/commands/sync-followups-autostart.js +99 -0
- package/dist/commands/sync-followups-memory.js +123 -0
- package/dist/commands/sync-followups-self-update.js +127 -0
- package/dist/commands/sync-followups-staging.js +202 -0
- package/dist/commands/sync-followups.js +30 -511
- package/dist/commands/sync-heartbeat.js +36 -0
- package/dist/commands/sync-receipt.js +138 -0
- package/dist/commands/sync-report.js +153 -0
- package/dist/commands/sync-roots.js +28 -0
- package/dist/commands/sync-run.js +52 -0
- package/dist/commands/sync-types.js +1 -0
- package/dist/commands/sync.js +94 -357
- package/dist/disk-usage-classify.js +109 -0
- package/dist/disk-usage-facts.js +4 -0
- package/dist/disk-usage-files.js +99 -0
- package/dist/disk-usage-footprint.js +45 -0
- package/dist/disk-usage-ledger.js +49 -0
- package/dist/disk-usage-scan.js +80 -0
- package/dist/disk-usage-totals.js +83 -0
- package/dist/disk-usage.js +33 -396
- package/package.json +4 -4
package/dist/disk-usage.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* The collector staged raw evidence, uploaded it, committed it, and then kept
|
|
10
10
|
* the local copy forever.
|
|
11
11
|
*
|
|
12
|
-
* Nothing here deletes anything. This
|
|
12
|
+
* Nothing here deletes anything. This family answers ONE question per file —
|
|
13
13
|
* "does a ledger on this machine say these bytes are durable remotely?" — and
|
|
14
14
|
* the answer has three values, not two, because the honest third one is what
|
|
15
15
|
* the same probe turned up:
|
|
@@ -45,401 +45,38 @@
|
|
|
45
45
|
*
|
|
46
46
|
* Metadata only: pack ids, pack-relative paths, byte counts, hashes, reason
|
|
47
47
|
* labels. Never a person's absolute path and never a byte of content.
|
|
48
|
-
*/
|
|
49
|
-
import crypto from "node:crypto";
|
|
50
|
-
import fs from "node:fs/promises";
|
|
51
|
-
import path from "node:path";
|
|
52
|
-
import { readRawEvidenceCursor } from "./cursors/raw-evidence-cursor.js";
|
|
53
|
-
import { readReconcileCursor, } from "./cursors/raw-evidence-reconcile-cursor.js";
|
|
54
|
-
import { describeError } from "./health-detail.js";
|
|
55
|
-
import { rotatedLogFootprint } from "./log-rotation.js";
|
|
56
|
-
import { readRawEvidenceStagingState } from "./raw-evidence-staging.js";
|
|
57
|
-
/** Bytes this inventory may spend hashing files no manifest names. */
|
|
58
|
-
export const UNNAMED_FILE_HASH_BUDGET_BYTES = 256 * 1024 * 1024;
|
|
59
|
-
/** Directory name of the staged raw-evidence tree under the state directory. */
|
|
60
|
-
export const RAW_EVIDENCE_DIR = "raw-evidence";
|
|
61
|
-
/** One-off evidence vaults a person made by hand; named, never auto-deleted. */
|
|
62
|
-
export const MANUAL_VAULT_PREFIX = "manual-study-evidence-vault-";
|
|
63
|
-
/**
|
|
64
|
-
* Everything on this machine Cockpit put there, in one read. Used by the sync
|
|
65
|
-
* tick's prune, by `cockpit clean` and by `cockpit doctor`'s disk row, so all
|
|
66
|
-
* three quote the same numbers rather than three near-misses.
|
|
67
|
-
*/
|
|
68
|
-
export async function readDiskFootprint(paths, now = new Date()) {
|
|
69
|
-
return {
|
|
70
|
-
staging: await readStagingInventory(paths, now),
|
|
71
|
-
logs: await rotatedLogFootprint(paths),
|
|
72
|
-
spool_bytes: await directoryBytes(paths.spool_dir),
|
|
73
|
-
vaults: await readManualVaults(paths),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
export async function readStagingInventory(paths, now = new Date()) {
|
|
77
|
-
const root = path.join(paths.state_dir, RAW_EVIDENCE_DIR);
|
|
78
|
-
const ledger = await readCommitLedger(paths);
|
|
79
|
-
const reconciled = await readReconcileLedger(paths);
|
|
80
|
-
const stagedAt = await readStagedAtIndex(paths);
|
|
81
|
-
const entries = await fs
|
|
82
|
-
.readdir(root, { withFileTypes: true })
|
|
83
|
-
.catch(() => []);
|
|
84
|
-
const inventory = emptyStagingInventory();
|
|
85
|
-
const budget = { remaining: UNNAMED_FILE_HASH_BUDGET_BYTES };
|
|
86
|
-
for (const entry of entries) {
|
|
87
|
-
if (!entry.isDirectory())
|
|
88
|
-
continue;
|
|
89
|
-
const dir = path.join(root, entry.name);
|
|
90
|
-
if (entry.name.startsWith(".staging-")) {
|
|
91
|
-
inventory.orphan_staging_dirs += 1;
|
|
92
|
-
inventory.orphan_staging_bytes += await directoryBytes(dir);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
if (!entry.name.startsWith("work-"))
|
|
96
|
-
continue;
|
|
97
|
-
const pack = await readPack(dir, entry.name, {
|
|
98
|
-
ledger,
|
|
99
|
-
reconciled,
|
|
100
|
-
stagedAt,
|
|
101
|
-
now,
|
|
102
|
-
budget,
|
|
103
|
-
});
|
|
104
|
-
inventory.packs.push(pack);
|
|
105
|
-
accumulatePack(inventory, pack);
|
|
106
|
-
}
|
|
107
|
-
rankUncommittedReasons(inventory);
|
|
108
|
-
return inventory;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* The commit record, read from the one place that writes it: the raw-evidence
|
|
112
|
-
* cursor's `objects` map (`upload.ts` → `markObjectCommitted`, only ever for an
|
|
113
|
-
* outcome that was not `upload_failed`).
|
|
114
|
-
*/
|
|
115
|
-
async function readCommitLedger(paths) {
|
|
116
|
-
const cursor = await readRawEvidenceCursor(paths);
|
|
117
|
-
const committed = new Map();
|
|
118
|
-
let oldest = null;
|
|
119
|
-
for (const [hash, entry] of Object.entries(cursor.objects)) {
|
|
120
|
-
committed.set(hash, entry.committed_at);
|
|
121
|
-
if (!oldest || entry.committed_at.localeCompare(oldest) < 0) {
|
|
122
|
-
oldest = entry.committed_at;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return { committed, reachesBackTo: oldest };
|
|
126
|
-
}
|
|
127
|
-
/** `pack_id/relative_path` → when this machine staged those bytes. */
|
|
128
|
-
async function readStagedAtIndex(paths) {
|
|
129
|
-
const staging = await readRawEvidenceStagingState(paths.state_dir);
|
|
130
|
-
const index = new Map();
|
|
131
|
-
for (const entry of Object.values(staging.staged)) {
|
|
132
|
-
index.set(`${entry.pack_id}/${entry.relative_path}`, entry.staged_at);
|
|
133
|
-
}
|
|
134
|
-
return index;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* The reconcile cursor as a lookup map. Read alongside the commit ledger so an
|
|
138
|
-
* object the server confirmed committed classifies exactly like one this
|
|
139
|
-
* machine's own ledger still remembers.
|
|
140
|
-
*/
|
|
141
|
-
async function readReconcileLedger(paths) {
|
|
142
|
-
const cursor = await readReconcileCursor(paths);
|
|
143
|
-
return new Map(Object.entries(cursor.results));
|
|
144
|
-
}
|
|
145
|
-
async function readPack(dir, packId, options) {
|
|
146
|
-
const manifest = await readManifestIndex(dir);
|
|
147
|
-
const pack = {
|
|
148
|
-
pack_id: packId,
|
|
149
|
-
objects: [],
|
|
150
|
-
manifest_bytes: 0,
|
|
151
|
-
manifest_present: manifest !== null,
|
|
152
|
-
};
|
|
153
|
-
for (const relative of await listPackFiles(dir)) {
|
|
154
|
-
// One stat, two facts: how big the staged copy is and WHEN it was written.
|
|
155
|
-
// The mtime is the only unfalsifiable record of how long undelivered
|
|
156
|
-
// evidence has been sitting here (BLI-3797) — every other timestamp on the
|
|
157
|
-
// machine records when something last had an opinion about it.
|
|
158
|
-
const info = await fs.stat(path.join(dir, relative)).catch(() => null);
|
|
159
|
-
const size = info?.isFile() ? info.size : 0;
|
|
160
|
-
if (relative === "manifest.json") {
|
|
161
|
-
pack.manifest_bytes += size;
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
const stagedOnDiskAt = info && Number.isFinite(info.mtimeMs)
|
|
165
|
-
? new Date(info.mtimeMs).toISOString()
|
|
166
|
-
: null;
|
|
167
|
-
pack.objects.push(await classifyObject(dir, packId, relative, size, manifest, {
|
|
168
|
-
...options,
|
|
169
|
-
stagedOnDiskAt,
|
|
170
|
-
}));
|
|
171
|
-
}
|
|
172
|
-
return pack;
|
|
173
|
-
}
|
|
174
|
-
async function classifyObject(dir, packId, relative, size, manifest, options) {
|
|
175
|
-
const hash = manifest?.get(relative) ??
|
|
176
|
-
(await hashUnnamedFile(path.join(dir, relative), size, options.budget));
|
|
177
|
-
const stagedAt = options.stagedAt.get(`${packId}/${relative}`) ?? null;
|
|
178
|
-
const committedAt = hash ? options.ledger.committed.get(hash) : undefined;
|
|
179
|
-
const base = {
|
|
180
|
-
pack_id: packId,
|
|
181
|
-
relative_path: relative,
|
|
182
|
-
byte_size: size,
|
|
183
|
-
staged_on_disk_at: options.stagedOnDiskAt,
|
|
184
|
-
disk_age_ms: ageMs(options.stagedOnDiskAt, options.now),
|
|
185
|
-
};
|
|
186
|
-
if (committedAt) {
|
|
187
|
-
return {
|
|
188
|
-
...base,
|
|
189
|
-
content_hash: hash ?? null,
|
|
190
|
-
state: "committed",
|
|
191
|
-
reason: "committed_in_ledger",
|
|
192
|
-
decided_at: committedAt,
|
|
193
|
-
age_ms: ageMs(committedAt, options.now),
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
if (hash) {
|
|
197
|
-
const reconciled = options.reconciled.get(hash);
|
|
198
|
-
if (reconciled) {
|
|
199
|
-
return classifyFromReconcileAnswer(base, hash, reconciled, options.now);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
const unknownReason = unknownStateReason(hash, manifest, stagedAt, options);
|
|
203
|
-
return {
|
|
204
|
-
...base,
|
|
205
|
-
content_hash: hash ?? null,
|
|
206
|
-
state: unknownReason ? "unknown" : "uncommitted",
|
|
207
|
-
reason: unknownReason ?? "absent_from_ledger",
|
|
208
|
-
decided_at: stagedAt,
|
|
209
|
-
age_ms: ageMs(stagedAt, options.now),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* The server's own answer for this hash, already on disk from an earlier
|
|
214
|
-
* `cockpit clean --reconcile` run. This takes priority over
|
|
215
|
-
* `unknownStateReason`'s guesswork about the LOCAL ledger's memory, because
|
|
216
|
-
* the server was actually asked and actually answered.
|
|
217
|
-
*/
|
|
218
|
-
function classifyFromReconcileAnswer(base, hash, reconciled, now) {
|
|
219
|
-
if (reconciled.verdict === "committed") {
|
|
220
|
-
const decidedAt = reconciled.server_committed_at ?? reconciled.checked_at;
|
|
221
|
-
return {
|
|
222
|
-
...base,
|
|
223
|
-
content_hash: hash,
|
|
224
|
-
state: "committed",
|
|
225
|
-
reason: "reconciled_committed",
|
|
226
|
-
decided_at: decidedAt,
|
|
227
|
-
age_ms: ageMs(decidedAt, now),
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
return {
|
|
231
|
-
...base,
|
|
232
|
-
content_hash: hash,
|
|
233
|
-
state: "uncommitted",
|
|
234
|
-
reason: reconciled.verdict === "not_committed"
|
|
235
|
-
? "reconciled_not_committed"
|
|
236
|
-
: "reconciled_unknown_to_server",
|
|
237
|
-
decided_at: reconciled.checked_at,
|
|
238
|
-
age_ms: ageMs(reconciled.checked_at, now),
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Why the ledger's silence about this object is NOT the same as a "no".
|
|
243
48
|
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (size > budget.remaining)
|
|
271
|
-
return null;
|
|
272
|
-
const bytes = await fs.readFile(file).catch(() => null);
|
|
273
|
-
if (!bytes)
|
|
274
|
-
return null;
|
|
275
|
-
budget.remaining -= bytes.byteLength;
|
|
276
|
-
return crypto.createHash("sha256").update(bytes).digest("hex");
|
|
277
|
-
}
|
|
278
|
-
async function readManifestIndex(dir) {
|
|
279
|
-
const raw = await fs
|
|
280
|
-
.readFile(path.join(dir, "manifest.json"), "utf8")
|
|
281
|
-
.catch(() => null);
|
|
282
|
-
if (!raw)
|
|
283
|
-
return null;
|
|
284
|
-
try {
|
|
285
|
-
const parsed = JSON.parse(raw);
|
|
286
|
-
const index = new Map();
|
|
287
|
-
for (const file of parsed.files ?? []) {
|
|
288
|
-
if (typeof file.relative_path === "string" &&
|
|
289
|
-
typeof file.content_hash_sha256 === "string" &&
|
|
290
|
-
file.content_hash_sha256.length === 64) {
|
|
291
|
-
index.set(file.relative_path, file.content_hash_sha256);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return index;
|
|
295
|
-
}
|
|
296
|
-
catch (error) {
|
|
297
|
-
// Named, not swallowed: an unreadable manifest turns every file in the
|
|
298
|
-
// pack into `unknown`, which is the difference between a pack that gets
|
|
299
|
-
// pruned and one that is kept forever (BLI-3238's rule, BLI-3619's cost).
|
|
300
|
-
console.error("[collector disk] pack manifest unreadable, its files cannot be classified", JSON.stringify({
|
|
301
|
-
reason: "manifest_unreadable",
|
|
302
|
-
pack_id: path.basename(dir),
|
|
303
|
-
...describeError(error),
|
|
304
|
-
}));
|
|
305
|
-
return null;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
async function listPackFiles(dir) {
|
|
309
|
-
const files = [];
|
|
310
|
-
const stack = [{ dir, prefix: "" }];
|
|
311
|
-
while (stack.length > 0) {
|
|
312
|
-
const current = stack.pop();
|
|
313
|
-
if (!current)
|
|
314
|
-
continue;
|
|
315
|
-
const entries = await fs
|
|
316
|
-
.readdir(current.dir, { withFileTypes: true })
|
|
317
|
-
.catch(() => []);
|
|
318
|
-
for (const entry of entries) {
|
|
319
|
-
const relative = current.prefix
|
|
320
|
-
? `${current.prefix}/${entry.name}`
|
|
321
|
-
: entry.name;
|
|
322
|
-
if (entry.isDirectory()) {
|
|
323
|
-
stack.push({ dir: path.join(current.dir, entry.name), prefix: relative });
|
|
324
|
-
}
|
|
325
|
-
else if (entry.isFile()) {
|
|
326
|
-
files.push(relative);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return files;
|
|
331
|
-
}
|
|
332
|
-
async function readManualVaults(paths) {
|
|
333
|
-
const entries = await fs
|
|
334
|
-
.readdir(paths.state_dir, { withFileTypes: true })
|
|
335
|
-
.catch(() => []);
|
|
336
|
-
const vaults = [];
|
|
337
|
-
for (const entry of entries) {
|
|
338
|
-
if (!entry.isDirectory() || !entry.name.startsWith(MANUAL_VAULT_PREFIX)) {
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
const dir = path.join(paths.state_dir, entry.name);
|
|
342
|
-
vaults.push({
|
|
343
|
-
name: entry.name,
|
|
344
|
-
byte_size: await directoryBytes(dir),
|
|
345
|
-
file_count: (await listPackFiles(dir)).length,
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
return vaults;
|
|
349
|
-
}
|
|
350
|
-
export async function directoryBytes(dir) {
|
|
351
|
-
let total = 0;
|
|
352
|
-
for (const relative of await listPackFiles(dir)) {
|
|
353
|
-
const info = await fs.stat(path.join(dir, relative)).catch(() => null);
|
|
354
|
-
total += info?.isFile() ? info.size : 0;
|
|
355
|
-
}
|
|
356
|
-
return total;
|
|
357
|
-
}
|
|
358
|
-
function accumulatePack(inventory, pack) {
|
|
359
|
-
inventory.pack_count += 1;
|
|
360
|
-
inventory.manifest_bytes += pack.manifest_bytes;
|
|
361
|
-
inventory.total_bytes += pack.manifest_bytes;
|
|
362
|
-
for (const object of pack.objects) {
|
|
363
|
-
inventory.object_count += 1;
|
|
364
|
-
inventory.total_bytes += object.byte_size;
|
|
365
|
-
if (object.state === "committed") {
|
|
366
|
-
inventory.committed_count += 1;
|
|
367
|
-
inventory.committed_bytes += object.byte_size;
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
if (object.state === "unknown") {
|
|
371
|
-
inventory.unknown_count += 1;
|
|
372
|
-
inventory.unknown_bytes += object.byte_size;
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
inventory.uncommitted_count += 1;
|
|
376
|
-
inventory.uncommitted_bytes += object.byte_size;
|
|
377
|
-
tallyUncommittedReason(inventory, object);
|
|
378
|
-
// The age of the BYTES, not of the answer about them (BLI-3797). Falls back
|
|
379
|
-
// to `decided_at` only when the file has no readable mtime, which is the
|
|
380
|
-
// one case where the disk itself cannot say.
|
|
381
|
-
const stagedAt = object.staged_on_disk_at ?? object.decided_at;
|
|
382
|
-
if (stagedAt &&
|
|
383
|
-
(!inventory.oldest_uncommitted_at ||
|
|
384
|
-
stagedAt.localeCompare(inventory.oldest_uncommitted_at) < 0)) {
|
|
385
|
-
inventory.oldest_uncommitted_at = stagedAt;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* One more object against its reason label. Kept as a mutable array rather than
|
|
391
|
-
* a Map so the inventory stays a plain serializable value — it rides `--json`
|
|
392
|
-
* output and a receipt, and a Map would silently become `{}` in both.
|
|
393
|
-
*/
|
|
394
|
-
function tallyUncommittedReason(inventory, object) {
|
|
395
|
-
const existing = inventory.uncommitted_reasons.find((tally) => tally.reason === object.reason);
|
|
396
|
-
const tally = existing ?? {
|
|
397
|
-
reason: object.reason,
|
|
398
|
-
count: 0,
|
|
399
|
-
bytes: 0,
|
|
400
|
-
oldest_disk_age_ms: 0,
|
|
401
|
-
};
|
|
402
|
-
tally.count += 1;
|
|
403
|
-
tally.bytes += object.byte_size;
|
|
404
|
-
tally.oldest_disk_age_ms = Math.max(tally.oldest_disk_age_ms, object.disk_age_ms || object.age_ms);
|
|
405
|
-
if (!existing)
|
|
406
|
-
inventory.uncommitted_reasons.push(tally);
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Rank the reasons by BYTES, then name the biggest one.
|
|
49
|
+
* This file is the table of contents and holds no logic (BLI-3979). Each
|
|
50
|
+
* responsibility lives in a named sibling, and every public name below is
|
|
51
|
+
* still importable from `./disk-usage.js`, which is what the prune, the
|
|
52
|
+
* retention plan, `cockpit clean`, `cockpit status`, `cockpit doctor`, the
|
|
53
|
+
* heartbeat and the redelivery plan all depend on:
|
|
54
|
+
*
|
|
55
|
+
* - `disk-usage-facts.ts` — the shapes: one staged object and its reason
|
|
56
|
+
* label, one pack, the inventory, a manual vault, the whole footprint, plus
|
|
57
|
+
* the two directory names that decide which folders are looked at.
|
|
58
|
+
* - `disk-usage-ledger.ts` — the three records an object is classified
|
|
59
|
+
* against, read once per run: the commit ledger and how far back its capped
|
|
60
|
+
* memory reaches, the recorded server answers, and when each object was
|
|
61
|
+
* staged.
|
|
62
|
+
* - `disk-usage-classify.ts` — the three-state question per file, in the order
|
|
63
|
+
* that makes it safe: this machine's ledger, then a server answer, then the
|
|
64
|
+
* named reason its silence has.
|
|
65
|
+
* - `disk-usage-files.ts` — the only code here that touches a file for its own
|
|
66
|
+
* sake: the pack walk, directory bytes, the manifest index, and the budgeted
|
|
67
|
+
* hash of a file no manifest names.
|
|
68
|
+
* - `disk-usage-totals.ts` — classified objects into the numbers a person
|
|
69
|
+
* reads: per-state totals, the reason ranking by BYTES, and the oldest
|
|
70
|
+
* undelivered bytes' own age.
|
|
71
|
+
* - `disk-usage-scan.ts` — the staging walk itself: `readStagingInventory`,
|
|
72
|
+
* one pack at a time.
|
|
73
|
+
* - `disk-usage-footprint.ts` — the whole-machine read: staging plus rotated
|
|
74
|
+
* logs, the spool and the hand-made vaults.
|
|
410
75
|
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
* are not the same finding, and counting rows would have called them close.
|
|
76
|
+
* A new sibling here must also join `scripts/build-public-cli.mjs`
|
|
77
|
+
* `runtimeFiles`, or the repo tests stay green while the packed CLI breaks.
|
|
414
78
|
*/
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
function emptyStagingInventory() {
|
|
421
|
-
return {
|
|
422
|
-
pack_count: 0,
|
|
423
|
-
object_count: 0,
|
|
424
|
-
total_bytes: 0,
|
|
425
|
-
committed_count: 0,
|
|
426
|
-
committed_bytes: 0,
|
|
427
|
-
uncommitted_count: 0,
|
|
428
|
-
uncommitted_bytes: 0,
|
|
429
|
-
oldest_uncommitted_at: null,
|
|
430
|
-
uncommitted_reasons: [],
|
|
431
|
-
dominant_uncommitted_reason: null,
|
|
432
|
-
unknown_count: 0,
|
|
433
|
-
unknown_bytes: 0,
|
|
434
|
-
manifest_bytes: 0,
|
|
435
|
-
orphan_staging_dirs: 0,
|
|
436
|
-
orphan_staging_bytes: 0,
|
|
437
|
-
packs: [],
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
function ageMs(at, now) {
|
|
441
|
-
if (!at)
|
|
442
|
-
return 0;
|
|
443
|
-
const parsed = Date.parse(at);
|
|
444
|
-
return Number.isFinite(parsed) ? Math.max(0, now.getTime() - parsed) : 0;
|
|
445
|
-
}
|
|
79
|
+
export { MANUAL_VAULT_PREFIX, RAW_EVIDENCE_DIR } from "./disk-usage-facts.js";
|
|
80
|
+
export { directoryBytes, UNNAMED_FILE_HASH_BUDGET_BYTES, } from "./disk-usage-files.js";
|
|
81
|
+
export { readStagingInventory } from "./disk-usage-scan.js";
|
|
82
|
+
export { readDiskFootprint } from "./disk-usage-footprint.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.95",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-verb-help.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@bli-cockpit/memory-mcp": "0.1.
|
|
31
|
-
"@bli-cockpit/mcp": "0.1.
|
|
32
|
-
"@bli-cockpit/telemetry-core": "0.1.
|
|
30
|
+
"@bli-cockpit/memory-mcp": "0.1.25",
|
|
31
|
+
"@bli-cockpit/mcp": "0.1.28",
|
|
32
|
+
"@bli-cockpit/telemetry-core": "0.1.42"
|
|
33
33
|
}
|
|
34
34
|
}
|