@bli-cockpit/cli 0.2.199 → 0.2.200
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/doctor-report.js +1 -0
- package/dist/commands/doctor-slack.js +25 -0
- package/dist/commands/doctor.js +3 -0
- package/dist/commands/local-args-tower-admin.js +11 -3
- package/dist/commands/local-help-commands-tower.js +1 -1
- package/dist/commands/local-help.js +1 -1
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/slack.js +24 -0
- package/package.json +2 -2
|
@@ -164,6 +164,7 @@ export function repairReceiptLines(repairs) {
|
|
|
164
164
|
}
|
|
165
165
|
export function doctorNeedsPerson(rows, receipt) {
|
|
166
166
|
const defaults = {
|
|
167
|
+
"slack-connected": "cockpit slack connect",
|
|
167
168
|
"cli-latest": "npm i -g @bli-cockpit/cli && cockpit doctor",
|
|
168
169
|
"authed": "cockpit doctor",
|
|
169
170
|
"roots-ok": "cockpit doctor",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DEFAULT_DASHBOARD_URL } from "../local-state.js";
|
|
2
|
+
import { asRecord, callTower, openTower } from "./tower-command.js";
|
|
3
|
+
export async function checkSlackConnected(context) {
|
|
4
|
+
const message = "Slack connected for private-channel coverage";
|
|
5
|
+
try {
|
|
6
|
+
const tower = await openTower("doctor", context.command, context.io);
|
|
7
|
+
const response = await callTower(tower, { path: "/api/slack/connections", label: "doctor-slack", timeoutMs: 5_000 });
|
|
8
|
+
if (!response.ok)
|
|
9
|
+
return { id: "slack-connected", status: "needs_fix", code: response.reason, message: `${message}: could not read connection state.`, nextAction: `${tower.dashboardUrl}/settings/personal` };
|
|
10
|
+
const connections = asRecord(response.body)["connections"];
|
|
11
|
+
if (!connections || connections.length !== 2)
|
|
12
|
+
throw new Error("connection_state_incomplete");
|
|
13
|
+
const missing = connections.filter(row => !row["connected"]);
|
|
14
|
+
const links = missing.map(row => new URL(String(row["connectPath"]), tower.dashboardUrl).toString());
|
|
15
|
+
console.error("[doctor slack] checked", JSON.stringify({ missing: missing.length, reason: missing.length ? "slack_connect_required" : "ok" }));
|
|
16
|
+
return { id: "slack-connected", status: missing.length ? "needs_fix" : "ok", code: missing.length ? "slack_connect_required" : "slack_connected",
|
|
17
|
+
message: missing.length ? `${message}: connect ${missing.map(row => row["workspace"]).join(", ")} at ${links.join(" or ")}` : message,
|
|
18
|
+
...(missing.length ? { nextAction: links.join(" ") } : {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
console.error("[doctor slack] refused", JSON.stringify({ reason: "connection_read_failed" }));
|
|
23
|
+
return { id: "slack-connected", status: "skipped", code: "connection_read_failed", message: `${message}: unavailable until this machine is signed in.`, nextAction: `${context.command.dashboardUrl ?? DEFAULT_DASHBOARD_URL}/settings/personal` };
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkSlackConnected } from "./doctor-slack.js";
|
|
1
2
|
import { checkUsageTelemetry, fixUsageTelemetry } from "./doctor-usage-telemetry.js";
|
|
2
3
|
import { LOCAL_COLLECTOR_VERSION } from "../local-state.js";
|
|
3
4
|
import { isSemverBelow } from "../scheduled-self-update.js";
|
|
@@ -153,6 +154,7 @@ export function doctorInvariantTable() {
|
|
|
153
154
|
function doctorInvariants() {
|
|
154
155
|
return [
|
|
155
156
|
{ id: "cli-latest", check: checkCliLatest, fix: fixCliLatest },
|
|
157
|
+
{ id: "slack-connected", check: context => context.deps.checkSlackConnected ? context.deps.checkSlackConnected(context) : Promise.resolve({ id: "slack-connected", status: "skipped", code: "not_probed", message: "Slack connection check not provided." }) },
|
|
156
158
|
{
|
|
157
159
|
id: "authed",
|
|
158
160
|
check: (context) => context.deps.readAuth(context),
|
|
@@ -248,6 +250,7 @@ function defaultDoctorDeps(hooks) {
|
|
|
248
250
|
readRootsCoverWork,
|
|
249
251
|
resolveAndSaveRoots: (context) => hooks.resolveAndSaveRoots(context.command, context.io),
|
|
250
252
|
checkSingleInstall: checkSingleInstallState,
|
|
253
|
+
checkSlackConnected,
|
|
251
254
|
checkAutostart: checkAutostartState,
|
|
252
255
|
fixAutostart: fixAutostartState,
|
|
253
256
|
checkUsageTelemetry,
|
|
@@ -193,11 +193,11 @@ export function parseSlackArgs(args) {
|
|
|
193
193
|
],
|
|
194
194
|
});
|
|
195
195
|
if (values.positionals.length > 1) {
|
|
196
|
-
throw new Error("slack accepts one action: coverage or
|
|
196
|
+
throw new Error("slack accepts one action: coverage, read, connect or disconnect.");
|
|
197
197
|
}
|
|
198
198
|
const rawAction = values.positionals[0] ?? "coverage";
|
|
199
|
-
if (
|
|
200
|
-
throw new Error("slack action must be coverage or
|
|
199
|
+
if (!["coverage", "read", "connect", "disconnect"].includes(rawAction)) {
|
|
200
|
+
throw new Error("slack action must be coverage, read, connect or disconnect.");
|
|
201
201
|
}
|
|
202
202
|
const base = {
|
|
203
203
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
@@ -210,6 +210,14 @@ export function parseSlackArgs(args) {
|
|
|
210
210
|
if (values.booleans.has("--list") && rawAction !== "coverage") {
|
|
211
211
|
throw new Error("--list belongs to `cockpit slack coverage`.");
|
|
212
212
|
}
|
|
213
|
+
if (rawAction === "connect" || rawAction === "disconnect") {
|
|
214
|
+
const workspace = optionalNonEmpty(values.flags.get("--workspace"));
|
|
215
|
+
if (workspace && !SLACK_WORKSPACE_KEYS.includes(workspace))
|
|
216
|
+
throw new Error("Unknown Slack workspace.");
|
|
217
|
+
if (rawAction === "disconnect" && !workspace)
|
|
218
|
+
throw new Error("slack disconnect needs --workspace bli|blue_pearl.");
|
|
219
|
+
return { kind: "slack", action: rawAction, workspace, ...base };
|
|
220
|
+
}
|
|
213
221
|
if (rawAction === "coverage") {
|
|
214
222
|
// `--workspace` here is a Slack workspace key, NOT the collection-root
|
|
215
223
|
// `--workspace <path>` every setup command takes. Named at the point of use
|
|
@@ -262,7 +262,7 @@ export const TOWER_COMMAND_HELP = [
|
|
|
262
262
|
[
|
|
263
263
|
"slack",
|
|
264
264
|
[
|
|
265
|
-
"Usage: cockpit slack [coverage [--workspace bli|blue_pearl] [--stale-only] [--list] | read [filters] [--format compact|full]] [--json]",
|
|
265
|
+
"Usage: cockpit slack [connect [--workspace bli|blue_pearl] | disconnect --workspace bli|blue_pearl | coverage [--workspace bli|blue_pearl] [--stale-only] [--list] | read [filters] [--format compact|full]] [--json]",
|
|
266
266
|
"",
|
|
267
267
|
" cockpit slack coverage",
|
|
268
268
|
" What Cockpit's Slack bot can see: how many channels it is in, what each",
|
|
@@ -111,7 +111,7 @@ export function localCommandHelp(command) {
|
|
|
111
111
|
" cockpit models [list|show <provider:model>|compare <provider:model> <provider:model> [...]] [--highlight] [--dashboard-url <url>] [--json]",
|
|
112
112
|
" cockpit scout [board|start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
|
|
113
113
|
" cockpit ops [status [--job <id>] [--skips] [--memory] [--models] | recompile --person <email|name|id> [--dry-run]] [--dashboard-url <url>] [--json]",
|
|
114
|
-
" cockpit slack [coverage [--workspace bli|blue_pearl] [--stale-only] [--list] | read [--person <p>] [--channel <c>] [--query <text> [--word]] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--format compact|full]] [--json]",
|
|
114
|
+
" cockpit slack [connect [--workspace bli|blue_pearl] | disconnect --workspace bli|blue_pearl | coverage [--workspace bli|blue_pearl] [--stale-only] [--list] | read [--person <p>] [--channel <c>] [--query <text> [--word]] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--format compact|full]] [--json]",
|
|
115
115
|
" cockpit settings [personal [--chat-model <key>] [--brief-model <key>] | switches [set <key> <value>] | models [set --chat <key>] [--memory <id>] | cli-floor [<version>] | env list|set --project <p> --file <f> --content-stdin|delete --id <uuid> [--yes]] [--json]",
|
|
116
116
|
" cockpit team [members | invite <email> --role <role> [--team-id <uuid>] | role <userId> --role <role> [--yes] | device [list] | device revoke <id|name> --reason <label> [--note \"<text>\"] [--yes]] [--json]",
|
|
117
117
|
" cockpit workbook [<project> [<doc>]] [--section <id>] [--markdown] [--width <n>] [--dashboard-url <url>] [--json]",
|
|
@@ -21,7 +21,7 @@ export async function runCockpitCli(argv, io) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
24
|
-
writeLine(io?.stdout ?? process.stdout, "0.2.
|
|
24
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.200");
|
|
25
25
|
return 0;
|
|
26
26
|
}
|
|
27
27
|
|
package/dist/commands/slack.js
CHANGED
|
@@ -24,6 +24,24 @@ import { colorEnabled, dim, writeLine } from "./cli-io.js";
|
|
|
24
24
|
import { asRecord, callTower, openTower, writeCommandFailure } from "./tower-command.js";
|
|
25
25
|
export async function runSlack(command, io) {
|
|
26
26
|
const tower = await openTower("slack", command, io);
|
|
27
|
+
if (command.action === "connect" || command.action === "disconnect") {
|
|
28
|
+
const result = await callTower(tower, { path: `/api/slack/connections${command.action === "disconnect" ? `?workspace=${command.workspace}` : ""}`,
|
|
29
|
+
method: command.action === "disconnect" ? "DELETE" : "GET", label: "slack-connection" });
|
|
30
|
+
if (!result.ok)
|
|
31
|
+
return writeCommandFailure(io, command.json, result);
|
|
32
|
+
writeLine(io.stderr, `[slack cli] connection answered ${JSON.stringify({ action: command.action, workspace: command.workspace ?? "all", reason: "ok" })}`);
|
|
33
|
+
const body = asRecord(result.body);
|
|
34
|
+
if (command.json)
|
|
35
|
+
writeLine(io.stdout, JSON.stringify(body));
|
|
36
|
+
else if (command.action === "disconnect")
|
|
37
|
+
writeLine(io.stdout, `Disconnected Slack for ${command.workspace}.`);
|
|
38
|
+
else
|
|
39
|
+
for (const connection of (body["connections"] ?? [])) {
|
|
40
|
+
if (!command.workspace || command.workspace === connection["workspace"])
|
|
41
|
+
writeLine(io.stdout, `${connection["workspace"]}: ${connection["connected"] ? "connected" : "Connect Slack"} ${new URL(String(connection["connectPath"]), tower.dashboardUrl).toString()}`);
|
|
42
|
+
}
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
27
45
|
return command.action === "coverage"
|
|
28
46
|
? runCoverageAction(command, io, tower)
|
|
29
47
|
: runReadAction(command, io, tower);
|
|
@@ -86,6 +104,12 @@ function coverageLines(coverage, workspaces, staleOnly, list, paint) {
|
|
|
86
104
|
for (const workspace of workspaces) {
|
|
87
105
|
writeWorkspaceCoverageLines(lines, workspace, staleAfter, staleOnly, list, paint);
|
|
88
106
|
}
|
|
107
|
+
for (const entry of (coverage["privateCoverage"] ?? [])) {
|
|
108
|
+
lines.push(`${entry["workspace"]}: ${entry["botMemberCount"]}/${entry["privateChannelsKnown"]} known private channels have the bot; ${entry["peopleConnected"]} people connected; as of ${entry["asOf"] ?? "no scan yet"}`);
|
|
109
|
+
lines.push(` Not connected: ${(entry["peopleNotConnected"] ?? []).join(", ") || "none"}`);
|
|
110
|
+
}
|
|
111
|
+
if (coverage["privateCoverageError"])
|
|
112
|
+
lines.push(`Private coverage unavailable: ${coverage["privateCoverageError"]}`);
|
|
89
113
|
writeCoverageFooter(lines, coverage, paint);
|
|
90
114
|
return lines;
|
|
91
115
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.200",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@bli-cockpit/memory-mcp": "0.1.60",
|
|
31
|
-
"@bli-cockpit/mcp": "0.1.
|
|
31
|
+
"@bli-cockpit/mcp": "0.1.112",
|
|
32
32
|
"@bli-cockpit/telemetry-core": "0.1.69"
|
|
33
33
|
},
|
|
34
34
|
"repository": {
|