@bli-cockpit/cli 0.2.237 → 0.2.239
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/adapters/attribution-core-fallbacks.js +15 -11
- package/dist/adapters/attribution-core-score.js +111 -87
- package/dist/adapters/car-state.js +38 -35
- package/dist/adapters/claude-attribution-discovery.js +81 -56
- package/dist/adapters/claude-attribution-score.js +108 -94
- package/dist/adapters/claude-attribution-signals.js +52 -42
- package/dist/adapters/codex-attribution.js +42 -33
- package/dist/adapters/codex-session-signals.js +11 -9
- package/dist/adapters/git-state.js +56 -50
- package/dist/adapters/local-sources.js +52 -43
- package/dist/adapters/raw-evidence-git-diff.js +4 -2
- package/dist/adapters/raw-evidence-sanitize.js +15 -12
- package/dist/adapters/raw-evidence-transcript-file.js +32 -31
- package/dist/autostart-self-heal.js +18 -2
- package/dist/commands/agent-session-report.js +51 -59
- package/dist/commands/agent-setup-observed.js +30 -23
- package/dist/commands/agent-setup.js +96 -45
- package/dist/commands/autostart-heal.js +8 -2
- package/dist/commands/autostart-reading.js +3 -0
- package/dist/commands/backfill-evidence-outcomes.js +9 -6
- package/dist/commands/backfill-issues.js +3 -0
- package/dist/commands/backfill-scan.js +27 -22
- package/dist/commands/backfill-upload.js +69 -60
- package/dist/commands/backfill.js +26 -21
- package/dist/commands/brief-done.js +4 -1
- package/dist/commands/brief-edit.js +71 -46
- package/dist/commands/cal-detail.js +35 -0
- package/dist/commands/cal-direct.js +119 -0
- package/dist/commands/cal-overlay.js +73 -30
- package/dist/commands/cal.js +62 -25
- package/dist/commands/clean.js +4 -1
- package/dist/commands/docs-read.js +5 -2
- package/dist/commands/docs.js +38 -6
- package/dist/commands/doctor-backfill.js +88 -71
- package/dist/commands/doctor-pipeline.js +59 -46
- package/dist/commands/heartbeat-post.js +116 -0
- package/dist/commands/heartbeat-token-lock.js +46 -33
- package/dist/commands/heartbeat.js +43 -125
- package/dist/commands/install-update.js +17 -15
- package/dist/commands/issue-show.js +27 -28
- package/dist/commands/issue-versions.js +93 -0
- package/dist/commands/issue-write.js +70 -23
- package/dist/commands/issue.js +73 -37
- package/dist/commands/jarvis-live-answer.js +99 -0
- package/dist/commands/jarvis-render.js +1 -94
- package/dist/commands/jarvis-trace.js +30 -22
- package/dist/commands/jarvis-turn-report.js +124 -0
- package/dist/commands/jarvis-turn.js +108 -182
- package/dist/commands/jarvis.js +26 -17
- package/dist/commands/local-args-collector-setup.js +82 -84
- package/dist/commands/local-args-collector-work.js +37 -38
- package/dist/commands/local-args-tower-admin.js +155 -142
- package/dist/commands/local-args-tower-cal.js +69 -54
- package/dist/commands/local-args-tower-careers.js +73 -54
- package/dist/commands/local-args-tower-docs-msg.js +79 -51
- package/dist/commands/local-args-tower-github.js +26 -20
- package/dist/commands/local-args-tower-hits.js +32 -20
- package/dist/commands/local-args-tower-manager.js +85 -4
- package/dist/commands/local-args-tower-memory.js +139 -117
- package/dist/commands/local-args-tower-msg.js +24 -6
- package/dist/commands/local-args-tower-notes.js +346 -0
- package/dist/commands/local-args-tower-pages.js +100 -378
- package/dist/commands/local-args-tower-settings.js +28 -29
- package/dist/commands/local-args-tower-setup.js +11 -4
- package/dist/commands/local-args-tower-work.js +74 -37
- package/dist/commands/local-args.js +80 -129
- package/dist/commands/local-help-commands-tower-ops.js +15 -4
- package/dist/commands/local-help-commands-tower.js +20 -7
- package/dist/commands/local-help-commands.js +342 -341
- package/dist/commands/local-help.js +87 -83
- package/dist/commands/local-run-guards.js +100 -0
- package/dist/commands/local.js +137 -206
- package/dist/commands/login.js +13 -9
- package/dist/commands/logs.js +55 -38
- package/dist/commands/mail-attachments.js +44 -26
- package/dist/commands/manager-render-home.js +208 -0
- package/dist/commands/manager-render.js +7 -3
- package/dist/commands/manager-requests.js +33 -0
- package/dist/commands/manager.js +39 -5
- package/dist/commands/memory-consolidation.js +18 -9
- package/dist/commands/memory-daemon-probe.js +37 -28
- package/dist/commands/memory-install-config.js +64 -60
- package/dist/commands/memory-install.js +21 -41
- package/dist/commands/memory-requeue.js +26 -19
- package/dist/commands/memory-rules.js +26 -18
- package/dist/commands/memory-search-print.js +26 -0
- package/dist/commands/memory-store.js +14 -30
- package/dist/commands/memory-verb-list.js +49 -48
- package/dist/commands/msg-read-line.js +32 -0
- package/dist/commands/msg.js +45 -3
- package/dist/commands/notes-accounts.js +49 -32
- package/dist/commands/notes-sync-loop.js +115 -89
- package/dist/commands/notes-wisprflow-login.js +44 -23
- package/dist/commands/notes-writes.js +115 -100
- package/dist/commands/notes.js +33 -55
- package/dist/commands/onboard-flows.js +69 -51
- package/dist/commands/onboard.js +11 -9
- package/dist/commands/ops-render-memory.js +10 -7
- package/dist/commands/ops-render.js +33 -30
- package/dist/commands/ops.js +3 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/session-sync-outside-roots.js +40 -36
- package/dist/commands/session-sync-plan.js +14 -10
- package/dist/commands/session-sync-upload.js +24 -22
- package/dist/commands/session-sync.js +19 -8
- package/dist/commands/setup.js +13 -0
- package/dist/commands/slack.js +25 -16
- package/dist/commands/sources-check.js +13 -7
- package/dist/commands/sources-discovery.js +25 -17
- package/dist/commands/sources-verdict.js +19 -18
- package/dist/commands/speed.js +23 -19
- package/dist/commands/start.js +20 -16
- package/dist/commands/sync-doctor.js +52 -48
- package/dist/commands/sync.js +22 -16
- package/dist/commands/usage-telemetry-toml.js +71 -45
- package/dist/commands/usage.js +94 -68
- package/dist/commands/workbook-render.js +10 -7
- package/dist/evidence-reconcile-client.js +75 -64
- package/dist/evidence-redelivery-plan.js +37 -34
- package/dist/evidence-redelivery.js +32 -29
- package/dist/evidence-transcripts-client.js +20 -20
- package/dist/evidence-upload-object.js +86 -89
- package/dist/evidence-upload-plan.js +94 -83
- package/dist/evidence-upload-terminal.js +45 -50
- package/dist/image-description-session.js +156 -159
- package/dist/local-state-pairing-code.js +39 -31
- package/dist/local-state-status.js +9 -2
- package/dist/onboarding-roots.js +5 -8
- package/dist/process-runner.js +8 -8
- package/dist/raw-evidence-gc.js +33 -38
- package/dist/raw-evidence-stuck-summary.js +7 -14
- package/dist/repo-identity.js +26 -23
- package/dist/scheduled-self-update.js +11 -15
- package/dist/second-install.js +3 -0
- package/dist/slim/codex-rules.js +23 -36
- package/dist/spool/local-spool-parse.js +13 -12
- package/dist/sync-lock.js +5 -15
- package/dist/tower-client.js +36 -53
- package/dist/tower-stream.js +7 -24
- package/dist/upload-agent-artifacts.js +58 -64
- package/dist/upload-envelope-build.js +60 -59
- package/dist/upload-evidence-delivery-reconcile.js +58 -56
- package/dist/upload-evidence-delivery-summary.js +32 -34
- package/dist/upload-ingest-receipt.js +20 -23
- package/dist/upload-session-reports-queue.js +4 -1
- package/dist/upload-session-reports-wire.js +64 -68
- package/dist/upload-sync.js +171 -157
- package/package.json +4 -4
|
@@ -41,54 +41,20 @@ export async function runBriefEdit(command, io) {
|
|
|
41
41
|
const log = (line) => writeLine(io.stderr, line);
|
|
42
42
|
const startedAt = Date.now();
|
|
43
43
|
// ── 1. The page, as a document ────────────────────────────────────────────
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
deviceToken: session.device_token,
|
|
48
|
-
fetch: io.fetch,
|
|
49
|
-
method: "GET",
|
|
50
|
-
label: "brief:edit",
|
|
51
|
-
timeoutMs: REQUEST_DEADLINE_MS,
|
|
52
|
-
log,
|
|
53
|
-
});
|
|
54
|
-
if (!read.ok)
|
|
55
|
-
return fail(command, io, read.reason, read.detail);
|
|
56
|
-
const brief = read.body;
|
|
57
|
-
const personId = brief.page?.personId;
|
|
58
|
-
const original = brief.editable?.document;
|
|
59
|
-
const lines = (brief.editable?.lines ?? []).filter((line) => typeof line.claimId === "string" && typeof line.text === "string");
|
|
60
|
-
if (!brief.ok || !personId || typeof original !== "string" || lines.length === 0) {
|
|
61
|
-
return fail(command, io, brief.error ?? "no_page", brief.reply ?? "There is no page to edit.");
|
|
62
|
-
}
|
|
44
|
+
const page = await readEditablePage(command, io, session, dashboardUrl, log);
|
|
45
|
+
if (!page.ok)
|
|
46
|
+
return fail(command, io, page.reason, page.detail);
|
|
63
47
|
// ── 2. Their editor, or their stdin ───────────────────────────────────────
|
|
64
|
-
const edited = await collectEditedDocument(io, original, log);
|
|
48
|
+
const edited = await collectEditedDocument(io, page.original, log);
|
|
65
49
|
if (!edited.ok)
|
|
66
50
|
return fail(command, io, edited.reason, edited.detail);
|
|
67
51
|
// ── 3. The diff, here ─────────────────────────────────────────────────────
|
|
68
|
-
const diff = diffDocument(lines, edited.text);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return fail(command, io, "deleted_claim", `The line [${diff.deleted[0]}] is missing from what you saved. Put it back — a line you delete ` +
|
|
75
|
-
"reads as no opinion at all, not as a sentence to remove, and Tower cannot tell those apart. " +
|
|
76
|
-
"Nothing has been sent.");
|
|
77
|
-
}
|
|
78
|
-
if (diff.edits.length === 0) {
|
|
79
|
-
const styled = colorEnabled(io);
|
|
80
|
-
if (command.json) {
|
|
81
|
-
writeLine(io.stdout, JSON.stringify({ ok: true, saved: 0, reason: "nothing_changed" }));
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
writeLine(io.stdout, dim("Nothing changed, so nothing was written.", styled));
|
|
85
|
-
}
|
|
86
|
-
writeLine(io.stderr, `[brief edit] nothing changed ${JSON.stringify({
|
|
87
|
-
lines: lines.length,
|
|
88
|
-
elapsed_ms: Date.now() - startedAt,
|
|
89
|
-
})}`);
|
|
90
|
-
return 0;
|
|
91
|
-
}
|
|
52
|
+
const diff = diffDocument(page.lines, edited.text);
|
|
53
|
+
const refusal = refuseBadDiff(diff);
|
|
54
|
+
if (refusal)
|
|
55
|
+
return fail(command, io, refusal.reason, refusal.detail);
|
|
56
|
+
if (diff.edits.length === 0)
|
|
57
|
+
return reportNothingChanged(command, io, page.lines.length, startedAt);
|
|
92
58
|
// ── 4. The same door the browser's save posts to ──────────────────────────
|
|
93
59
|
const saved = await towerJsonRequest({
|
|
94
60
|
dashboardUrl,
|
|
@@ -99,8 +65,8 @@ export async function runBriefEdit(command, io) {
|
|
|
99
65
|
timeoutMs: REQUEST_DEADLINE_MS,
|
|
100
66
|
log,
|
|
101
67
|
body: {
|
|
102
|
-
personId,
|
|
103
|
-
pageId:
|
|
68
|
+
personId: page.personId,
|
|
69
|
+
pageId: page.pageId,
|
|
104
70
|
edits: diff.edits,
|
|
105
71
|
...(command.reason ? { reason: command.reason } : {}),
|
|
106
72
|
},
|
|
@@ -122,6 +88,65 @@ export async function runBriefEdit(command, io) {
|
|
|
122
88
|
})}`);
|
|
123
89
|
return 0;
|
|
124
90
|
}
|
|
91
|
+
async function readEditablePage(command, io, session, dashboardUrl, log) {
|
|
92
|
+
const read = await towerJsonRequest({
|
|
93
|
+
dashboardUrl,
|
|
94
|
+
path: `/api/jarvis/brief${briefQuery(command)}`,
|
|
95
|
+
deviceToken: session.device_token,
|
|
96
|
+
fetch: io.fetch,
|
|
97
|
+
method: "GET",
|
|
98
|
+
label: "brief:edit",
|
|
99
|
+
timeoutMs: REQUEST_DEADLINE_MS,
|
|
100
|
+
log,
|
|
101
|
+
});
|
|
102
|
+
if (!read.ok)
|
|
103
|
+
return { ok: false, reason: read.reason, detail: read.detail };
|
|
104
|
+
const brief = read.body;
|
|
105
|
+
const personId = brief.page?.personId;
|
|
106
|
+
const original = brief.editable?.document;
|
|
107
|
+
const lines = (brief.editable?.lines ?? []).filter((line) => typeof line.claimId === "string" && typeof line.text === "string");
|
|
108
|
+
if (!brief.ok || !personId || typeof original !== "string" || lines.length === 0) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
reason: brief.error ?? "no_page",
|
|
112
|
+
detail: brief.reply ?? "There is no page to edit.",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return { ok: true, personId, pageId: brief.page?.pageId ?? null, original, lines };
|
|
116
|
+
}
|
|
117
|
+
/** The refusal for a diff that invented or deleted a claim id, or null when the diff may be sent. */
|
|
118
|
+
function refuseBadDiff(diff) {
|
|
119
|
+
if (diff.invented.length > 0) {
|
|
120
|
+
return {
|
|
121
|
+
reason: "invented_claim",
|
|
122
|
+
detail: `The page has no line called “${diff.invented[0]}”. Change the words after an id, never the id ` +
|
|
123
|
+
"itself — nothing has been sent to Tower.",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (diff.deleted.length > 0) {
|
|
127
|
+
return {
|
|
128
|
+
reason: "deleted_claim",
|
|
129
|
+
detail: `The line [${diff.deleted[0]}] is missing from what you saved. Put it back — a line you delete ` +
|
|
130
|
+
"reads as no opinion at all, not as a sentence to remove, and Tower cannot tell those apart. " +
|
|
131
|
+
"Nothing has been sent.",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
function reportNothingChanged(command, io, lineCount, startedAt) {
|
|
137
|
+
const styled = colorEnabled(io);
|
|
138
|
+
if (command.json) {
|
|
139
|
+
writeLine(io.stdout, JSON.stringify({ ok: true, saved: 0, reason: "nothing_changed" }));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
writeLine(io.stdout, dim("Nothing changed, so nothing was written.", styled));
|
|
143
|
+
}
|
|
144
|
+
writeLine(io.stderr, `[brief edit] nothing changed ${JSON.stringify({
|
|
145
|
+
lines: lineCount,
|
|
146
|
+
elapsed_ms: Date.now() - startedAt,
|
|
147
|
+
})}`);
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
125
150
|
function briefQuery(command) {
|
|
126
151
|
const params = new URLSearchParams({ editable: "1" });
|
|
127
152
|
if (command.subject)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { askAgentDoor, emitAgentDoor, failAgentDoor } from "./agent-door.js";
|
|
2
|
+
import { writeLine } from "./cli-io.js";
|
|
3
|
+
export async function runCalDetail(command, door) {
|
|
4
|
+
const query = new URLSearchParams({ tz: command.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone });
|
|
5
|
+
if (command.occurrenceStart)
|
|
6
|
+
query.set("occurrence_start", command.occurrenceStart);
|
|
7
|
+
const path = `/api/cal/events/${encodeURIComponent(command.subjectId)}${command.action === "export" ? "/export" : ""}?${query}`;
|
|
8
|
+
const answer = await askAgentDoor(door, { method: "GET", path, label: `cal ${command.action}`, timeoutMs: 30000 });
|
|
9
|
+
if (!answer.ok)
|
|
10
|
+
return failAgentDoor(door, "[cal cli]", answer.reason, answer.detail);
|
|
11
|
+
if (door.json)
|
|
12
|
+
return emitAgentDoor(door, answer.body);
|
|
13
|
+
if (command.action === "export") {
|
|
14
|
+
writeLine(door.io.stdout, String(answer.body.content));
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
const event = answer.body.event;
|
|
18
|
+
const zone = command.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
19
|
+
const date = new Date(event.starts_at).toLocaleDateString("en-CA", { timeZone: event.all_day ? "UTC" : zone });
|
|
20
|
+
const clock = (value) => new Intl.DateTimeFormat("en-GB", { timeZone: zone, hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(new Date(value));
|
|
21
|
+
writeLine(door.io.stdout, `${event.title} · ${date} · ${event.all_day ? "All day" : `${clock(event.starts_at)}–${clock(event.ends_at)}`} · ${zone} · ${event.calendar_name}`);
|
|
22
|
+
writeLine(door.io.stdout, event.recurrence_summary);
|
|
23
|
+
if (event.recurrence_coverage_note)
|
|
24
|
+
writeLine(door.io.stdout, event.recurrence_coverage_note);
|
|
25
|
+
writeLine(door.io.stdout, `Organizer: ${event.organizer_detail?.name ?? "Unknown"}`);
|
|
26
|
+
for (const guest of event.guests)
|
|
27
|
+
writeLine(door.io.stdout, `${guest.name} · ${guest.rsvp}${guest.optional ? " · Optional" : ""}`);
|
|
28
|
+
if (event.meeting_url)
|
|
29
|
+
writeLine(door.io.stdout, `Join: ${event.meeting_url}`);
|
|
30
|
+
if (event.location)
|
|
31
|
+
writeLine(door.io.stdout, `Location: ${event.location}`);
|
|
32
|
+
if (event.capabilities.reason)
|
|
33
|
+
writeLine(door.io.stdout, event.capabilities.reason);
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit cal duplicate | batch | undo` (BLI-21296, calendar slice 4): the
|
|
3
|
+
* terminal in front of the same doors the browser's grid uses. `batch` reads
|
|
4
|
+
* its changes as JSON on stdin; every verb takes `--idempotency-key` so a retry
|
|
5
|
+
* writes once, and `--json` prints the door's answer untouched.
|
|
6
|
+
*/
|
|
7
|
+
import { askAgentDoor, emitAgentDoor, failAgentDoor } from "./agent-door.js";
|
|
8
|
+
import { isInteractiveStdin, readPipedText, writeLine } from "./cli-io.js";
|
|
9
|
+
const TAG = "[cal cli]";
|
|
10
|
+
const WRITE_DEADLINE_MS = 300_000;
|
|
11
|
+
export async function runCalDirect(command, door) {
|
|
12
|
+
if (command.action === "duplicate")
|
|
13
|
+
return duplicate(command, door);
|
|
14
|
+
if (command.action === "batch")
|
|
15
|
+
return batch(command, door);
|
|
16
|
+
return undo(command, door);
|
|
17
|
+
}
|
|
18
|
+
function keyed(command) {
|
|
19
|
+
return {
|
|
20
|
+
...(command.idempotencyKey ? { idempotency_key: command.idempotencyKey } : {}),
|
|
21
|
+
...(command.reason ? { reason: command.reason } : {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function writeReceiptLines(door, receipt) {
|
|
25
|
+
if (receipt.event?.account_name)
|
|
26
|
+
writeLine(door.io.stdout, `Account: ${receipt.event.account_name}.`);
|
|
27
|
+
if (receipt.event?.event_id)
|
|
28
|
+
writeLine(door.io.stdout, `Tower event: ${receipt.event.event_id}`);
|
|
29
|
+
if (receipt.change_id)
|
|
30
|
+
writeLine(door.io.stdout, `Change: ${receipt.change_id} (undo with: cockpit cal undo ${receipt.change_id})`);
|
|
31
|
+
else
|
|
32
|
+
writeLine(door.io.stdout, "Google took the change but Tower could not record it in the ledger, so there is no change id to undo.");
|
|
33
|
+
if (receipt.event?.version)
|
|
34
|
+
writeLine(door.io.stdout, `Version: ${receipt.event.version}`);
|
|
35
|
+
if (receipt.replayed)
|
|
36
|
+
writeLine(door.io.stdout, "This key was used before: nothing new was written; this is the first receipt.");
|
|
37
|
+
}
|
|
38
|
+
async function duplicate(command, door) {
|
|
39
|
+
const answer = await askAgentDoor(door, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
path: `/api/cal/events/${encodeURIComponent(command.subjectId ?? "")}/duplicate`,
|
|
42
|
+
label: "cal duplicate",
|
|
43
|
+
timeoutMs: WRITE_DEADLINE_MS,
|
|
44
|
+
body: {
|
|
45
|
+
...(command.startsAt ? { starts_at: command.startsAt } : {}),
|
|
46
|
+
...(command.endsAt ? { ends_at: command.endsAt } : {}),
|
|
47
|
+
...(command.calendarId ? { calendar_id: command.calendarId } : {}),
|
|
48
|
+
...(command.accountName ? { account: command.accountName } : {}),
|
|
49
|
+
...(command.keepInvitees ? { keep_invitees: true } : {}),
|
|
50
|
+
...keyed(command),
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
if (!answer.ok)
|
|
54
|
+
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
55
|
+
if (door.json)
|
|
56
|
+
return emitAgentDoor(door, { ok: true, ...answer.body });
|
|
57
|
+
const receipt = answer.body;
|
|
58
|
+
writeLine(door.io.stdout, `Duplicated "${receipt.event?.title ?? "the event"}". ${receipt.invitees === "kept" ? "The same guests were invited (Google emailed them)." : "No guests were invited."}`);
|
|
59
|
+
writeReceiptLines(door, receipt);
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
async function readBatchBody(door) {
|
|
63
|
+
if (isInteractiveStdin(door.io))
|
|
64
|
+
return null;
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(await readPipedText(door.io.stdin, { maxChars: 200_000, overflowMessage: "The batch is too large." }));
|
|
67
|
+
if (Array.isArray(parsed))
|
|
68
|
+
return { changes: parsed };
|
|
69
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function batch(command, door) {
|
|
76
|
+
const body = await readBatchBody(door);
|
|
77
|
+
if (!body || !Array.isArray(body.changes)) {
|
|
78
|
+
return failAgentDoor(door, TAG, "invalid_body", 'Pipe JSON: {"changes":[{"event_id":"…","starts_at":"…","ends_at":"…","expected_version":"…"}]} (or a bare array). Up to 25 changes, each event once.');
|
|
79
|
+
}
|
|
80
|
+
const answer = await askAgentDoor(door, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
path: "/api/cal/events/batch",
|
|
83
|
+
label: "cal batch",
|
|
84
|
+
timeoutMs: WRITE_DEADLINE_MS,
|
|
85
|
+
body: { ...body, ...keyed(command) },
|
|
86
|
+
});
|
|
87
|
+
if (!answer.ok)
|
|
88
|
+
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
89
|
+
const result = answer.body;
|
|
90
|
+
// A script must be able to tell "all of it" from "some of it" without parsing: exit 1 unless every item was written.
|
|
91
|
+
const code = result.outcome === "all_committed" ? 0 : 1;
|
|
92
|
+
if (door.json)
|
|
93
|
+
return emitAgentDoor(door, { ok: code === 0, ...result }, code);
|
|
94
|
+
for (const item of result.items) {
|
|
95
|
+
writeLine(door.io.stdout, `#${item.index + 1} ${item.event_id}: ${item.outcome}${item.reason ? ` (${item.reason})` : ""}${item.change_id ? ` change ${item.change_id}` : ""}`);
|
|
96
|
+
}
|
|
97
|
+
writeLine(door.io.stdout, `${result.committed} of ${result.items.length} changed (${result.outcome.replaceAll("_", " ")}).`);
|
|
98
|
+
if (result.unchanged.length > 0) {
|
|
99
|
+
writeLine(door.io.stdout, `Left as they were: ${result.unchanged.map((item) => `#${item.index + 1} ${item.event_id}`).join(", ")}.`);
|
|
100
|
+
}
|
|
101
|
+
return code;
|
|
102
|
+
}
|
|
103
|
+
async function undo(command, door) {
|
|
104
|
+
const answer = await askAgentDoor(door, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
path: "/api/cal/undo",
|
|
107
|
+
label: "cal undo",
|
|
108
|
+
timeoutMs: WRITE_DEADLINE_MS,
|
|
109
|
+
body: { ...(command.subjectId ? { change_id: command.subjectId } : {}), ...keyed(command) },
|
|
110
|
+
});
|
|
111
|
+
if (!answer.ok)
|
|
112
|
+
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
113
|
+
if (door.json)
|
|
114
|
+
return emitAgentDoor(door, { ok: true, ...answer.body });
|
|
115
|
+
const receipt = answer.body;
|
|
116
|
+
writeLine(door.io.stdout, `Undid change ${receipt.undoes_change_id}. The earlier values are back (or the created event is gone); guests, if any, were told by Google.`);
|
|
117
|
+
writeReceiptLines(door, receipt);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
@@ -1,29 +1,51 @@
|
|
|
1
1
|
import { askAgentDoor, emitAgentDoor, failAgentDoor } from "./agent-door.js";
|
|
2
2
|
import { readPipedText, isInteractiveStdin, writeLine } from "./cli-io.js";
|
|
3
|
+
const FAIL_TAG = "[cal cli]";
|
|
4
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
3
5
|
export async function runCalOverlay(command, door) {
|
|
4
|
-
if (command.action === "calendar-change")
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return failAgentDoor(door, "[cal cli]", "invalid_body", "Pipe JSON preferences or use --color '#4285f4' (provider resets).");
|
|
15
|
-
}
|
|
6
|
+
if (command.action === "calendar-change")
|
|
7
|
+
return changeCalendarPreferences(command, door);
|
|
8
|
+
return showConflicts(command, door);
|
|
9
|
+
}
|
|
10
|
+
async function changeCalendarPreferences(command, door) {
|
|
11
|
+
let body = command.color ? { color_override: command.color === "provider" ? null : command.color } : {};
|
|
12
|
+
if (!command.color && !isInteractiveStdin(door.io)) {
|
|
13
|
+
const piped = await readPipedPreferences(door);
|
|
14
|
+
if (piped === null) {
|
|
15
|
+
return failAgentDoor(door, FAIL_TAG, "invalid_body", "Pipe JSON preferences or use --color '#4285f4' (provider resets).");
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
return failAgentDoor(door, "[cal cli]", "invalid_body", "Use --color '#4285f4', --color provider, or pipe JSON with color_override/selected.");
|
|
19
|
-
const answer = await askAgentDoor(door, { method: "PATCH", path: `/api/cal/calendars/${encodeURIComponent(command.subjectId)}`, body, label: "cal calendar-change", timeoutMs: 30000 });
|
|
20
|
-
if (!answer.ok)
|
|
21
|
-
return failAgentDoor(door, "[cal cli]", answer.reason, answer.detail);
|
|
22
|
-
if (door.json)
|
|
23
|
-
return emitAgentDoor(door, answer.body);
|
|
24
|
-
writeLine(door.io.stdout, "Calendar preferences saved. Provider sync preserves your colour override.");
|
|
25
|
-
return 0;
|
|
17
|
+
body = piped;
|
|
26
18
|
}
|
|
19
|
+
if (!Object.keys(body).length) {
|
|
20
|
+
return failAgentDoor(door, FAIL_TAG, "invalid_body", "Use --color '#4285f4', --color provider, or pipe JSON with color_override/selected.");
|
|
21
|
+
}
|
|
22
|
+
const answer = await askAgentDoor(door, {
|
|
23
|
+
method: "PATCH",
|
|
24
|
+
path: `/api/cal/calendars/${encodeURIComponent(command.subjectId)}`,
|
|
25
|
+
body,
|
|
26
|
+
label: "cal calendar-change",
|
|
27
|
+
timeoutMs: REQUEST_TIMEOUT_MS,
|
|
28
|
+
});
|
|
29
|
+
if (!answer.ok)
|
|
30
|
+
return failAgentDoor(door, FAIL_TAG, answer.reason, answer.detail);
|
|
31
|
+
if (door.json)
|
|
32
|
+
return emitAgentDoor(door, answer.body);
|
|
33
|
+
writeLine(door.io.stdout, "Calendar preferences saved. Provider sync preserves your colour override.");
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
/** Piped JSON preferences, or null when the pipe is unreadable or not a JSON object. */
|
|
37
|
+
async function readPipedPreferences(door) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(await readPipedText(door.io.stdin, { maxChars: 4000, overflowMessage: "Preferences are too large." }));
|
|
40
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
41
|
+
return null;
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function showConflicts(command, door) {
|
|
27
49
|
const query = new URLSearchParams({ tz: command.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone });
|
|
28
50
|
if (command.from)
|
|
29
51
|
query.set("from", command.from);
|
|
@@ -33,31 +55,52 @@ export async function runCalOverlay(command, door) {
|
|
|
33
55
|
query.set("offset", String(command.offset));
|
|
34
56
|
if (command.includeDeselected)
|
|
35
57
|
query.set("all", "true");
|
|
36
|
-
const answer = await askAgentDoor(door, { method: "GET", path: `/api/cal/conflicts?${query}`, label: "cal conflicts", timeoutMs:
|
|
58
|
+
const answer = await askAgentDoor(door, { method: "GET", path: `/api/cal/conflicts?${query}`, label: "cal conflicts", timeoutMs: REQUEST_TIMEOUT_MS });
|
|
37
59
|
if (!answer.ok)
|
|
38
|
-
return failAgentDoor(door,
|
|
60
|
+
return failAgentDoor(door, FAIL_TAG, answer.reason, answer.detail);
|
|
39
61
|
if (door.json)
|
|
40
62
|
return emitAgentDoor(door, answer.body);
|
|
41
63
|
const result = answer.body;
|
|
42
64
|
const zone = result.window?.time_zone ?? command.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
65
|
+
writeConflictGroups(door, result, zone);
|
|
66
|
+
writeCoverage(door, result);
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
function writeConflictGroups(door, result, zone) {
|
|
43
70
|
writeLine(door.io.stdout, `${result.groups.length} double-bookings · own calendars`);
|
|
44
71
|
for (const group of result.groups) {
|
|
45
72
|
const parts = new Intl.DateTimeFormat("en-GB", { timeZone: zone, weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).formatToParts(new Date(group.from));
|
|
46
73
|
const part = (name) => parts.find(item => item.type === name)?.value;
|
|
47
|
-
|
|
74
|
+
const meetingTitles = group.meetings.map(meeting => `${meeting.event.title} · ${meeting.event.calendar_name}`).join(" / ");
|
|
75
|
+
writeLine(door.io.stdout, `${part("weekday")} ${part("day")} ${part("month")} ${part("hour")}:${part("minute")}: ${meetingTitles}`);
|
|
48
76
|
}
|
|
49
|
-
|
|
77
|
+
}
|
|
78
|
+
function describeSyncAge(checked) {
|
|
50
79
|
const oldest = Math.min(...checked.map(calendar => calendar.lastSyncAt ? Date.parse(calendar.lastSyncAt) : NaN));
|
|
51
80
|
const minutes = Math.max(0, Math.floor((Date.now() - oldest) / 60000));
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
if (!checked.length)
|
|
82
|
+
return "none checked";
|
|
83
|
+
if (!Number.isFinite(oldest))
|
|
84
|
+
return "sync age unknown";
|
|
85
|
+
if (minutes < 1)
|
|
86
|
+
return "just now";
|
|
87
|
+
if (minutes < 60)
|
|
88
|
+
return `${minutes} min ago`;
|
|
89
|
+
if (minutes < 1440)
|
|
90
|
+
return `${Math.floor(minutes / 60)} h ago`;
|
|
91
|
+
return `${Math.floor(minutes / 1440)} days ago`;
|
|
92
|
+
}
|
|
93
|
+
function writeCoverage(door, result) {
|
|
94
|
+
const checked = result.coverage.calendars.filter(calendar => calendar.checked);
|
|
95
|
+
writeLine(door.io.stdout, `Checked ${checked.length} calendar${checked.length === 1 ? "" : "s"} · ${describeSyncAge(checked)}`);
|
|
54
96
|
for (const calendar of result.coverage.calendars.filter(calendar => calendar.hidden || !calendar.checked)) {
|
|
55
97
|
writeLine(door.io.stdout, `${calendar.name}: ${calendar.hidden ? "hidden; " : ""}${calendar.checked ? "included by request" : `not checked (${(calendar.excludedReason ?? "unavailable").replaceAll("_", " ")})`}`);
|
|
56
98
|
}
|
|
57
|
-
for (const account of result.coverage.accounts ?? [])
|
|
99
|
+
for (const account of result.coverage.accounts ?? []) {
|
|
58
100
|
if (!account.calendars)
|
|
59
101
|
writeLine(door.io.stdout, `${account.label}: no discovered calendars (${account.status.replaceAll("_", " ")})`);
|
|
60
|
-
|
|
102
|
+
}
|
|
103
|
+
if (result.coverage.incomplete || result.coverage.truncated || result.coverage.notes.length) {
|
|
61
104
|
writeLine(door.io.stdout, "Incomplete window. This check cannot confirm free time.");
|
|
62
|
-
|
|
105
|
+
}
|
|
63
106
|
}
|
package/dist/commands/cal.js
CHANGED
|
@@ -22,8 +22,10 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor, } from "./agent-door.js";
|
|
24
24
|
import { runCalAddIcal, runCalDetach, runCalShare, runCalSync } from "./cal-calendars-write.js";
|
|
25
|
+
import { runCalDetail } from "./cal-detail.js";
|
|
25
26
|
import { runCalOverlay } from "./cal-overlay.js";
|
|
26
27
|
import { runCalDelete } from "./cal-delete.js";
|
|
28
|
+
import { runCalDirect } from "./cal-direct.js";
|
|
27
29
|
import { runCalSyncStatus } from "./cal-sync-status.js";
|
|
28
30
|
import { writeLine } from "./cli-io.js";
|
|
29
31
|
import { cacheAgeLabel, cacheCoverage, readThroughCache } from "./read-cache.js";
|
|
@@ -33,6 +35,13 @@ const WRITE_DEADLINE_MS = 60_000;
|
|
|
33
35
|
export async function runCal(command, io) {
|
|
34
36
|
const door = await openAgentDoor("cal", command, io);
|
|
35
37
|
switch (command.action) {
|
|
38
|
+
case "event":
|
|
39
|
+
case "export":
|
|
40
|
+
return runCalDetail(command, door);
|
|
41
|
+
case "duplicate":
|
|
42
|
+
case "batch":
|
|
43
|
+
case "undo":
|
|
44
|
+
return runCalDirect(command, door);
|
|
36
45
|
case "conflicts":
|
|
37
46
|
case "calendar-change":
|
|
38
47
|
return runCalOverlay(command, door);
|
|
@@ -95,6 +104,19 @@ function windowQuery(command) {
|
|
|
95
104
|
return query;
|
|
96
105
|
}
|
|
97
106
|
async function window(command, door) {
|
|
107
|
+
const answer = await askForWindow(command, door);
|
|
108
|
+
if (!answer.ok)
|
|
109
|
+
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
110
|
+
const body = answer.body;
|
|
111
|
+
if (door.json)
|
|
112
|
+
return emitAgentDoor(door, { ok: true, ...body, ...cacheCoverage(answer) });
|
|
113
|
+
if (answer.fromCache) {
|
|
114
|
+
writeLine(door.io.stderr, `(${cacheAgeLabel(answer.ageMs ?? 0)}, refreshing in the background)`);
|
|
115
|
+
}
|
|
116
|
+
writeWindow(body, door);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
async function askForWindow(command, door) {
|
|
98
120
|
// `--from`/`--to` beat a named window, on any of the three verbs: somebody
|
|
99
121
|
// who named two dates meant them.
|
|
100
122
|
const path = command.from && command.to ? "events" : command.action;
|
|
@@ -109,33 +131,27 @@ async function window(command, door) {
|
|
|
109
131
|
timeoutMs: READ_DEADLINE_MS,
|
|
110
132
|
warmRoute: cached,
|
|
111
133
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const body = answer.body;
|
|
127
|
-
if (door.json)
|
|
128
|
-
return emitAgentDoor(door, { ok: true, ...body, ...cacheCoverage(answer) });
|
|
129
|
-
if (answer.fromCache) {
|
|
130
|
-
writeLine(door.io.stderr, `(${cacheAgeLabel(answer.ageMs ?? 0)}, refreshing in the background)`);
|
|
131
|
-
}
|
|
134
|
+
if (!cached)
|
|
135
|
+
return { ...(await ask()), fromCache: false, ageMs: null };
|
|
136
|
+
return readThroughCache({
|
|
137
|
+
io: door.io,
|
|
138
|
+
homeDir: door.homeDir,
|
|
139
|
+
deviceToken: door.deviceToken,
|
|
140
|
+
surface: "cal",
|
|
141
|
+
label: "cal today",
|
|
142
|
+
path: apiPath,
|
|
143
|
+
json: door.json,
|
|
144
|
+
ask,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function writeWindow(body, door) {
|
|
132
148
|
const zone = body.window?.time_zone ?? localTimeZone();
|
|
133
149
|
const events = body.events ?? [];
|
|
134
150
|
if (events.length === 0) {
|
|
135
151
|
writeLine(door.io.stdout, (body.calendars ?? []).length === 0
|
|
136
152
|
? "No calendars attached yet. `cockpit cal add-ical` for a personal one, or connect Google in the browser."
|
|
137
153
|
: `Nothing on ${describeWindow(body, zone)}.`);
|
|
138
|
-
return
|
|
154
|
+
return;
|
|
139
155
|
}
|
|
140
156
|
writeLine(door.io.stdout, describeWindow(body, zone));
|
|
141
157
|
writeLine(door.io.stdout, "");
|
|
@@ -156,7 +172,6 @@ async function window(command, door) {
|
|
|
156
172
|
for (const note of body.notes ?? []) {
|
|
157
173
|
writeLine(door.io.stdout, `Note: "${note.title}" repeats on a rule Tower does not expand (${note.detail.join(", ")}), so only its first occurrence is shown.`);
|
|
158
174
|
}
|
|
159
|
-
return 0;
|
|
160
175
|
}
|
|
161
176
|
function describeWindow(body, zone) {
|
|
162
177
|
if (!body.window)
|
|
@@ -289,6 +304,8 @@ async function create(command, door) {
|
|
|
289
304
|
...(command.allDay ? { all_day: true } : {}),
|
|
290
305
|
...(command.attendees.length > 0 ? { attendees: command.attendees } : {}),
|
|
291
306
|
...(command.meet ? { meet: true } : {}),
|
|
307
|
+
...(command.idempotencyKey ? { idempotency_key: command.idempotencyKey } : {}),
|
|
308
|
+
...(command.reason ? { reason: command.reason } : {}),
|
|
292
309
|
time_zone: command.timeZone ?? localTimeZone(),
|
|
293
310
|
},
|
|
294
311
|
});
|
|
@@ -297,6 +314,10 @@ async function create(command, door) {
|
|
|
297
314
|
const event = answer.body.event;
|
|
298
315
|
if (door.json)
|
|
299
316
|
return emitAgentDoor(door, { ok: true, event });
|
|
317
|
+
writeCreated(command, door, event);
|
|
318
|
+
return 0;
|
|
319
|
+
}
|
|
320
|
+
function writeCreated(command, door, event) {
|
|
300
321
|
const zone = command.timeZone ?? localTimeZone();
|
|
301
322
|
writeLine(door.io.stdout, `Created "${event?.title ?? command.title}" on ${event ? `${dayLabel(event.starts_at, zone)} ${clock(event.starts_at, zone)}` : "that calendar"}.`);
|
|
302
323
|
if (event?.event_id) {
|
|
@@ -317,7 +338,6 @@ async function create(command, door) {
|
|
|
317
338
|
if (command.attendees.length > 0) {
|
|
318
339
|
writeLine(door.io.stdout, `Invitations went to ${command.attendees.join(", ")} from Google.`);
|
|
319
340
|
}
|
|
320
|
-
return 0;
|
|
321
341
|
}
|
|
322
342
|
function meetingLinkText(event) {
|
|
323
343
|
const label = { meet: "Meet link", zoom: "Zoom link", teams: "Teams link" }[event.meeting_kind ?? ""] ?? "Meeting link";
|
|
@@ -327,11 +347,28 @@ function meetingLinkText(event) {
|
|
|
327
347
|
}
|
|
328
348
|
async function changeEvent(command, door) {
|
|
329
349
|
const answer = await askAgentDoor(door, { path: `/api/cal/events/${encodeURIComponent(command.subjectId)}`, method: "PATCH", label: "cal change", timeoutMs: WRITE_DEADLINE_MS,
|
|
330
|
-
body: {
|
|
350
|
+
body: {
|
|
351
|
+
...(command.accountName ? { account: command.accountName } : {}),
|
|
352
|
+
...(command.title ? { title: command.title } : {}),
|
|
353
|
+
...(command.startsAt ? { starts_at: command.startsAt } : {}),
|
|
354
|
+
...(command.endsAt ? { ends_at: command.endsAt } : {}),
|
|
355
|
+
...(command.location ? { location: command.location } : {}),
|
|
356
|
+
// BLI-21296: all-day conversion either way, and the write controls.
|
|
357
|
+
...(command.allDay ? { all_day: true } : command.timed ? { all_day: false } : {}),
|
|
358
|
+
...(command.timeZone && (command.allDay || command.timed) ? { time_zone: command.timeZone } : {}),
|
|
359
|
+
...(command.expectedVersion ? { expected_version: command.expectedVersion } : {}),
|
|
360
|
+
...(command.idempotencyKey ? { idempotency_key: command.idempotencyKey } : {}),
|
|
361
|
+
...(command.reason ? { reason: command.reason } : {}),
|
|
362
|
+
} });
|
|
331
363
|
if (!answer.ok)
|
|
332
364
|
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
333
365
|
if (door.json)
|
|
334
366
|
return emitAgentDoor(door, { ok: true, ...answer.body });
|
|
335
|
-
|
|
367
|
+
const changed = answer.body;
|
|
368
|
+
writeLine(door.io.stdout, `Changed event in ${changed.event?.account_name ?? "its account"}.`);
|
|
369
|
+
if (changed.change_id)
|
|
370
|
+
writeLine(door.io.stdout, `Change: ${changed.change_id} (undo with: cockpit cal undo ${changed.change_id})`);
|
|
371
|
+
if (changed.version)
|
|
372
|
+
writeLine(door.io.stdout, `Version: ${changed.version}`);
|
|
336
373
|
return 0;
|
|
337
374
|
}
|
package/dist/commands/clean.js
CHANGED
|
@@ -83,11 +83,14 @@ export async function runClean(command, io) {
|
|
|
83
83
|
writeLine(io.stdout, cleanJson(footprint, plan, vaults, pruned, reconciled));
|
|
84
84
|
return pruned.status === "fail" ? 1 : 0;
|
|
85
85
|
}
|
|
86
|
+
writeFreedSummary(io, pruned, removedVaults);
|
|
87
|
+
return pruned.status === "fail" ? 1 : 0;
|
|
88
|
+
}
|
|
89
|
+
function writeFreedSummary(io, pruned, removedVaults) {
|
|
86
90
|
writeLine(io.stdout, `Freed ${mb(pruned.deleted_bytes)} MB across ${pruned.deleted_files} file(s) and ${pruned.removed_packs} pack(s)${removedVaults > 0 ? `, plus ${removedVaults} evidence vault(s)` : ""}.`);
|
|
87
91
|
if (pruned.cap_blocked_by_uncommitted) {
|
|
88
92
|
writeLine(io.stdout, `Still ${mb(pruned.bytes_after)} MB over a ${mb(pruned.cap_bytes)} MB cap, held by ${pruned.cap_blocked_count} object(s) the upload ledger cannot vouch for. Run \`cockpit sync\` to try delivering them; nothing here will delete them.`);
|
|
89
93
|
}
|
|
90
|
-
return pruned.status === "fail" ? 1 : 0;
|
|
91
94
|
}
|
|
92
95
|
/** The bucket table a person reads before anything is deleted. */
|
|
93
96
|
export function cleanLines(footprint, plan, vaults, command, logArchives = []) {
|
|
@@ -166,8 +166,11 @@ export async function readDoc(command, door) {
|
|
|
166
166
|
writeDocumentBlock(door.io, result.document);
|
|
167
167
|
return 0;
|
|
168
168
|
}
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
return reportSeveralDocuments(door, results);
|
|
170
|
+
}
|
|
171
|
+
// Several refs: `{ ok, documents: [{ ref, ok, document|error }] }` — one bad
|
|
172
|
+
// ref never loses the rest of the batch.
|
|
173
|
+
function reportSeveralDocuments(door, results) {
|
|
171
174
|
const failed = results.filter((result) => !result.ok);
|
|
172
175
|
if (door.json) {
|
|
173
176
|
return emitAgentDoor(door, {
|