@bli-cockpit/cli 0.2.119 → 0.2.122
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/analyze.js +74 -54
- package/dist/commands/brief-rewrite.js +164 -101
- package/dist/commands/brief.js +38 -13
- package/dist/commands/careers.js +81 -9
- package/dist/commands/correct.js +38 -21
- package/dist/commands/docs.js +13 -10
- package/dist/commands/editor.js +59 -30
- package/dist/commands/install-receipts.js +106 -91
- package/dist/commands/local-args-tower-admin.js +4 -0
- package/dist/commands/local-args-tower-cal.js +28 -3
- package/dist/commands/local-args-tower-careers.js +56 -9
- package/dist/commands/local-args-tower-chat.js +55 -28
- package/dist/commands/local-args-tower-docs-msg.js +39 -8
- package/dist/commands/local-args-tower-mail.js +27 -1
- package/dist/commands/local-args-tower-models.js +14 -17
- package/dist/commands/local-args-tower-work.js +37 -6
- package/dist/commands/local-help-commands-tower.js +15 -1
- package/dist/commands/local-help-commands.js +2 -1
- package/dist/commands/local-help.js +1 -1
- package/dist/commands/mcp-stdio-probe.js +92 -73
- package/dist/commands/memory-hook-performance.js +135 -101
- package/dist/commands/memory-install-claude.js +15 -14
- package/dist/commands/memory-install-codex.js +10 -6
- package/dist/commands/memory-install-config.js +5 -4
- package/dist/commands/memory-install-contract.js +56 -10
- package/dist/commands/memory-install-report.js +16 -11
- package/dist/commands/memory-install-skills.js +11 -11
- package/dist/commands/memory-log.js +22 -5
- package/dist/commands/msg.js +11 -5
- package/dist/commands/onboard-setup.js +16 -1
- package/dist/commands/ops-sections.js +89 -0
- package/dist/commands/ops.js +117 -120
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/scout.js +90 -68
- package/dist/commands/session-sync-failures.js +19 -13
- package/dist/commands/session-sync-record.js +53 -52
- package/dist/commands/session-sync-upload.js +15 -11
- package/dist/commands/sessions.js +61 -51
- package/dist/commands/slack.js +90 -61
- package/dist/commands/status.js +53 -41
- package/dist/commands/workbook.js +23 -20
- package/package.json +2 -2
package/dist/commands/analyze.js
CHANGED
|
@@ -4,6 +4,13 @@ import { runSync } from "./sync.js";
|
|
|
4
4
|
import { describeError, isMissingFileFailure } from "../health-detail.js";
|
|
5
5
|
import { DEFAULT_DASHBOARD_URL, getCollectorRuntimePaths, readLocalCollectorSessionFile, } from "../local-state.js";
|
|
6
6
|
export async function runAnalyze(command, io) {
|
|
7
|
+
const sync = await runAnalysisSync(command, io);
|
|
8
|
+
if (sync.exitCode !== 0 || !isUploadedSyncResult(sync.output)) {
|
|
9
|
+
return writeIncompleteSync(command, io, sync);
|
|
10
|
+
}
|
|
11
|
+
return queueAnalysis(command, io, sync);
|
|
12
|
+
}
|
|
13
|
+
async function runAnalysisSync(command, io) {
|
|
7
14
|
const syncStdout = [];
|
|
8
15
|
const syncStderr = [];
|
|
9
16
|
const syncIo = {
|
|
@@ -17,39 +24,33 @@ export async function runAnalyze(command, io) {
|
|
|
17
24
|
json: true,
|
|
18
25
|
};
|
|
19
26
|
const syncExitCode = await runSync(syncCommand, syncIo);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}, null, 2));
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
replayCaptured(io.stderr, syncStderr);
|
|
34
|
-
writeLine(io.stderr, syncExitCode === 0
|
|
35
|
-
? "Analysis was not queued because a fresh upload did not complete."
|
|
36
|
-
: "Analysis was not queued because the latest upload failed.");
|
|
37
|
-
}
|
|
38
|
-
return syncExitCode === 0 ? 1 : syncExitCode;
|
|
27
|
+
return { exitCode: syncExitCode, output: parseCapturedJson(syncStdout), stderr: syncStderr };
|
|
28
|
+
}
|
|
29
|
+
function writeIncompleteSync(command, io, sync) {
|
|
30
|
+
const error = syncFailureMessage(sync);
|
|
31
|
+
if (command.json) {
|
|
32
|
+
writeLine(io.stdout, JSON.stringify({
|
|
33
|
+
status: sync.exitCode === 0 ? "sync_not_completed" : "sync_failed",
|
|
34
|
+
sync: sync.output,
|
|
35
|
+
error,
|
|
36
|
+
}, null, 2));
|
|
39
37
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
38
|
+
else {
|
|
39
|
+
replayCaptured(io.stderr, sync.stderr);
|
|
40
|
+
writeLine(io.stderr, sync.exitCode === 0
|
|
41
|
+
? "Analysis was not queued because a fresh upload did not complete."
|
|
42
|
+
: "Analysis was not queued because the latest upload failed.");
|
|
43
|
+
}
|
|
44
|
+
return sync.exitCode === 0 ? 1 : sync.exitCode;
|
|
45
|
+
}
|
|
46
|
+
function syncFailureMessage(sync) {
|
|
47
|
+
return sync.stderr.join("").trim()
|
|
48
|
+
|| (sync.exitCode === 0
|
|
49
|
+
? "Tower sync did not upload fresh evidence."
|
|
50
|
+
: "Tower sync failed.");
|
|
51
|
+
}
|
|
52
|
+
async function queueAnalysis(command, io, sync) {
|
|
53
|
+
const session = await readAnalyzeSession(command.homeDir);
|
|
53
54
|
const dashboardUrl = normalizeUrl(command.dashboardUrl ?? session.dashboard_url ?? DEFAULT_DASHBOARD_URL);
|
|
54
55
|
const response = await io.fetch(`${dashboardUrl}/api/ambient/analyze`, {
|
|
55
56
|
method: "POST",
|
|
@@ -60,40 +61,59 @@ export async function runAnalyze(command, io) {
|
|
|
60
61
|
body: "{}",
|
|
61
62
|
});
|
|
62
63
|
const body = await readAnalyzeApiResponse(response);
|
|
63
|
-
if (!response.ok)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
replayCaptured(io.stderr, syncStderr);
|
|
81
|
-
writeLine(io.stderr, `Analysis was not queued: ${message}`);
|
|
64
|
+
if (!response.ok)
|
|
65
|
+
return writeFailedAnalysis(command, io, sync, response, body);
|
|
66
|
+
return writeQueuedAnalysis(command, io, sync, dashboardUrl, body);
|
|
67
|
+
}
|
|
68
|
+
async function readAnalyzeSession(homeDir) {
|
|
69
|
+
const paths = getCollectorRuntimePaths(homeDir);
|
|
70
|
+
return readLocalCollectorSessionFile(paths).catch((error) => {
|
|
71
|
+
// "Not signed in" is the right sentence for an absent session file and the
|
|
72
|
+
// wrong one for a corrupt one, which `cockpit login` will not repair
|
|
73
|
+
// (BLI-3238).
|
|
74
|
+
if (!isMissingFileFailure(error)) {
|
|
75
|
+
console.error("[cockpit-analyze] session file present but unreadable, reporting as not signed in", JSON.stringify({
|
|
76
|
+
reason: "session_file_unusable",
|
|
77
|
+
...describeError(error),
|
|
78
|
+
}));
|
|
82
79
|
}
|
|
83
|
-
|
|
80
|
+
throw new Error("Tower is not signed in. Run `cockpit onboard` or `cockpit login` first.");
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function writeFailedAnalysis(command, io, sync, response, body) {
|
|
84
|
+
const message = typeof body.message === "string"
|
|
85
|
+
? body.message
|
|
86
|
+
: `Analysis request failed with HTTP ${response.status}.`;
|
|
87
|
+
const retryAfterSeconds = typeof body.retry_after_seconds === "number" ? body.retry_after_seconds : null;
|
|
88
|
+
if (command.json) {
|
|
89
|
+
writeLine(io.stdout, JSON.stringify({
|
|
90
|
+
status: "analyze_failed",
|
|
91
|
+
sync: sync.output,
|
|
92
|
+
http_status: response.status,
|
|
93
|
+
code: typeof body.code === "string" ? body.code : null,
|
|
94
|
+
message,
|
|
95
|
+
retry_after_seconds: retryAfterSeconds,
|
|
96
|
+
}, null, 2));
|
|
84
97
|
}
|
|
98
|
+
else {
|
|
99
|
+
replayCaptured(io.stderr, sync.stderr);
|
|
100
|
+
writeLine(io.stderr, `Analysis was not queued: ${message}`);
|
|
101
|
+
}
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
function writeQueuedAnalysis(command, io, sync, dashboardUrl, body) {
|
|
85
105
|
const jobId = typeof body.job?.id === "string" ? body.job.id : null;
|
|
86
106
|
const jobStatus = typeof body.job?.status === "string" ? body.job.status : "pending";
|
|
87
107
|
if (command.json) {
|
|
88
108
|
writeLine(io.stdout, JSON.stringify({
|
|
89
109
|
status: "queued",
|
|
90
|
-
sync:
|
|
110
|
+
sync: sync.output,
|
|
91
111
|
job: body.job ?? null,
|
|
92
112
|
dashboard_url: `${dashboardUrl}/my-work`,
|
|
93
113
|
}, null, 2));
|
|
94
114
|
return 0;
|
|
95
115
|
}
|
|
96
|
-
replayCaptured(io.stderr,
|
|
116
|
+
replayCaptured(io.stderr, sync.stderr);
|
|
97
117
|
writeLine(io.stdout, "Tower uploaded your latest work.");
|
|
98
118
|
writeLine(io.stdout, "Tower analysis queued.");
|
|
99
119
|
if (jobId)
|
|
@@ -42,23 +42,64 @@ export async function runBriefRewrite(command, io, watchOptions = {}) {
|
|
|
42
42
|
const log = (line) => writeLine(io.stderr, line);
|
|
43
43
|
const styled = colorEnabled(io);
|
|
44
44
|
const startedAt = Date.now();
|
|
45
|
+
const target = await readRewriteTarget(command, io, session.device_token, dashboardUrl, log);
|
|
46
|
+
if (!target.ok)
|
|
47
|
+
return target.exitCode;
|
|
48
|
+
const queued = await queueBriefRewrite(command, io, session.device_token, dashboardUrl, log, target.personId, target.displayName);
|
|
49
|
+
if (!queued.ok)
|
|
50
|
+
return queued.exitCode;
|
|
51
|
+
const { answer, request, whose } = queued;
|
|
52
|
+
const scheduled = answer.compileScheduled !== false;
|
|
53
|
+
writeQueueReceipt(io, request.requestId, answer, command.wait !== false);
|
|
54
|
+
if (!scheduled) {
|
|
55
|
+
return reportQueueOnly(command, io, request.requestId, target.personId, answer, whose);
|
|
56
|
+
}
|
|
57
|
+
if (command.wait === false) {
|
|
58
|
+
return reportQueuedRewrite(command, io, request.requestId, target.personId, whose, styled);
|
|
59
|
+
}
|
|
60
|
+
return watchQueuedRewrite({
|
|
61
|
+
command,
|
|
62
|
+
io,
|
|
63
|
+
session,
|
|
64
|
+
dashboardUrl,
|
|
65
|
+
log,
|
|
66
|
+
styled,
|
|
67
|
+
startedAt,
|
|
68
|
+
beforePageId: target.beforePageId,
|
|
69
|
+
personId: target.personId,
|
|
70
|
+
requestId: request.requestId,
|
|
71
|
+
whose,
|
|
72
|
+
watchOptions,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async function readRewriteTarget(command, io, deviceToken, dashboardUrl, log) {
|
|
45
76
|
// Which page, and what it looks like NOW — the "before" the poll compares
|
|
46
77
|
// against. Read first for a second reason: it is the only way this command
|
|
47
78
|
// learns the person id, and it is the same read that decides whether this
|
|
48
79
|
// caller may see that page at all.
|
|
49
|
-
const before = await readPage(command, io,
|
|
80
|
+
const before = await readPage(command, io, deviceToken, dashboardUrl, log);
|
|
50
81
|
if (!before.ok)
|
|
51
|
-
return fail(command, io, before.reason, before.detail);
|
|
82
|
+
return { ok: false, exitCode: fail(command, io, before.reason, before.detail) };
|
|
52
83
|
const personId = before.page.page?.personId;
|
|
53
84
|
if (!personId) {
|
|
54
|
-
return
|
|
55
|
-
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
exitCode: fail(command, io, before.page.error ?? "no_page", before.page.reply ??
|
|
88
|
+
"There is no page here to write again yet. The first one is compiled by the daily job."),
|
|
89
|
+
};
|
|
56
90
|
}
|
|
57
|
-
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
personId,
|
|
94
|
+
beforePageId: before.page.page?.pageId ?? null,
|
|
95
|
+
displayName: before.page.page?.displayName,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function queueBriefRewrite(command, io, deviceToken, dashboardUrl, log, personId, displayName) {
|
|
58
99
|
const queued = await towerJsonRequest({
|
|
59
100
|
dashboardUrl,
|
|
60
101
|
path: "/api/jarvis/recompile",
|
|
61
|
-
deviceToken
|
|
102
|
+
deviceToken,
|
|
62
103
|
fetch: io.fetch,
|
|
63
104
|
label: "brief:rewrite",
|
|
64
105
|
timeoutMs: REQUEST_DEADLINE_MS,
|
|
@@ -66,91 +107,108 @@ export async function runBriefRewrite(command, io, watchOptions = {}) {
|
|
|
66
107
|
body: { personIds: [personId], mode: "queue" },
|
|
67
108
|
});
|
|
68
109
|
if (!queued.ok)
|
|
69
|
-
return fail(command, io, queued.reason, queued.detail);
|
|
110
|
+
return { ok: false, exitCode: fail(command, io, queued.reason, queued.detail) };
|
|
70
111
|
const answer = queued.body;
|
|
71
112
|
const request = (answer.queued ?? [])[0];
|
|
72
113
|
if (!answer.ok || !request?.requestId) {
|
|
73
|
-
return
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
exitCode: fail(command, io, "not_queued", answer.reply ?? "Tower did not accept that ask."),
|
|
117
|
+
};
|
|
74
118
|
}
|
|
75
|
-
|
|
76
|
-
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
answer,
|
|
122
|
+
request: { requestId: request.requestId, name: request.name },
|
|
123
|
+
whose: request.name ?? displayName ?? "that page",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function writeQueueReceipt(io, requestId, answer, waiting) {
|
|
77
127
|
writeLine(io.stderr, `[brief rewrite] queued ${JSON.stringify({
|
|
78
|
-
request_id:
|
|
79
|
-
compile_scheduled:
|
|
128
|
+
request_id: requestId,
|
|
129
|
+
compile_scheduled: answer.compileScheduled !== false,
|
|
80
130
|
compile_reason: answer.compileReason ?? null,
|
|
81
|
-
waiting
|
|
131
|
+
waiting,
|
|
82
132
|
})}`);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
writeLine(io.stdout, sentence);
|
|
101
|
-
}
|
|
102
|
-
return 0;
|
|
133
|
+
}
|
|
134
|
+
function reportQueueOnly(command, io, requestId, personId, answer, whose) {
|
|
135
|
+
// Queue-only, said as queue-only. The ask is a real row; what is missing is
|
|
136
|
+
// somebody to run it in the next minute, and pretending otherwise would
|
|
137
|
+
// leave a person watching a page that was never going to change.
|
|
138
|
+
const sentence = `${whose} is on the list to be written again, but this Tower could not start the work now ` +
|
|
139
|
+
`(${answer.compileReason ?? "no reason given"}). The daily compile will reach it. Nothing is lost.`;
|
|
140
|
+
if (command.json) {
|
|
141
|
+
writeLine(io.stdout, JSON.stringify({
|
|
142
|
+
ok: true,
|
|
143
|
+
outcome: "queued_only",
|
|
144
|
+
requestId,
|
|
145
|
+
personId,
|
|
146
|
+
compileScheduled: false,
|
|
147
|
+
compileReason: answer.compileReason ?? null,
|
|
148
|
+
}));
|
|
103
149
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
150
|
+
else {
|
|
151
|
+
writeLine(io.stdout, sentence);
|
|
152
|
+
}
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
function reportQueuedRewrite(command, io, requestId, personId, whose, styled) {
|
|
156
|
+
if (command.json) {
|
|
157
|
+
writeLine(io.stdout, JSON.stringify({
|
|
158
|
+
ok: true,
|
|
159
|
+
outcome: "queued",
|
|
160
|
+
requestId,
|
|
161
|
+
personId,
|
|
162
|
+
compileScheduled: true,
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
writeLine(io.stdout, `Asked. ${whose} is being written again.`);
|
|
167
|
+
writeLine(io.stdout, dim("Run `cockpit brief` in a few minutes to read it.", styled));
|
|
119
168
|
}
|
|
120
|
-
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
async function watchQueuedRewrite(input) {
|
|
172
|
+
writeLine(input.io.stderr, dim(`Writing ${input.whose} again. This takes a few minutes; watching for the new version…`, input.styled));
|
|
121
173
|
const watched = await watchForNewVersion({
|
|
122
|
-
command,
|
|
123
|
-
io,
|
|
124
|
-
deviceToken: session.device_token,
|
|
125
|
-
dashboardUrl,
|
|
126
|
-
beforePageId,
|
|
127
|
-
log,
|
|
128
|
-
pollEveryMs: watchOptions.pollEveryMs ?? POLL_EVERY_MS,
|
|
129
|
-
ceilingMs: watchOptions.ceilingMs ?? WATCH_CEILING_MS,
|
|
174
|
+
command: input.command,
|
|
175
|
+
io: input.io,
|
|
176
|
+
deviceToken: input.session.device_token,
|
|
177
|
+
dashboardUrl: input.dashboardUrl,
|
|
178
|
+
beforePageId: input.beforePageId,
|
|
179
|
+
log: input.log,
|
|
180
|
+
pollEveryMs: input.watchOptions.pollEveryMs ?? POLL_EVERY_MS,
|
|
181
|
+
ceilingMs: input.watchOptions.ceilingMs ?? WATCH_CEILING_MS,
|
|
130
182
|
});
|
|
131
183
|
if (watched.outcome === "written") {
|
|
132
|
-
|
|
133
|
-
writeLine(io.stdout, JSON.stringify({
|
|
134
|
-
ok: true,
|
|
135
|
-
outcome: "written",
|
|
136
|
-
requestId: request.requestId,
|
|
137
|
-
personId,
|
|
138
|
-
pageId: watched.pageId,
|
|
139
|
-
waitedMs: watched.waitedMs,
|
|
140
|
-
}));
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
writeLine(io.stdout, `${whose} has been written again.`);
|
|
144
|
-
writeLine(io.stdout, dim("Run `cockpit brief` to read it.", styled));
|
|
145
|
-
}
|
|
146
|
-
writeLine(io.stderr, `[brief rewrite] written ${JSON.stringify({
|
|
147
|
-
request_id: request.requestId,
|
|
148
|
-
polls: watched.polls,
|
|
149
|
-
waited_ms: watched.waitedMs,
|
|
150
|
-
elapsed_ms: Date.now() - startedAt,
|
|
151
|
-
})}`);
|
|
152
|
-
return 0;
|
|
184
|
+
return reportWrittenRewrite(input, watched);
|
|
153
185
|
}
|
|
186
|
+
return reportUnconfirmedRewrite(input.command, input.io, input.requestId, input.personId, input.whose, watched);
|
|
187
|
+
}
|
|
188
|
+
function reportWrittenRewrite(input, watched) {
|
|
189
|
+
if (input.command.json) {
|
|
190
|
+
writeLine(input.io.stdout, JSON.stringify({
|
|
191
|
+
ok: true,
|
|
192
|
+
outcome: "written",
|
|
193
|
+
requestId: input.requestId,
|
|
194
|
+
personId: input.personId,
|
|
195
|
+
pageId: watched.pageId,
|
|
196
|
+
waitedMs: watched.waitedMs,
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
writeLine(input.io.stdout, `${input.whose} has been written again.`);
|
|
201
|
+
writeLine(input.io.stdout, dim("Run `cockpit brief` to read it.", input.styled));
|
|
202
|
+
}
|
|
203
|
+
writeLine(input.io.stderr, `[brief rewrite] written ${JSON.stringify({
|
|
204
|
+
request_id: input.requestId,
|
|
205
|
+
polls: watched.polls,
|
|
206
|
+
waited_ms: watched.waitedMs,
|
|
207
|
+
elapsed_ms: Date.now() - input.startedAt,
|
|
208
|
+
})}`);
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
function reportUnconfirmedRewrite(command, io, requestId, personId, whose, watched) {
|
|
154
212
|
// Not a failure, and named so it cannot be read as one. Nothing observed here
|
|
155
213
|
// says the compile stopped — only that this command stopped watching.
|
|
156
214
|
const sentence = watched.outcome === "watch_failed"
|
|
@@ -163,7 +221,7 @@ export async function runBriefRewrite(command, io, watchOptions = {}) {
|
|
|
163
221
|
writeLine(io.stdout, JSON.stringify({
|
|
164
222
|
ok: false,
|
|
165
223
|
outcome: watched.outcome,
|
|
166
|
-
requestId
|
|
224
|
+
requestId,
|
|
167
225
|
personId,
|
|
168
226
|
waitedMs: watched.waitedMs,
|
|
169
227
|
...(watched.outcome === "watch_failed" ? { detail: watched.detail } : {}),
|
|
@@ -173,7 +231,7 @@ export async function runBriefRewrite(command, io, watchOptions = {}) {
|
|
|
173
231
|
writeLine(io.stderr, sentence);
|
|
174
232
|
}
|
|
175
233
|
writeLine(io.stderr, `[brief rewrite] not confirmed ${JSON.stringify({
|
|
176
|
-
request_id:
|
|
234
|
+
request_id: requestId,
|
|
177
235
|
reason: watched.outcome,
|
|
178
236
|
polls: watched.polls,
|
|
179
237
|
waited_ms: watched.waitedMs,
|
|
@@ -194,30 +252,35 @@ async function watchForNewVersion(input) {
|
|
|
194
252
|
while (Date.now() - startedAt < input.ceilingMs) {
|
|
195
253
|
await sleep(input.pollEveryMs);
|
|
196
254
|
polls += 1;
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// A single blip while waiting is not an answer about the compile. Only a
|
|
201
|
-
// refusal is, and that one stops the watch rather than spinning.
|
|
202
|
-
if (read.httpStatus === 403 || read.httpStatus === 401) {
|
|
203
|
-
return { outcome: "watch_failed", detail: read.detail, polls, waitedMs };
|
|
204
|
-
}
|
|
205
|
-
input.log(`[brief rewrite] poll failed ${JSON.stringify({ poll: polls, reason: read.reason })}`);
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
const pageId = read.page.page?.pageId ?? null;
|
|
209
|
-
input.log(`[brief rewrite] polled ${JSON.stringify({
|
|
210
|
-
poll: polls,
|
|
211
|
-
changed: pageId != null && pageId !== input.beforePageId,
|
|
212
|
-
compiled_at: read.page.page?.compiledAt ?? null,
|
|
213
|
-
waited_ms: waitedMs,
|
|
214
|
-
})}`);
|
|
215
|
-
if (pageId && pageId !== input.beforePageId) {
|
|
216
|
-
return { outcome: "written", pageId, polls, waitedMs };
|
|
217
|
-
}
|
|
255
|
+
const result = await readNextPageVersion(input, polls, startedAt);
|
|
256
|
+
if (result)
|
|
257
|
+
return result;
|
|
218
258
|
}
|
|
219
259
|
return { outcome: "still_running", polls, waitedMs: Date.now() - startedAt };
|
|
220
260
|
}
|
|
261
|
+
async function readNextPageVersion(input, polls, startedAt) {
|
|
262
|
+
const read = await readPage(input.command, input.io, input.deviceToken, input.dashboardUrl, input.log);
|
|
263
|
+
const waitedMs = Date.now() - startedAt;
|
|
264
|
+
if (!read.ok) {
|
|
265
|
+
// A single blip while waiting is not an answer about the compile. Only a
|
|
266
|
+
// refusal is, and that one stops the watch rather than spinning.
|
|
267
|
+
if (read.httpStatus === 403 || read.httpStatus === 401) {
|
|
268
|
+
return { outcome: "watch_failed", detail: read.detail, polls, waitedMs };
|
|
269
|
+
}
|
|
270
|
+
input.log(`[brief rewrite] poll failed ${JSON.stringify({ poll: polls, reason: read.reason })}`);
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
const pageId = read.page.page?.pageId ?? null;
|
|
274
|
+
input.log(`[brief rewrite] polled ${JSON.stringify({
|
|
275
|
+
poll: polls,
|
|
276
|
+
changed: pageId != null && pageId !== input.beforePageId,
|
|
277
|
+
compiled_at: read.page.page?.compiledAt ?? null,
|
|
278
|
+
waited_ms: waitedMs,
|
|
279
|
+
})}`);
|
|
280
|
+
return pageId && pageId !== input.beforePageId
|
|
281
|
+
? { outcome: "written", pageId, polls, waitedMs }
|
|
282
|
+
: null;
|
|
283
|
+
}
|
|
221
284
|
async function readPage(command, io, deviceToken, dashboardUrl, log) {
|
|
222
285
|
const params = new URLSearchParams({ tldr: "1" });
|
|
223
286
|
if (command.subject)
|
package/dist/commands/brief.js
CHANGED
|
@@ -23,10 +23,18 @@ export async function runBrief(command, io) {
|
|
|
23
23
|
if (command.action === "status") {
|
|
24
24
|
return runBriefStatus(command, io, dashboardUrl, session.device_token, startedAt);
|
|
25
25
|
}
|
|
26
|
+
const body = await requestBrief(command, io, dashboardUrl, session.device_token);
|
|
27
|
+
if (!body)
|
|
28
|
+
return 1;
|
|
29
|
+
writeBriefReply(command, io, body);
|
|
30
|
+
logBriefRead(command, io, body, startedAt);
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
async function requestBrief(command, io, dashboardUrl, deviceToken) {
|
|
26
34
|
const result = await towerJsonRequest({
|
|
27
35
|
dashboardUrl,
|
|
28
36
|
path: `/api/jarvis/brief${queryFor(command)}`,
|
|
29
|
-
deviceToken
|
|
37
|
+
deviceToken,
|
|
30
38
|
fetch: io.fetch,
|
|
31
39
|
method: "GET",
|
|
32
40
|
label: "brief",
|
|
@@ -39,20 +47,25 @@ export async function runBrief(command, io) {
|
|
|
39
47
|
// on the page ("Nothing has been written for you yet."). Relayed verbatim
|
|
40
48
|
// rather than translated into a code.
|
|
41
49
|
writeFailure(command, io, result.reason, result.detail);
|
|
42
|
-
return
|
|
50
|
+
return undefined;
|
|
43
51
|
}
|
|
44
52
|
const body = result.body;
|
|
45
53
|
if (!body.ok || typeof body.text !== "string") {
|
|
46
54
|
const detail = body.reply ?? body.error ?? "Tower answered without a page.";
|
|
47
55
|
writeFailure(command, io, body.error ?? "no_page", detail);
|
|
48
|
-
return
|
|
56
|
+
return undefined;
|
|
49
57
|
}
|
|
58
|
+
return body;
|
|
59
|
+
}
|
|
60
|
+
function writeBriefReply(command, io, body) {
|
|
50
61
|
if (command.json) {
|
|
51
62
|
writeLine(io.stdout, JSON.stringify(body));
|
|
52
63
|
}
|
|
53
64
|
else {
|
|
54
65
|
writeHuman(io, command, body);
|
|
55
66
|
}
|
|
67
|
+
}
|
|
68
|
+
function logBriefRead(command, io, body, startedAt) {
|
|
56
69
|
writeLine(io.stderr, `[brief cli] read ${JSON.stringify({
|
|
57
70
|
text_length: body.text.length,
|
|
58
71
|
cadence: body.page?.cadence ?? null,
|
|
@@ -70,7 +83,6 @@ export async function runBrief(command, io) {
|
|
|
70
83
|
claim_count: body.claims?.length ?? null,
|
|
71
84
|
elapsed_ms: Date.now() - startedAt,
|
|
72
85
|
})}`);
|
|
73
|
-
return 0;
|
|
74
86
|
}
|
|
75
87
|
/**
|
|
76
88
|
* `cockpit brief status` (BLI-3462) — why a brief was or was not delivered.
|
|
@@ -157,15 +169,7 @@ function writeHuman(io, command, body) {
|
|
|
157
169
|
// Decided once per page: colour only for a real terminal that has not said
|
|
158
170
|
// NO_COLOR, so `cockpit brief > page.txt` is text and nothing else (BLI-3482).
|
|
159
171
|
const styled = colorEnabled(io);
|
|
160
|
-
|
|
161
|
-
// Whose page, and — when it is not the newest — that it is a record of a
|
|
162
|
-
// moment rather than something that failed to update.
|
|
163
|
-
const whose = page.displayName ? `${page.displayName}'s page` : "This page";
|
|
164
|
-
const pinned = page.olderVersionLabel ? ` · version from ${page.olderVersionLabel}` : "";
|
|
165
|
-
// BLI-3484: which day, in the SUBJECT's zone, said with the zone beside it so
|
|
166
|
-
// nobody has to work out whose midnight this is.
|
|
167
|
-
const day = body.day?.date ? ` · ${body.day.date}${body.day.zone ? ` ${body.day.zone}` : ""}` : "";
|
|
168
|
-
writeLine(io.stdout, dim(`${whose}${pinned}${day}`, styled));
|
|
172
|
+
writeBriefHeading(io, body, styled);
|
|
169
173
|
// `history` lists days and prints no page — a list of dates under a full
|
|
170
174
|
// brief would bury the thing somebody asked for.
|
|
171
175
|
if (command.action === "history") {
|
|
@@ -176,6 +180,22 @@ function writeHuman(io, command, body) {
|
|
|
176
180
|
writeLine(io.stdout, body.text ?? "");
|
|
177
181
|
if (command.delta)
|
|
178
182
|
writeDelta(io, body, styled);
|
|
183
|
+
writeBriefClaims(io, body, styled);
|
|
184
|
+
writeBriefVersions(io, body, styled);
|
|
185
|
+
writeOtherBriefPages(io, command, body, styled);
|
|
186
|
+
}
|
|
187
|
+
function writeBriefHeading(io, body, styled) {
|
|
188
|
+
const page = body.page ?? {};
|
|
189
|
+
// Whose page, and — when it is not the newest — that it is a record of a
|
|
190
|
+
// moment rather than something that failed to update.
|
|
191
|
+
const whose = page.displayName ? `${page.displayName}'s page` : "This page";
|
|
192
|
+
const pinned = page.olderVersionLabel ? ` · version from ${page.olderVersionLabel}` : "";
|
|
193
|
+
// BLI-3484: which day, in the SUBJECT's zone, said with the zone beside it so
|
|
194
|
+
// nobody has to work out whose midnight this is.
|
|
195
|
+
const day = body.day?.date ? ` · ${body.day.date}${body.day.zone ? ` ${body.day.zone}` : ""}` : "";
|
|
196
|
+
writeLine(io.stdout, dim(`${whose}${pinned}${day}`, styled));
|
|
197
|
+
}
|
|
198
|
+
function writeBriefClaims(io, body, styled) {
|
|
179
199
|
if (body.claims && body.claims.length > 0) {
|
|
180
200
|
writeLine(io.stdout, "");
|
|
181
201
|
writeLine(io.stdout, dim("Claim ids — pass one to `cockpit correct --claim`:", styled));
|
|
@@ -183,6 +203,8 @@ function writeHuman(io, command, body) {
|
|
|
183
203
|
writeLine(io.stdout, `${dim(` [${claim.claimId}]`, styled)} ${claim.text ?? ""}`);
|
|
184
204
|
}
|
|
185
205
|
}
|
|
206
|
+
}
|
|
207
|
+
function writeBriefVersions(io, body, styled) {
|
|
186
208
|
if (body.versions) {
|
|
187
209
|
writeLine(io.stdout, "");
|
|
188
210
|
if (body.versions.length === 0) {
|
|
@@ -198,7 +220,10 @@ function writeHuman(io, command, body) {
|
|
|
198
220
|
}
|
|
199
221
|
}
|
|
200
222
|
}
|
|
223
|
+
}
|
|
224
|
+
function writeOtherBriefPages(io, command, body, styled) {
|
|
201
225
|
if (!command.subject && body.people && body.people.length > 1) {
|
|
226
|
+
const page = body.page ?? {};
|
|
202
227
|
const others = body.people
|
|
203
228
|
.filter((person) => person.personId !== page.personId)
|
|
204
229
|
.map((person) => person.slug)
|