@bli-cockpit/cli 0.2.48 → 0.2.49

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.
Files changed (39) hide show
  1. package/dist/adapters/raw-evidence-attribution-gaps.js +133 -0
  2. package/dist/adapters/raw-evidence.js +360 -349
  3. package/dist/autostart-contract.js +79 -0
  4. package/dist/autostart-darwin-plist.js +265 -0
  5. package/dist/autostart-darwin.js +171 -0
  6. package/dist/autostart-windows-scripts.js +310 -0
  7. package/dist/autostart-windows-task-xml.js +260 -0
  8. package/dist/autostart-windows.js +237 -0
  9. package/dist/autostart-xml.js +23 -0
  10. package/dist/autostart.js +35 -1148
  11. package/dist/commands/agent-rules-command.js +55 -0
  12. package/dist/commands/agent-session-report.js +290 -0
  13. package/dist/commands/analyze.js +131 -0
  14. package/dist/commands/autostart-command.js +105 -0
  15. package/dist/commands/backfill.js +824 -551
  16. package/dist/commands/cli-io.js +13 -0
  17. package/dist/commands/jarvis.js +179 -3
  18. package/dist/commands/local-arg-values.js +169 -0
  19. package/dist/commands/local-args-collector.js +578 -0
  20. package/dist/commands/local-args-tower.js +870 -0
  21. package/dist/commands/local-args.js +8 -1549
  22. package/dist/commands/local-help.js +11 -3
  23. package/dist/commands/local.js +18 -1786
  24. package/dist/commands/login.js +53 -0
  25. package/dist/commands/logout.js +66 -0
  26. package/dist/commands/onboard-receipts.js +66 -0
  27. package/dist/commands/onboard-report.js +274 -0
  28. package/dist/commands/onboard.js +449 -0
  29. package/dist/commands/ops-render.js +36 -0
  30. package/dist/commands/public-root.js +1 -1
  31. package/dist/commands/serve.js +13 -0
  32. package/dist/commands/session-sync.js +513 -534
  33. package/dist/commands/settings-render.js +28 -0
  34. package/dist/commands/settings.js +66 -2
  35. package/dist/commands/start.js +47 -0
  36. package/dist/commands/sync-followups.js +203 -0
  37. package/dist/commands/sync.js +381 -0
  38. package/dist/tower-stream.js +20 -4
  39. package/package.json +1 -1
@@ -0,0 +1,237 @@
1
+ import os from "node:os";
2
+ import { AUTOSTART_LABEL, DEFAULT_AUTOSTART_INTERVAL_SECONDS, resolveAutostartSettings, schedulerNodeExecutable, WINDOWS_AUTOSTART_TASK_NAME, } from "./autostart-contract.js";
3
+ import { removeWindowsAutostartScripts, syncLauncherProblem, syncScriptProblem, windowsAutostartLauncherPath, windowsAutostartRegistrationScriptPath, windowsAutostartScriptPath, windowsWScriptPath, writeWindowsAutostartScripts, } from "./autostart-windows-scripts.js";
4
+ import { windowsTaskRegistrationProblems } from "./autostart-windows-task-xml.js";
5
+ /**
6
+ * Installs (or repairs) the scheduled task that runs `cockpit sync` every
7
+ * `intervalSeconds`, then reads the registration back to say whether Windows
8
+ * actually agrees — a rewrite that "succeeded" and still disagrees is the
9
+ * BLI-2996 failure and gets its own log line.
10
+ */
11
+ export async function installWindowsTask(options) {
12
+ const homeDir = options.homeDir ?? os.homedir();
13
+ const settings = resolveAutostartSettings(options, "win32");
14
+ const intervalMinutes = Math.max(1, Math.ceil(settings.interval_seconds / 60));
15
+ const node = await schedulerNodeExecutable(options, "win32");
16
+ const written = await writeWindowsAutostartScripts({
17
+ homeDir,
18
+ workDirs: settings.work_dirs,
19
+ dashboardUrl: settings.dashboard_url,
20
+ nodeExecutable: node.path,
21
+ cliEntryPoint: settings.cli_entry_point,
22
+ intervalMinutes,
23
+ });
24
+ const created = await runWindowsRegistrationScript(options.exec, written.registration_path);
25
+ if (created.code !== 0)
26
+ reportRegistrationRewriteFailed(created);
27
+ const verified = created.code === 0
28
+ ? await windowsTaskStatus({
29
+ ...options,
30
+ repoRoot: settings.work_dir,
31
+ repoRoots: settings.work_dirs,
32
+ dashboardUrl: settings.dashboard_url,
33
+ intervalSeconds: intervalMinutes * 60,
34
+ nodeExecutable: node.path,
35
+ cliEntryPoint: settings.cli_entry_point,
36
+ })
37
+ : null;
38
+ const loaded = verified?.status === "loaded";
39
+ if (created.code === 0 && loaded)
40
+ reportRepairConverged();
41
+ return {
42
+ status: "installed",
43
+ label: AUTOSTART_LABEL,
44
+ plist_path: written.script_path,
45
+ task_name: WINDOWS_AUTOSTART_TASK_NAME,
46
+ registration_path: written.registration_path,
47
+ loaded,
48
+ interval_seconds: intervalMinutes * 60,
49
+ work_dir: settings.work_dir,
50
+ work_dirs: settings.work_dirs,
51
+ dashboard_url: settings.dashboard_url,
52
+ ...(loaded ? {} : { message: installFailureMessage(created, verified) }),
53
+ };
54
+ }
55
+ /**
56
+ * Why the install did not converge, in the operator's own terms: the rewrite's
57
+ * own failure when it never ran, otherwise what the read-back objected to.
58
+ */
59
+ function installFailureMessage(created, verified) {
60
+ if (created.code !== 0) {
61
+ return `PowerShell task registration exited ${created.code}: ${created.stderr.trim() || created.stdout.trim() || "unknown error"}`;
62
+ }
63
+ return verified?.message ?? "Windows task registration could not be verified";
64
+ }
65
+ /**
66
+ * Deletes the scheduled task and the three files it ran. A task that was never
67
+ * registered still gets its files removed, so the command is safe to repeat.
68
+ */
69
+ export async function uninstallWindowsTask(options) {
70
+ const scriptPath = windowsAutostartScriptPath(options.homeDir);
71
+ const registrationPath = windowsAutostartRegistrationScriptPath(options.homeDir);
72
+ const current = await windowsTaskStatus(options);
73
+ if (current.status === "absent") {
74
+ await removeWindowsAutostartScripts(options.homeDir);
75
+ return {
76
+ ...current,
77
+ plist_path: scriptPath,
78
+ registration_path: registrationPath,
79
+ };
80
+ }
81
+ const removed = await options.exec("schtasks.exe", [
82
+ "/Delete",
83
+ "/TN",
84
+ WINDOWS_AUTOSTART_TASK_NAME,
85
+ "/F",
86
+ ]);
87
+ if (removed.code === 0) {
88
+ await removeWindowsAutostartScripts(options.homeDir);
89
+ }
90
+ return {
91
+ status: removed.code === 0 ? "uninstalled" : "not_loaded",
92
+ label: AUTOSTART_LABEL,
93
+ plist_path: scriptPath,
94
+ task_name: WINDOWS_AUTOSTART_TASK_NAME,
95
+ registration_path: registrationPath,
96
+ ...(removed.code === 0
97
+ ? {}
98
+ : {
99
+ message: `schtasks /Delete exited ${removed.code}: ${removed.stderr.trim() || removed.stdout.trim() || "unknown error"}`,
100
+ }),
101
+ };
102
+ }
103
+ /**
104
+ * Whether Windows still has the task, still has the files it runs, and still
105
+ * agrees with the roots and runtime this build would register.
106
+ */
107
+ export async function windowsTaskStatus(options) {
108
+ const scriptPath = windowsAutostartScriptPath(options.homeDir);
109
+ const query = await queryWindowsTaskXml(options.exec);
110
+ if (query.code !== 0)
111
+ return windowsTaskQueryFailure(query, scriptPath);
112
+ const intervalMinutes = Math.max(1, Math.ceil((options.intervalSeconds ?? DEFAULT_AUTOSTART_INTERVAL_SECONDS) / 60));
113
+ const problems = [
114
+ ...(isTaskDisabled(query.stdout) ? ["task is disabled"] : []),
115
+ ...windowsTaskRegistrationProblems(query.stdout, {
116
+ launcherPath: windowsAutostartLauncherPath(options.homeDir),
117
+ intervalMinutes,
118
+ wscriptPath: windowsWScriptPath(),
119
+ }),
120
+ ...(await writtenScriptProblems(options)),
121
+ ];
122
+ reportWindowsValidation(problems, intervalMinutes);
123
+ return {
124
+ status: problems.length > 0 ? "not_loaded" : "loaded",
125
+ label: AUTOSTART_LABEL,
126
+ plist_path: scriptPath,
127
+ task_name: WINDOWS_AUTOSTART_TASK_NAME,
128
+ ...(problems.length > 0
129
+ ? { message: `Windows task needs repair: ${problems.join("; ")}` }
130
+ : {}),
131
+ };
132
+ }
133
+ /** The sync script first, then its launcher — the order an operator reads them
134
+ * in the repair message. */
135
+ async function writtenScriptProblems(options) {
136
+ const problems = [];
137
+ const scriptProblem = await syncScriptProblem(options);
138
+ if (scriptProblem)
139
+ problems.push(scriptProblem);
140
+ const launcherProblem = await syncLauncherProblem(options);
141
+ if (launcherProblem)
142
+ problems.push(launcherProblem);
143
+ return problems;
144
+ }
145
+ function runWindowsRegistrationScript(exec, registrationPath) {
146
+ return exec("powershell.exe", [
147
+ "-NoProfile",
148
+ "-NonInteractive",
149
+ "-ExecutionPolicy",
150
+ "Bypass",
151
+ "-File",
152
+ registrationPath,
153
+ ]);
154
+ }
155
+ function queryWindowsTaskXml(exec) {
156
+ return exec("schtasks.exe", [
157
+ "/Query",
158
+ "/TN",
159
+ WINDOWS_AUTOSTART_TASK_NAME,
160
+ "/XML",
161
+ ]);
162
+ }
163
+ function isTaskDisabled(taskXml) {
164
+ return /<Enabled>\s*false\s*<\/Enabled>/iu.test(taskXml);
165
+ }
166
+ /**
167
+ * A task Windows would not show us: absent when it says so in as many words,
168
+ * otherwise not_loaded — an unreadable task is not a missing one, and treating
169
+ * it as missing would make a repair create a second registration.
170
+ */
171
+ function windowsTaskQueryFailure(query, scriptPath) {
172
+ const queryMessage = `${query.stderr}\n${query.stdout}`;
173
+ const taskIsKnownAbsent = query.code === 1 &&
174
+ /cannot find|not found|does not exist/iu.test(queryMessage);
175
+ // Metadata only: schtasks stderr can carry the state directory, so the exit
176
+ // code and the classification travel, never the text.
177
+ console.error("[autostart] windows task query failed", JSON.stringify({
178
+ task_name: WINDOWS_AUTOSTART_TASK_NAME,
179
+ exit_code: query.code,
180
+ status: taskIsKnownAbsent ? "absent" : "not_loaded",
181
+ }));
182
+ return {
183
+ status: taskIsKnownAbsent ? "absent" : "not_loaded",
184
+ label: AUTOSTART_LABEL,
185
+ plist_path: scriptPath,
186
+ task_name: WINDOWS_AUTOSTART_TASK_NAME,
187
+ message: query.stderr.trim() || query.stdout.trim() || undefined,
188
+ };
189
+ }
190
+ /**
191
+ * BLI-2996: before this, a nonzero exit here (schtasks rejected the
192
+ * registration script, or Set-ScheduledTask threw) skipped straight to the
193
+ * return with `verified: null` and never logged anything — the doctor row
194
+ * said "needs repair" every run with no way to tell "the rewrite itself
195
+ * never ran" apart from "it ran and Windows still disagrees" (BLI-2996,
196
+ * Brandon's machine: same silent non-convergence under two different
197
+ * validator messages across CLI versions). Metadata only: exit code and
198
+ * whether the process produced any output, never the stderr/stdout text
199
+ * (it can carry the state directory or a workspace path).
200
+ */
201
+ function reportRegistrationRewriteFailed(created) {
202
+ console.error("[autostart] windows task repair rewrite failed", JSON.stringify({
203
+ task_name: WINDOWS_AUTOSTART_TASK_NAME,
204
+ exit_code: created.code,
205
+ had_output: Boolean(created.stderr.trim() || created.stdout.trim()),
206
+ }));
207
+ }
208
+ /**
209
+ * windowsTaskStatus() already logs the needs-repair case (with the problem
210
+ * list) when the rewrite ran but the read-back still disagrees; the converged
211
+ * branch is logged here too, tagged as a repair outcome and by name, so "did
212
+ * the rewrite actually fix it this run" never depends on inferring it from a
213
+ * routine status log emitted for an unrelated reason.
214
+ */
215
+ function reportRepairConverged() {
216
+ console.error("[autostart] windows task repair converged", JSON.stringify({ task_name: WINDOWS_AUTOSTART_TASK_NAME }));
217
+ }
218
+ /**
219
+ * Both branches log. A line that only fires on failure cannot answer "did
220
+ * background collection validate at all today?", which is the question that
221
+ * would have caught a validator rejecting every healthy Windows task.
222
+ * Problem labels carry no paths, so they are safe to emit verbatim.
223
+ */
224
+ function reportWindowsValidation(problems, intervalMinutes) {
225
+ if (problems.length > 0) {
226
+ console.error("[autostart] windows task needs repair", JSON.stringify({
227
+ task_name: WINDOWS_AUTOSTART_TASK_NAME,
228
+ problem_count: problems.length,
229
+ problems,
230
+ }));
231
+ return;
232
+ }
233
+ console.error("[autostart] windows task validated", JSON.stringify({
234
+ task_name: WINDOWS_AUTOSTART_TASK_NAME,
235
+ interval_minutes: intervalMinutes,
236
+ }));
237
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The two directions of XML text, shared by both host families (BLI-3571).
3
+ *
4
+ * The plist writer escapes on the way out; the plist reader and the Task
5
+ * Scheduler read-back both decode on the way in. Kept as one symmetric pair so
6
+ * a change to either side is made where its opposite is visible.
7
+ */
8
+ export function xmlEscape(value) {
9
+ return value
10
+ .replace(/&/g, "&amp;")
11
+ .replace(/</g, "&lt;")
12
+ .replace(/>/g, "&gt;")
13
+ .replace(/"/g, "&quot;")
14
+ .replace(/'/g, "&apos;");
15
+ }
16
+ export function decodeXmlEntities(value) {
17
+ return value
18
+ .replace(/&quot;/giu, '"')
19
+ .replace(/&apos;/giu, "'")
20
+ .replace(/&lt;/giu, "<")
21
+ .replace(/&gt;/giu, ">")
22
+ .replace(/&amp;/giu, "&");
23
+ }