@bli-cockpit/cli 0.2.286 → 0.2.288
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/capture-opt-outs.js +129 -0
- package/dist/collection-scope.js +201 -0
- package/dist/commands/agent-session-report.js +3 -0
- package/dist/commands/agreements-render.js +61 -0
- package/dist/commands/agreements.js +196 -0
- package/dist/commands/analyze.js +2 -0
- package/dist/commands/backfill-scan.js +16 -11
- package/dist/commands/backfill-scope.js +16 -4
- package/dist/commands/backfill-session-report.js +2 -0
- package/dist/commands/books-files.js +159 -0
- package/dist/commands/books-usage.js +14 -0
- package/dist/commands/books.js +214 -0
- package/dist/commands/cli-io.js +1 -3
- package/dist/commands/docs-share.js +21 -11
- package/dist/commands/doctor-access.js +7 -0
- package/dist/commands/doctor-registration.js +39 -0
- package/dist/commands/doctor-report.js +1 -0
- package/dist/commands/doctor-shelf.js +62 -0
- package/dist/commands/doctor-update.js +2 -0
- package/dist/commands/doctor-usage-telemetry.js +18 -2
- package/dist/commands/doctor.js +9 -0
- package/dist/commands/heartbeat-post.js +19 -0
- package/dist/commands/heartbeat.js +13 -2
- package/dist/commands/install-receipts.js +4 -0
- package/dist/commands/local-args-collector-setup.js +2 -0
- package/dist/commands/local-args-collector-status.js +7 -21
- package/dist/commands/local-args-tower-agreements.js +80 -0
- package/dist/commands/local-args-tower-docs-msg.js +17 -5
- package/dist/commands/local-args-tower.js +2 -0
- package/dist/commands/local-args.js +6 -1
- package/dist/commands/local-command-shapes-agreements.js +5 -0
- package/dist/commands/local-discovery.js +2 -2
- package/dist/commands/local-help-agreements.js +24 -0
- package/dist/commands/local-help-commands-tower-ops.js +3 -3
- package/dist/commands/local-help-commands-tower.js +13 -6
- package/dist/commands/local-help-commands.js +20 -5
- package/dist/commands/local-help.js +8 -2
- package/dist/commands/local.js +9 -0
- package/dist/commands/memory-capture-prompt.js +130 -0
- package/dist/commands/memory-install-claude.js +51 -4
- package/dist/commands/memory-install-report.js +19 -3
- package/dist/commands/memory-install-toml.js +22 -0
- package/dist/commands/memory-install.js +110 -11
- package/dist/commands/memory-store.js +5 -0
- package/dist/commands/memory-switch.js +165 -0
- package/dist/commands/memory-uninstall.js +187 -0
- package/dist/commands/memory-verb-list.js +6 -2
- package/dist/commands/onboard.js +5 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/roots-label.js +85 -0
- package/dist/commands/roots.js +28 -10
- package/dist/commands/session-sync-plan.js +8 -4
- package/dist/commands/session-sync-scan.js +25 -3
- package/dist/commands/session-sync-shelf.js +143 -0
- package/dist/commands/session-sync-upload.js +6 -2
- package/dist/commands/session-sync.js +68 -8
- package/dist/commands/settings.js +1 -1
- package/dist/commands/shelf.js +165 -0
- package/dist/commands/sources-check.js +2 -2
- package/dist/commands/sources-reconciliation.js +2 -1
- package/dist/commands/speed-seen.js +2 -2
- package/dist/commands/sync-doctor.js +2 -1
- package/dist/commands/sync-receipt.js +1 -0
- package/dist/commands/sync-report.js +1 -0
- package/dist/commands/sync-stub-heal.js +2 -2
- package/dist/commands/sync.js +12 -0
- package/dist/error-message.js +11 -0
- package/dist/evidence-reconcile-client.js +2 -0
- package/dist/evidence-redelivery-plan.js +6 -0
- package/dist/evidence-redelivery.js +6 -1
- package/dist/evidence-stubs-client.js +2 -0
- package/dist/evidence-transcripts-client.js +2 -0
- package/dist/evidence-upload-transport.js +2 -0
- package/dist/founder-standing-file.js +52 -0
- package/dist/founder-standing.js +117 -0
- package/dist/image-describe-client.js +2 -1
- package/dist/live-founder-memo.js +57 -0
- package/dist/local-state-config.js +13 -7
- package/dist/memory-capture-choice.js +73 -0
- package/dist/root-work.js +55 -7
- package/dist/shelf-decision.js +52 -0
- package/dist/shelf-headers.js +15 -0
- package/dist/shelf-reports.js +167 -0
- package/dist/shelf-state.js +112 -0
- package/dist/source-opt-out.js +83 -6
- package/dist/state-dir-audit.js +2 -0
- package/dist/tower-client.js +4 -1
- package/dist/upload-agent-artifacts.js +2 -0
- package/dist/upload-envelope-build.js +2 -2
- package/dist/upload-ingest-receipt.js +2 -0
- package/dist/upload-session-reports-wire.js +2 -0
- package/package.json +4 -4
|
@@ -2,6 +2,7 @@ import { inspectAgentRules, installAgentRules } from "../agent-rules.js";
|
|
|
2
2
|
import { doctorRoots } from "./doctor-access.js";
|
|
3
3
|
import { installAutostartAgent } from "../autostart.js";
|
|
4
4
|
import { clearAutostartOptOut } from "../autostart-opt-out.js";
|
|
5
|
+
import { isMemoryCaptureOff } from "../memory-capture-choice.js";
|
|
5
6
|
import { getCollectorRuntimePaths } from "../local-state.js";
|
|
6
7
|
import { fail, needsFix, ok, skipped } from "./doctor-report.js";
|
|
7
8
|
import { autostartDoctorStatus, autostartRegistrationRoots, readAutostartRegistration, } from "./autostart-reading.js";
|
|
@@ -73,10 +74,21 @@ export async function fixAutostartState(context) {
|
|
|
73
74
|
* a hook failure, which is worse than waiting a day.
|
|
74
75
|
*/
|
|
75
76
|
export async function checkMemoryState(context) {
|
|
77
|
+
if (await memoryOptedOut(context)) {
|
|
78
|
+
// BLI-22640: a dry run of the opted-out install writes nothing and says
|
|
79
|
+
// whether any of our entries (or `bli-tower`) still needs the fix.
|
|
80
|
+
const preview = await installMemoryIntegration({ ...memoryCommandFor(context), dryRun: true }, context.io);
|
|
81
|
+
return optedOutStepState(preview, "check");
|
|
82
|
+
}
|
|
76
83
|
const outcome = await inspectMemoryIntegration(memoryCommandFor(context), context.io);
|
|
77
84
|
return memoryStepState(outcome, "check");
|
|
78
85
|
}
|
|
79
86
|
export async function fixMemoryState(context) {
|
|
87
|
+
if (await memoryOptedOut(context)) {
|
|
88
|
+
// Never re-adds BLI Memory: the opted-out install only takes our entries
|
|
89
|
+
// back off and keeps `bli-tower`, which `mcp-answers`' repair relies on.
|
|
90
|
+
return optedOutStepState(await installMemoryIntegration(memoryCommandFor(context), context.io), "fix");
|
|
91
|
+
}
|
|
80
92
|
let outcome = await installMemoryIntegration(memoryCommandFor(context), context.io);
|
|
81
93
|
if (!outcome.bin_found) {
|
|
82
94
|
// The public CLI declares the memory and Tower MCP packages as dependencies.
|
|
@@ -86,6 +98,33 @@ export async function fixMemoryState(context) {
|
|
|
86
98
|
}
|
|
87
99
|
return memoryStepState(outcome, "fix");
|
|
88
100
|
}
|
|
101
|
+
async function memoryOptedOut(context) {
|
|
102
|
+
return isMemoryCaptureOff(getCollectorRuntimePaths(context.command.homeDir), { fetch: context.io.fetch });
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* BLI-22640. The row on a machine whose owner turned BLI Memory off: never
|
|
106
|
+
* red for the absent registration (that is the choice), `skipped
|
|
107
|
+
* explicit_opt_out` once nothing of ours is left, and a fix only when an
|
|
108
|
+
* entry of ours is still on disk or `bli-tower` is missing.
|
|
109
|
+
*/
|
|
110
|
+
function optedOutStepState(outcome, phase) {
|
|
111
|
+
const failed = outcome.targets.find((target) => target.status === "failed");
|
|
112
|
+
if (failed) {
|
|
113
|
+
const message = `BLI Memory is off on this machine, but ${failed.target} could not be cleaned: ${failed.reason}`;
|
|
114
|
+
return phase === "fix"
|
|
115
|
+
? fail("memory-registered", failed.reason, message)
|
|
116
|
+
: needsFix("memory-registered", failed.reason, message);
|
|
117
|
+
}
|
|
118
|
+
const leftover = outcome.removed_count ?? 0;
|
|
119
|
+
const towerPending = outcome.targets.some((target) => target.status === "would_install");
|
|
120
|
+
if (phase === "check" && (leftover > 0 || towerPending)) {
|
|
121
|
+
return needsFix("memory-registered", leftover > 0 ? "opted_out_entries_left" : "registration_incomplete", leftover > 0
|
|
122
|
+
? `BLI Memory is off on this machine, but ${leftover} of its entries are still registered; doctor removes them`
|
|
123
|
+
: "BLI Memory is off on this machine; bli-tower is not registered yet");
|
|
124
|
+
}
|
|
125
|
+
const removed = phase === "fix" && leftover > 0 ? ` Removed ${leftover} of its entries.` : "";
|
|
126
|
+
return skipped("memory-registered", outcome.reason === "choice_unreadable" ? "choice_unreadable" : "explicit_opt_out", `BLI Memory is off on this machine (your choice); nothing of it is registered.${removed} \`cockpit memory on\` turns it back on.`);
|
|
127
|
+
}
|
|
89
128
|
function memoryCommandFor(context) {
|
|
90
129
|
return {
|
|
91
130
|
kind: "memory",
|
|
@@ -169,6 +169,7 @@ export function doctorNeedsPerson(rows, receipt) {
|
|
|
169
169
|
"authed": "cockpit doctor",
|
|
170
170
|
"roots-ok": "cockpit doctor",
|
|
171
171
|
"roots-cover-work": "cockpit doctor",
|
|
172
|
+
"shelf-routing": "cockpit doctor",
|
|
172
173
|
"single-install": "Run npm uninstall -g @bli-cockpit/cli with the Node installation that owns the extra CLI listed above.",
|
|
173
174
|
"autostart-alive": "cockpit doctor",
|
|
174
175
|
"usage-telemetry": "cockpit doctor",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `shelf-routing` doctor row (BLI-23478, three-shelves design 9.1 item 13).
|
|
3
|
+
*
|
|
4
|
+
* Reads what the last heartbeat stored about Tower's founder lock (`shelf-state.json`) and this laptop's default.
|
|
5
|
+
* On a founder's laptop, once Tower enforces the lock AND an owner has switched founder routing on, the repair sets
|
|
6
|
+
* `shelf_default: drawer`, so a founder's work outside every labelled folder stays on the laptop from then on. It
|
|
7
|
+
* never overrides a default the founder chose (`cockpit shelf default team`), and it never runs before Tower says
|
|
8
|
+
* the lock is on: labels and the default do nothing until both locks exist (design 4.1).
|
|
9
|
+
*
|
|
10
|
+
* Every other laptop: one `ok` line, nothing written.
|
|
11
|
+
*/
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import { safeLog } from "@bli-cockpit/telemetry-core";
|
|
14
|
+
import { ensureLocalCollectorConfig } from "../local-state.js";
|
|
15
|
+
import { writeJsonFile } from "../local-state-files.js";
|
|
16
|
+
import { readShelfState } from "../shelf-state.js";
|
|
17
|
+
import { askFounderLive } from "../shelf-decision.js";
|
|
18
|
+
import { configForDisk } from "../source-opt-out.js";
|
|
19
|
+
import { needsFix, ok, skipped } from "./doctor-report.js";
|
|
20
|
+
const ID = "shelf-routing";
|
|
21
|
+
/**
|
|
22
|
+
* Whether this laptop's founder should have the drawer default now. Shared with the usage-telemetry row. The
|
|
23
|
+
* switch's state comes from the saved heartbeat answer; WHO is a founder is Tower's live answer, never the saved
|
|
24
|
+
* copy (`shelf-decision.ts`): a forged `is_owner` in the file changes nothing here.
|
|
25
|
+
*/
|
|
26
|
+
export async function founderRoutingOn(homeDir, fetchImpl) {
|
|
27
|
+
const state = readShelfState(homeDir);
|
|
28
|
+
if (!state?.shelf_enforced || state.founder_routing_enabled !== true)
|
|
29
|
+
return false;
|
|
30
|
+
return (await askFounderLive({ homeDir, fetchImpl })).standing === "founder";
|
|
31
|
+
}
|
|
32
|
+
export async function checkShelfRouting(context) {
|
|
33
|
+
const homeDir = context.command.homeDir ?? os.homedir();
|
|
34
|
+
const state = readShelfState(homeDir);
|
|
35
|
+
if (!state)
|
|
36
|
+
return skipped(ID, "not_enforced", "shelf routing: Tower has not answered this laptop yet; nothing changes until it does");
|
|
37
|
+
if (state.is_owner !== true)
|
|
38
|
+
return ok(ID, "enforced", "shelf routing: not a founder's laptop; nothing changes here");
|
|
39
|
+
if (state.cli_too_old) {
|
|
40
|
+
return { ...needsFix(ID, "cli_too_old", "shelf routing: this CLI is older than Tower's founder lock accepts; run `cockpit update`"), nextAction: "cockpit update" };
|
|
41
|
+
}
|
|
42
|
+
if (state.founder_routing_enabled !== true || !state.shelf_enforced) {
|
|
43
|
+
return ok(ID, "routing_disabled_by_owner", "shelf routing: founder routing is off in Tower; labels wait until an owner switches it on");
|
|
44
|
+
}
|
|
45
|
+
const config = await ensureLocalCollectorConfig({ homeDir }).then((result) => result.config).catch(() => null);
|
|
46
|
+
if (config?.shelf_default === "drawer")
|
|
47
|
+
return ok(ID, "drawer_default_set", "shelf routing: on; work outside team-labelled folders stays in the drawer on this laptop");
|
|
48
|
+
if (config?.shelf_default === "team")
|
|
49
|
+
return ok(ID, "team_default_chosen", "shelf routing: on; you chose team as the default (`cockpit shelf default drawer` to change it)");
|
|
50
|
+
return needsFix(ID, "drawer_default_missing", "shelf routing: on, but this laptop has no default yet; doctor sets it to drawer");
|
|
51
|
+
}
|
|
52
|
+
export async function fixShelfRouting(context) {
|
|
53
|
+
const homeDir = context.command.homeDir ?? os.homedir();
|
|
54
|
+
if (!(await founderRoutingOn(homeDir, context.io.fetch)))
|
|
55
|
+
return checkShelfRouting(context);
|
|
56
|
+
const { config, paths } = await ensureLocalCollectorConfig({ homeDir });
|
|
57
|
+
if (config.shelf_default)
|
|
58
|
+
return checkShelfRouting(context);
|
|
59
|
+
await writeJsonFile(paths.config_file, configForDisk({ ...config, shelf_default: "drawer" }));
|
|
60
|
+
safeLog("shelf", "drawer_default_set", { by: "doctor" });
|
|
61
|
+
return { ...ok(ID, "drawer_default_set", "shelf routing: on; work outside team-labelled folders now stays in the drawer on this laptop"), fixed: true };
|
|
62
|
+
}
|
|
@@ -78,6 +78,8 @@ export function reexecDoctor(command, io) {
|
|
|
78
78
|
const args = ["doctor"];
|
|
79
79
|
if (command.noUsageTelemetry)
|
|
80
80
|
args.push("--no-usage-telemetry");
|
|
81
|
+
if (command.noMemory)
|
|
82
|
+
args.push("--no-memory");
|
|
81
83
|
if (command.homeDir)
|
|
82
84
|
args.push("--home", command.homeDir);
|
|
83
85
|
if (command.backfillBudgetSeconds)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { getCollectorRuntimePaths, readLocalCollectorSessionFile } from "../local-state.js";
|
|
3
|
+
import { getCollectorRuntimePaths, readLocalCollectorConfig, readLocalCollectorSessionFile } from "../local-state.js";
|
|
4
|
+
import { founderRoutingOn } from "./doctor-shelf.js";
|
|
4
5
|
import { claudeSettingsFile } from "./memory-install-claude.js";
|
|
5
6
|
import { codexConfigFile } from "./tower-mcp-codex.js";
|
|
6
7
|
import { defaultMemoryFileIo } from "./memory-install-files.js";
|
|
@@ -83,7 +84,7 @@ async function usageTelemetryStep(context, checkOnly) {
|
|
|
83
84
|
if (!session?.device_token)
|
|
84
85
|
return needsFix("usage-telemetry", "device_token_missing", "usage telemetry: needs Tower sign-in");
|
|
85
86
|
const result = await configureUsageTelemetry({ homeDir: home, origin: context.command.dashboardUrl, token: session.device_token,
|
|
86
|
-
optOut: context.command.noUsageTelemetry, checkOnly, io: defaultMemoryFileIo({ mode: 0o600 }) });
|
|
87
|
+
optOut: context.command.noUsageTelemetry || (await founderWithholdsModels(home, context.io.fetch)), checkOnly, io: defaultMemoryFileIo({ mode: 0o600 }) });
|
|
87
88
|
const message = result.off ? "usage telemetry: off (explicit opt-out)" : "usage telemetry: on (claude code, codex)";
|
|
88
89
|
return checkOnly && result.changed ? needsFix("usage-telemetry", "settings_differ", message) : ok("usage-telemetry", result.off ? "explicit_opt_out" : "already_current", message);
|
|
89
90
|
}
|
|
@@ -93,5 +94,20 @@ async function usageTelemetryStep(context, checkOnly) {
|
|
|
93
94
|
return fail("usage-telemetry", reason, "usage telemetry: settings could not be safely merged");
|
|
94
95
|
}
|
|
95
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* BLI-23478 (three shelves, design 3.2 category C): usage telemetry names the model and tool of every session, so
|
|
99
|
+
* on a founder's laptop, once founder routing is on, it is opted out unless the founder allowed `model` with
|
|
100
|
+
* `cockpit shelf metrics --allow`. The opt-out persists like any other (the marker), so allowing `model` later
|
|
101
|
+
* needs the marker removed by hand: deliberate, since turning a channel back on is a person's call.
|
|
102
|
+
*/
|
|
103
|
+
async function founderWithholdsModels(homeDir, fetchImpl) {
|
|
104
|
+
if (!(await founderRoutingOn(homeDir, fetchImpl)))
|
|
105
|
+
return false;
|
|
106
|
+
const config = await readLocalCollectorConfig(getCollectorRuntimePaths(homeDir)).catch(() => null);
|
|
107
|
+
const withheld = !(config?.shelf_metrics_allow ?? []).includes("model");
|
|
108
|
+
if (withheld)
|
|
109
|
+
console.error("[usage telemetry] founder opt-out", JSON.stringify({ reason: "shelf_model_category_off" }));
|
|
110
|
+
return withheld;
|
|
111
|
+
}
|
|
96
112
|
export const checkUsageTelemetry = (context) => usageTelemetryStep(context, true);
|
|
97
113
|
export const fixUsageTelemetry = (context) => usageTelemetryStep(context, false);
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { checkSlackConnected } from "./doctor-slack.js";
|
|
2
2
|
import { checkUsageTelemetry, fixUsageTelemetry } from "./doctor-usage-telemetry.js";
|
|
3
|
+
import { settleDoctorMemoryChoice } from "./memory-capture-prompt.js";
|
|
4
|
+
import { reportIgnoredOptOuts } from "../capture-opt-outs.js";
|
|
5
|
+
import { checkShelfRouting, fixShelfRouting } from "./doctor-shelf.js";
|
|
3
6
|
import { DEFAULT_DASHBOARD_URL, getCollectorRuntimePaths, LOCAL_COLLECTOR_VERSION } from "../local-state.js";
|
|
4
7
|
import { withCommandGuard } from "../command-guard.js";
|
|
5
8
|
import { installHttp1GlobalFetch } from "../http1-fetch.js";
|
|
@@ -31,6 +34,10 @@ export async function runDoctorWithDeps(command, io, deps) {
|
|
|
31
34
|
const deadlineMs = doctorRunDeadline(command, runStarted);
|
|
32
35
|
const context = { command, io, deps, deadlineMs };
|
|
33
36
|
console.error("[doctor] run started", JSON.stringify({ json: command.json, check_only: Boolean(command.dryRun || command.checkOnly), budget_seconds: deadlineMs === null ? null : DOCTOR_JSON_BUDGET_SECONDS }));
|
|
37
|
+
// BLI-22640: `--no-memory`, or the one-time question, before any row reads the choice.
|
|
38
|
+
await settleDoctorMemoryChoice(command, io);
|
|
39
|
+
// Founders only: a non-founder's opt-outs are ignored, and doctor says so in one line.
|
|
40
|
+
await reportIgnoredOptOuts({ ...(command.homeDir ? { homeDir: command.homeDir } : {}), json: command.json }, io);
|
|
34
41
|
const rows = [];
|
|
35
42
|
const repairs = [];
|
|
36
43
|
const diagnoseOnly = command.dryRun || command.checkOnly;
|
|
@@ -178,6 +185,8 @@ function doctorInvariants() {
|
|
|
178
185
|
// no fix because a session outside an approved root is not a defect
|
|
179
186
|
// doctor can repair; it names the counts and moves on.
|
|
180
187
|
{ id: "roots-cover-work", check: context => context.deps.readRootsCoverWork(context) },
|
|
188
|
+
// BLI-23478: a founder's drawer default once Tower's lock is on (`doctor-shelf.ts`); everyone else: one ok line.
|
|
189
|
+
{ id: "shelf-routing", check: (c) => (c.deps.checkShelfRouting ?? checkShelfRouting)(c), fix: (c) => (c.deps.fixShelfRouting ?? fixShelfRouting)(c) },
|
|
181
190
|
// Deliberately has NO fix: uninstalling software the operator did not ask
|
|
182
191
|
// to have uninstalled is not a repair (BLI-3218/BLI-3553). Doctor names the
|
|
183
192
|
// other install and the exact command; the person decides.
|
|
@@ -11,6 +11,10 @@ import { describeError } from "../health-detail.js";
|
|
|
11
11
|
import { classifySyncHealthError } from "../sync-health-class.js";
|
|
12
12
|
import { noteHeartbeatRefusal, recordHeartbeatPendingToken, recordHeartbeatTokenExpiry, TOKEN_ROTATION_HEADER, } from "./heartbeat-token.js";
|
|
13
13
|
import { noteDoctorRequest } from "./sync-doctor-request.js";
|
|
14
|
+
import { recordFounderStanding } from "../founder-standing-file.js";
|
|
15
|
+
import { getCollectorRuntimePaths } from "../local-state.js";
|
|
16
|
+
import { collectorShelfHeaders } from "../shelf-headers.js";
|
|
17
|
+
import { recordShelfState } from "../shelf-state.js";
|
|
14
18
|
const HEARTBEAT_TIMEOUT_MS = 5_000;
|
|
15
19
|
export async function postHeartbeat(options, deviceToken, heartbeat, memory) {
|
|
16
20
|
const controller = new AbortController();
|
|
@@ -21,6 +25,8 @@ export async function postHeartbeat(options, deviceToken, heartbeat, memory) {
|
|
|
21
25
|
headers: {
|
|
22
26
|
"Content-Type": "application/json",
|
|
23
27
|
Authorization: `Bearer ${deviceToken}`,
|
|
28
|
+
// BLI-23478: the CLI release, so Tower records `cli_too_old` on an owner's old laptop.
|
|
29
|
+
...collectorShelfHeaders(),
|
|
24
30
|
// BLI-17994. "I store a renewed token if you send one": the dashboard
|
|
25
31
|
// only rotates for a collector that says so.
|
|
26
32
|
[TOKEN_ROTATION_HEADER]: "1",
|
|
@@ -84,6 +90,19 @@ async function recordAcceptedHeartbeat(options, response, heartbeat, memory) {
|
|
|
84
90
|
// door reports, which moves only when this request promoted a token.
|
|
85
91
|
await recordHeartbeatPendingToken(options.homeDir, reply);
|
|
86
92
|
await recordHeartbeatTokenExpiry(options.homeDir, response);
|
|
93
|
+
// BLI-22640: Tower's answer to "is this machine's person a founder?", kept
|
|
94
|
+
// for DISPLAY (doctor, support). No decision reads this file: every reader
|
|
95
|
+
// that decides what leaves the machine asks Tower live. Only a Tower reply
|
|
96
|
+
// writes it. A reply that could not read the owners list says null, and the
|
|
97
|
+
// last displayed answer stands.
|
|
98
|
+
if (typeof reply?.["is_owner"] === "boolean") {
|
|
99
|
+
await recordFounderStanding(getCollectorRuntimePaths(options.homeDir), reply["is_owner"], "heartbeat");
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
console.error("[heartbeat] no founder answer in this reply; the last one stands", JSON.stringify({ reason: "is_owner_absent" }));
|
|
103
|
+
}
|
|
104
|
+
// BLI-23478 (three shelves): what the lock is for this laptop's person, for the hooks, the MCP server and doctor.
|
|
105
|
+
await recordShelfState(options.homeDir, reply);
|
|
87
106
|
console.error("[heartbeat] checked in", JSON.stringify({
|
|
88
107
|
reason: "heartbeat_recorded",
|
|
89
108
|
http_status: response.status,
|
|
@@ -24,7 +24,7 @@ import { collectAgentSetup } from "./agent-setup.js";
|
|
|
24
24
|
import fs from "node:fs";
|
|
25
25
|
import os from "node:os";
|
|
26
26
|
import path from "node:path";
|
|
27
|
-
import { COLLECTOR_HEARTBEAT_SCHEMA_VERSION, MemoryInstallReceiptSchema, setupReceiptGaps, } from "@bli-cockpit/telemetry-core";
|
|
27
|
+
import { COLLECTOR_HEARTBEAT_MAX_ROOTS, COLLECTOR_HEARTBEAT_SCHEMA_VERSION, MemoryInstallReceiptSchema, setupReceiptGaps, } from "@bli-cockpit/telemetry-core";
|
|
28
28
|
import { readCachedSetupReceipt } from "./setup-receipt.js";
|
|
29
29
|
import { readStagingInventory } from "../disk-usage.js";
|
|
30
30
|
import { readStagingGate } from "../staging-gate.js";
|
|
@@ -32,6 +32,8 @@ import { describeError } from "../health-detail.js";
|
|
|
32
32
|
import { shouldSuppressFleetReceipts, } from "../dev-build.js";
|
|
33
33
|
import { getCollectorRuntimePaths, LOCAL_COLLECTOR_VERSION, } from "../local-state.js";
|
|
34
34
|
import { postHeartbeat } from "./heartbeat-post.js";
|
|
35
|
+
import { readCaptureOptOuts } from "../capture-opt-outs.js";
|
|
36
|
+
import { teamRootsForHeartbeat } from "../shelf-state.js";
|
|
35
37
|
import { readHeartbeatSession } from "./heartbeat-token.js";
|
|
36
38
|
import { withTokenRotationLock } from "./heartbeat-token-lock.js";
|
|
37
39
|
import { readMemoryReceiptFile } from "./memory-install-receipt.js";
|
|
@@ -103,6 +105,9 @@ export function buildCollectorHeartbeat(options) {
|
|
|
103
105
|
collector_version: options.collectorVersion ?? LOCAL_COLLECTOR_VERSION,
|
|
104
106
|
os_platform: platform,
|
|
105
107
|
roots: collectionRootLabels(options.roots, platform),
|
|
108
|
+
...(options.shelvedRoots
|
|
109
|
+
? { shelved_roots: Array.from({ length: Math.min(options.shelvedRoots, COLLECTOR_HEARTBEAT_MAX_ROOTS) }, () => ({ shelf: "drawer" })) }
|
|
110
|
+
: {}),
|
|
106
111
|
last_sync_status: options.facts.status,
|
|
107
112
|
...(options.facts.outsideRootDirectories ? { outside_root_directories: options.facts.outsideRootDirectories } : {}),
|
|
108
113
|
...(options.facts.reason ? { last_sync_reason: options.facts.reason } : {}),
|
|
@@ -118,6 +123,7 @@ export function buildCollectorHeartbeat(options) {
|
|
|
118
123
|
...(typeof options.facts.sessionsPendingUpload === "number"
|
|
119
124
|
? { sessions_pending_upload: options.facts.sessionsPendingUpload }
|
|
120
125
|
: {}),
|
|
126
|
+
...(options.facts.collectionScope ? { collection_scope: options.facts.collectionScope } : {}),
|
|
121
127
|
...(typeof options.facts.stagingUncommittedBytes === "number"
|
|
122
128
|
? { staging_uncommitted_bytes: options.facts.stagingUncommittedBytes }
|
|
123
129
|
: {}),
|
|
@@ -127,6 +133,7 @@ export function buildCollectorHeartbeat(options) {
|
|
|
127
133
|
...(options.facts.disk ? { disk: options.facts.disk } : {}),
|
|
128
134
|
...(options.memoryInstall ? { memory_install: options.memoryInstall } : {}),
|
|
129
135
|
...(options.setupReceipt ? { setup_receipt: options.setupReceipt } : {}),
|
|
136
|
+
...(options.captureOptOuts ? { capture_opt_outs: options.captureOptOuts } : {}),
|
|
130
137
|
};
|
|
131
138
|
}
|
|
132
139
|
/**
|
|
@@ -280,9 +287,13 @@ async function sendCollectorHeartbeatLocked(options) {
|
|
|
280
287
|
}).catch(() => ({ receipt: null, reason: "receipt_read_threw" }));
|
|
281
288
|
const setupReceipt = await readCachedSetupReceipt(options.homeDir, options.now ?? new Date());
|
|
282
289
|
logSetupReceiptCarried(setupReceipt);
|
|
290
|
+
// BLI-23478: a drawer-labelled folder's name and path hash never travel; only its shelf, counted.
|
|
291
|
+
const shelfRoots = await teamRootsForHeartbeat(options.roots, options.homeDir, options.io.fetch);
|
|
283
292
|
const heartbeat = buildCollectorHeartbeat({
|
|
284
|
-
roots:
|
|
293
|
+
roots: shelfRoots.team,
|
|
294
|
+
shelvedRoots: shelfRoots.shelved,
|
|
285
295
|
facts: options.facts,
|
|
296
|
+
captureOptOuts: await readCaptureOptOuts(options.homeDir),
|
|
286
297
|
setupReceipt: setupReceipt ? { ...setupReceipt, agent_setup: await collectAgentSetup(options.homeDir ?? os.homedir()) } : null,
|
|
287
298
|
...(options.now ? { now: options.now } : {}),
|
|
288
299
|
memoryInstall: memory.receipt,
|
|
@@ -144,6 +144,9 @@ async function deliverPendingInstallEvents(options, paths, deviceToken) {
|
|
|
144
144
|
if (receipt.imageHandlingMode !== undefined && options.observed) {
|
|
145
145
|
options.observed.imageHandlingMode = receipt.imageHandlingMode;
|
|
146
146
|
}
|
|
147
|
+
if (receipt.collectionScope !== undefined && options.observed) {
|
|
148
|
+
options.observed.collectionScope = receipt.collectionScope;
|
|
149
|
+
}
|
|
147
150
|
delivered += 1;
|
|
148
151
|
})));
|
|
149
152
|
}
|
|
@@ -215,6 +218,7 @@ async function readReceiptReply(response) {
|
|
|
215
218
|
? receipt.min_cli_version.trim()
|
|
216
219
|
: null,
|
|
217
220
|
imageHandlingMode: receipt?.image_handling_mode,
|
|
221
|
+
collectionScope: receipt?.collection_scope,
|
|
218
222
|
};
|
|
219
223
|
}
|
|
220
224
|
async function keepFailedInstallEventEntry(paths, entry, error, failures) {
|
|
@@ -95,6 +95,7 @@ const DOCTOR_ALLOWED_FLAGS = [
|
|
|
95
95
|
"--check",
|
|
96
96
|
"--no-repair",
|
|
97
97
|
"--no-usage-telemetry",
|
|
98
|
+
"--no-memory",
|
|
98
99
|
"--lock-wait",
|
|
99
100
|
"--backfill-budget",
|
|
100
101
|
"--json",
|
|
@@ -141,6 +142,7 @@ export function parseDoctorArgs(alias, args) {
|
|
|
141
142
|
return {
|
|
142
143
|
kind: "doctor",
|
|
143
144
|
noUsageTelemetry: values.booleans.has("--no-usage-telemetry"),
|
|
145
|
+
noMemory: values.booleans.has("--no-memory"),
|
|
144
146
|
checkOnly: values.booleans.has("--check") || values.booleans.has("--no-repair"),
|
|
145
147
|
// Absent stays absent (BLI-11101): `doctor-budget.ts` owns the defaults,
|
|
146
148
|
// and `--json` without either flag runs under one bounded budget.
|
|
@@ -168,23 +168,9 @@ export function parseAgentRulesArgs(args) {
|
|
|
168
168
|
json: values.booleans.has("--json"),
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
* `cockpit memory` is now SIX verbs behind one noun: the two that configure
|
|
175
|
-
* this machine (`install`, `status`), the experience log (`log`), and the four
|
|
176
|
-
* that read and write the memory store itself (`search`, `save`, `update`,
|
|
177
|
-
* `forget`, parsed next door in `local-args-tower-memory.ts`).
|
|
178
|
-
*
|
|
179
|
-
* **A bare `cockpit memory` prints the verb list; it no longer installs.**
|
|
180
|
-
* BLI-3580 made `install` the default because the command is run unasked by
|
|
181
|
-
* `doctor` and by the sync tick, but those callers build
|
|
182
|
-
* `{ kind: "memory", action: "install" }` in code (`doctor-registration.ts`,
|
|
183
|
-
* `sync-followups-memory.ts`, `onboard-completion.ts`) and never pass through
|
|
184
|
-
* this parser. The only caller the default ever served was a PERSON typing the
|
|
185
|
-
* noun to find out what it does, and what they got was an MCP server and three
|
|
186
|
-
* hooks registered on their machine. Installing stays one word away.
|
|
187
|
-
*/
|
|
171
|
+
const MEMORY_MACHINE_ACTIONS = new Set([
|
|
172
|
+
"install", "uninstall", "on", "off", "status", "help",
|
|
173
|
+
]);
|
|
188
174
|
export function parseMemoryArgs(args) {
|
|
189
175
|
// The verb word first, always: reading it off `args[0]` rather than "the
|
|
190
176
|
// first token without a leading dash" is what keeps `cockpit memory --home
|
|
@@ -214,16 +200,16 @@ export function parseMemoryArgs(args) {
|
|
|
214
200
|
if (values.flags.has("--store") || values.booleans.has("--reason-stdin"))
|
|
215
201
|
throw new Error("--store and --reason-stdin require memory log");
|
|
216
202
|
if (values.positionals.length > 1) {
|
|
217
|
-
throw new Error("memory accepts at most one action (install|status).");
|
|
203
|
+
throw new Error("memory accepts at most one action (on|off|install|uninstall|status).");
|
|
218
204
|
}
|
|
219
205
|
// BLI-4047: no positional means "tell me what this noun does", not "install".
|
|
220
206
|
const action = values.positionals[0] ?? "help";
|
|
221
|
-
if (action
|
|
222
|
-
throw new Error("memory action must be install, status, help, log, search, save, update, or forget.");
|
|
207
|
+
if (!MEMORY_MACHINE_ACTIONS.has(action)) {
|
|
208
|
+
throw new Error("memory action must be on, off, install, uninstall, status, help, log, search, save, update, or forget.");
|
|
223
209
|
}
|
|
224
210
|
return {
|
|
225
211
|
kind: "memory",
|
|
226
|
-
action,
|
|
212
|
+
action: action,
|
|
227
213
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
228
214
|
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
229
215
|
dryRun: values.booleans.has("--dry-run"),
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit agreements` argument parsing (docs/architecture/agreements.md).
|
|
3
|
+
*
|
|
4
|
+
* cockpit agreements [list] your own requests to sign: open, signed, cancelled
|
|
5
|
+
* cockpit agreements show <request-id> the text, its boxes, and the record once signed
|
|
6
|
+
* cockpit agreements copy <request-id> [--out <file>] your signed copy as plain text
|
|
7
|
+
* cockpit agreements documents founders: every document, its versions and who was asked
|
|
8
|
+
* cockpit agreements draft founders: save a draft (the DraftInput JSON on stdin)
|
|
9
|
+
* cockpit agreements request --version <id> --person <user-id> [--due <date>] founders: ask a person to sign
|
|
10
|
+
* cockpit agreements cancel <request-id> founders: withdraw a request that is not signed
|
|
11
|
+
*
|
|
12
|
+
* Every verb is one `/api/agreements` door on this machine's device token, the same doors the web page, the MCP twins
|
|
13
|
+
* and JARVIS use. Asking for a signing code, signing and publishing have no verb: they are a person's own act in the
|
|
14
|
+
* browser, and those doors refuse a device token.
|
|
15
|
+
*/
|
|
16
|
+
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
17
|
+
const ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
18
|
+
const ID_EXAMPLE = "8f14e45f-ceea-467a-9575-1d3a4e7b2c10";
|
|
19
|
+
const ONE_VERB = "agreements takes one verb: list, show, copy, documents, draft, request or cancel. Example: `cockpit agreements` or `cockpit agreements show <request-id>`.";
|
|
20
|
+
/** Which flags each verb owns, so a flag on the wrong verb is refused by name rather than ignored. */
|
|
21
|
+
const FLAGS_BY_ACTION = {
|
|
22
|
+
list: [],
|
|
23
|
+
show: [],
|
|
24
|
+
copy: ["--out"],
|
|
25
|
+
documents: [],
|
|
26
|
+
draft: [],
|
|
27
|
+
request: ["--version", "--person", "--due"],
|
|
28
|
+
cancel: [],
|
|
29
|
+
};
|
|
30
|
+
const OWN_FLAGS = ["--out", "--version", "--person", "--due"];
|
|
31
|
+
const COMMON_FLAGS = ["--home", "--dashboard-url", "--json"];
|
|
32
|
+
const NEEDS_REQUEST_ID = ["show", "copy", "cancel"];
|
|
33
|
+
export function parseAgreementsArgs(args) {
|
|
34
|
+
const values = parseNamedArgs(args, {
|
|
35
|
+
allowedFlags: [...OWN_FLAGS, ...COMMON_FLAGS],
|
|
36
|
+
valueFlags: [...OWN_FLAGS, "--home", "--dashboard-url"],
|
|
37
|
+
});
|
|
38
|
+
const first = values.positionals[0];
|
|
39
|
+
if (first !== undefined && !Object.keys(FLAGS_BY_ACTION).includes(first))
|
|
40
|
+
throw new Error(ONE_VERB);
|
|
41
|
+
const action = (first ?? "list");
|
|
42
|
+
const given = [...values.flags.keys()].filter((flag) => OWN_FLAGS.includes(flag));
|
|
43
|
+
const owned = FLAGS_BY_ACTION[action];
|
|
44
|
+
const stray = given.find((flag) => !owned.includes(flag));
|
|
45
|
+
if (stray)
|
|
46
|
+
throw new Error(`${stray} does not belong to \`cockpit agreements ${action}\`.`);
|
|
47
|
+
const rest = values.positionals.slice(first === undefined ? 0 : 1);
|
|
48
|
+
const needsId = NEEDS_REQUEST_ID.includes(action);
|
|
49
|
+
if (needsId ? rest.length !== 1 : rest.length > 0) {
|
|
50
|
+
throw new Error(needsId ? `\`cockpit agreements ${action}\` takes exactly one request id.` : `\`cockpit agreements ${action}\` takes no positional arguments.`);
|
|
51
|
+
}
|
|
52
|
+
const requestId = needsId ? rest[0] : undefined;
|
|
53
|
+
if (requestId !== undefined && !ID.test(requestId))
|
|
54
|
+
throw new Error(`A request id looks like ${ID_EXAMPLE} (\`cockpit agreements\` lists yours).`);
|
|
55
|
+
const versionId = optionalNonEmpty(values.flags.get("--version"));
|
|
56
|
+
const personId = optionalNonEmpty(values.flags.get("--person"));
|
|
57
|
+
const dueAt = optionalNonEmpty(values.flags.get("--due"));
|
|
58
|
+
if (action === "request") {
|
|
59
|
+
if (!versionId || !personId)
|
|
60
|
+
throw new Error("`cockpit agreements request` needs --version <version-id> and --person <user-id> (`cockpit agreements documents` lists both).");
|
|
61
|
+
if (!ID.test(versionId))
|
|
62
|
+
throw new Error(`--version looks like ${ID_EXAMPLE} (\`cockpit agreements documents\` lists the versions).`);
|
|
63
|
+
if (!ID.test(personId))
|
|
64
|
+
throw new Error(`--person is a user id like ${ID_EXAMPLE} (\`cockpit team members\` lists them).`);
|
|
65
|
+
if (dueAt !== undefined && Number.isNaN(Date.parse(dueAt)))
|
|
66
|
+
throw new Error("--due wants a date, like 2026-11-15.");
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
kind: "agreements",
|
|
70
|
+
action,
|
|
71
|
+
...(requestId ? { requestId } : {}),
|
|
72
|
+
...(optionalNonEmpty(values.flags.get("--out")) ? { outPath: optionalNonEmpty(values.flags.get("--out")) } : {}),
|
|
73
|
+
...(versionId ? { versionId } : {}),
|
|
74
|
+
...(personId ? { personId } : {}),
|
|
75
|
+
...(dueAt ? { dueAt } : {}),
|
|
76
|
+
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
77
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
78
|
+
json: values.booleans.has("--json"),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -47,6 +47,7 @@ const DOCS_ALLOWED_FLAGS = [
|
|
|
47
47
|
"--out",
|
|
48
48
|
"--add",
|
|
49
49
|
"--remove",
|
|
50
|
+
"--role",
|
|
50
51
|
"--reason",
|
|
51
52
|
"--cursor",
|
|
52
53
|
"--before",
|
|
@@ -70,6 +71,7 @@ const DOCS_VALUE_FLAGS = [
|
|
|
70
71
|
"--out",
|
|
71
72
|
"--add",
|
|
72
73
|
"--remove",
|
|
74
|
+
"--role",
|
|
73
75
|
"--reason",
|
|
74
76
|
"--cursor",
|
|
75
77
|
"--before",
|
|
@@ -296,24 +298,34 @@ function parseDocsReadFlags(action, values) {
|
|
|
296
298
|
return { query, wholeWord, limit, bodies, section, offset };
|
|
297
299
|
}
|
|
298
300
|
/**
|
|
299
|
-
* BLI-21313: `docs share <doc> --add <email> | --remove <email> [--reason <text>]` names
|
|
300
|
-
* `docs access <doc>` reads who may edit and why. A person
|
|
301
|
+
* BLI-21313, BLI-23558: `docs share <doc> --add <email> [--role viewer|editor] | --remove <email> [--reason <text>]` names
|
|
302
|
+
* or removes a person on a page (a private page too); `docs access <doc>` reads who may read and edit and why. A person
|
|
303
|
+
* is an exact email address, never a fuzzy name. `--role` is for `--add` only; absent means editor (what share did
|
|
304
|
+
* before viewers existed).
|
|
301
305
|
*/
|
|
302
306
|
function parseDocsShareFlags(action, values) {
|
|
303
307
|
const addPerson = optionalNonEmpty(values.flags.get("--add"));
|
|
304
308
|
const removePerson = optionalNonEmpty(values.flags.get("--remove"));
|
|
309
|
+
const role = optionalNonEmpty(values.flags.get("--role"));
|
|
305
310
|
const reason = optionalNonEmpty(values.flags.get("--reason"));
|
|
306
|
-
if ((addPerson || removePerson || reason) && action !== "share") {
|
|
307
|
-
throw new Error("--add, --remove and --reason belong to `cockpit docs share`.");
|
|
311
|
+
if ((addPerson || removePerson || reason || role) && action !== "share") {
|
|
312
|
+
throw new Error("--add, --remove, --role and --reason belong to `cockpit docs share`.");
|
|
308
313
|
}
|
|
314
|
+
if (role !== undefined && role !== "viewer" && role !== "editor")
|
|
315
|
+
throw new Error('docs share --role must be "viewer" or "editor".');
|
|
309
316
|
if (action === "share") {
|
|
310
317
|
if (Boolean(addPerson) === Boolean(removePerson)) {
|
|
311
318
|
throw new Error("docs share needs exactly one of --add <email> or --remove <email>.");
|
|
312
319
|
}
|
|
320
|
+
if (role && !addPerson)
|
|
321
|
+
throw new Error("--role goes with --add; --remove takes the person off whatever role they hold.");
|
|
313
322
|
if (reason && reason.length > 200)
|
|
314
323
|
throw new Error("--reason can be at most 200 characters.");
|
|
315
324
|
}
|
|
316
|
-
return {
|
|
325
|
+
return {
|
|
326
|
+
...(addPerson ? { addPerson } : {}), ...(removePerson ? { removePerson } : {}),
|
|
327
|
+
...(role ? { shareRole: role } : {}), ...(reason ? { reason } : {}),
|
|
328
|
+
};
|
|
317
329
|
}
|
|
318
330
|
/**
|
|
319
331
|
* BLI-21413: `docs trash [--cursor C] [--limit N]`, `docs versions <doc> [--before N] [--limit N] [--version V]`,
|
|
@@ -50,11 +50,13 @@ export { parseWhoArgs } from "./local-args-tower-who.js";
|
|
|
50
50
|
export { parsePeopleArgs } from "./local-args-tower-people.js";
|
|
51
51
|
export { parseApprovalsArgs } from "./local-args-tower-approvals.js";
|
|
52
52
|
export { parseDrillsArgs } from "./local-args-tower-drills.js";
|
|
53
|
+
export { parseAgreementsArgs } from "./local-args-tower-agreements.js";
|
|
53
54
|
export { parseManagerArgs } from "./local-args-tower-manager.js";
|
|
54
55
|
export { parseImageArgs } from "./local-args-tower-image.js";
|
|
55
56
|
export { parseCareersArgs } from './local-args-tower-careers.js';
|
|
56
57
|
export { parseRenameArgs } from './local-args-tower-rename.js';
|
|
57
58
|
export { parseGlossaryArgs } from "./glossary.js";
|
|
59
|
+
export { parseBooksArgs } from "./books.js";
|
|
58
60
|
export { parseSkillsArgs } from "./skills.js";
|
|
59
61
|
export { parseSpendArgs } from "./spend.js";
|
|
60
62
|
export { parsePushArgs } from "./push.js";
|
|
@@ -16,15 +16,17 @@
|
|
|
16
16
|
* verbatim, no logic change.
|
|
17
17
|
*/
|
|
18
18
|
import { parseGlossaryArgs } from "./glossary.js";
|
|
19
|
+
import { parseBooksArgs } from "./books.js";
|
|
19
20
|
import { parseSkillsArgs } from "./skills.js";
|
|
20
21
|
import { parseSpendArgs } from "./spend.js";
|
|
21
22
|
import { parsePushArgs } from "./push.js";
|
|
22
23
|
import { parseRootsArgs } from "./roots.js";
|
|
24
|
+
import { parseShelfArgs } from "./shelf.js";
|
|
23
25
|
import { parseSlackPrivacyArgs } from "./slack-privacy-args.js";
|
|
24
26
|
import { parseMilestoneArgs } from "./local-args-tower-milestone.js";
|
|
25
27
|
import { SURFACE_ALIASES } from "./local-help.js";
|
|
26
28
|
import { parseAgentRulesArgs, parseAnalyzeArgs, parseAutostartArgs, parseBackfillArgs, parseCleanArgs, parseDoctorArgs, parseInstallArgs, parseLoginArgs, parseMemoryArgs, parseLogoutArgs, parseOnboardArgs, parseReleaseArgs, parseServeArgs, parseSessionsArgs, parseSourcesArgs, parseStartArgs, parseStatusArgs, parseSyncArgs, parseUpdateArgs, } from "./local-args-collector.js";
|
|
27
|
-
import { parseCiArgs, parseDeliveryArgs, parsePrOutputArgs, parseAiSpendArgs, parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseHitsArgs, parseAiSkillsArgs, parseIntentionsArgs, parseWhoArgs, parsePeopleArgs, parseApprovalsArgs, parseDrillsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
|
|
29
|
+
import { parseCiArgs, parseDeliveryArgs, parsePrOutputArgs, parseAiSpendArgs, parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseHitsArgs, parseAiSkillsArgs, parseIntentionsArgs, parseWhoArgs, parsePeopleArgs, parseApprovalsArgs, parseDrillsArgs, parseAgreementsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
|
|
28
30
|
// `normalizeUrl` has always been part of this module's surface — `local.ts` and
|
|
29
31
|
// `local-auth.ts` import it from here — so it stays exported from this address
|
|
30
32
|
// even though it now lives next door. The same goes for the four names the
|
|
@@ -42,6 +44,7 @@ export { SCOUT_MIN_PREFIX_LENGTH, SLACK_WORKSPACE_KEYS, WORKBOOK_MIN_WIDTH, ISSU
|
|
|
42
44
|
*/
|
|
43
45
|
const COMMAND_PARSERS = new Map([
|
|
44
46
|
["roots", (_command, rest) => parseRootsArgs(rest)],
|
|
47
|
+
["shelf", (_command, rest) => parseShelfArgs(rest)],
|
|
45
48
|
["onboard", (_command, rest) => parseOnboardArgs(rest)],
|
|
46
49
|
["update", (command, rest) => parseUpdateArgs(command, rest)],
|
|
47
50
|
["upgrade", (command, rest) => parseUpdateArgs(command, rest)],
|
|
@@ -89,6 +92,7 @@ const COMMAND_PARSERS = new Map([
|
|
|
89
92
|
["clean", (_command, rest) => parseCleanArgs(rest)],
|
|
90
93
|
["sources", (_command, rest) => parseSourcesArgs(rest)],
|
|
91
94
|
["glossary", (_command, rest) => parseGlossaryArgs(rest)],
|
|
95
|
+
["books", (_command, rest) => parseBooksArgs(rest)], // BLI-23711: Client Books
|
|
92
96
|
["skills", (_command, rest) => parseSkillsArgs(rest)], // BLI-23364 D: the Skill Library
|
|
93
97
|
["spend", (_command, rest) => parseSpendArgs(rest)],
|
|
94
98
|
["push", (_command, rest) => parsePushArgs(rest)],
|
|
@@ -116,6 +120,7 @@ const COMMAND_PARSERS = new Map([
|
|
|
116
120
|
["people", (_command, rest) => parsePeopleArgs(rest)],
|
|
117
121
|
["approvals", (_command, rest) => parseApprovalsArgs(rest)],
|
|
118
122
|
["drills", (_command, rest) => parseDrillsArgs(rest)],
|
|
123
|
+
["agreements", (_command, rest) => parseAgreementsArgs(rest)],
|
|
119
124
|
["manager", (_command, rest) => parseManagerArgs(rest)],
|
|
120
125
|
["image", (_command, rest) => parseImageArgs(rest)],
|
|
121
126
|
]);
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* binding a ticket from a scratch folder to every approved root on the machine
|
|
17
17
|
* is far wider than the operator asked for, so it keeps the old refusal.
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
19
|
+
import { readSourceSwitches } from "../source-opt-out.js";
|
|
20
20
|
import { writeLine } from "./cli-io.js";
|
|
21
21
|
import { resolveDiscoveryLimits, saveDiscoveryLimits, } from "../discovery-limits.js";
|
|
22
22
|
import { getCollectorRuntimePaths, readLocalCollectorConfig, } from "../local-state.js";
|
|
@@ -196,7 +196,7 @@ export async function scanWorkDirectories(homeDir, persist = true) {
|
|
|
196
196
|
return null; throw error; });
|
|
197
197
|
const now = new Date();
|
|
198
198
|
const options = { worktrees: [], collectionRoots: config?.default_repo_paths ?? [], now, sinceMinutes: ROOT_WORK_SCAN_WINDOW_DAYS * 24 * 60, limit: Number.MAX_SAFE_INTEGER };
|
|
199
|
-
const switches =
|
|
199
|
+
const switches = await readSourceSwitches(config, paths, "discovery");
|
|
200
200
|
if (!switches.codex)
|
|
201
201
|
console.error("[discovery] codex scan withheld by operator opt-out", JSON.stringify({ reason: switches.codexReason }));
|
|
202
202
|
const codex = switches.codex ? await scanAndAttributeCodexSessions({ ...options, sessionsDirs: defaultCodexSessionDirs(homeDir) }) : null;
|