@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.
Files changed (92) hide show
  1. package/dist/capture-opt-outs.js +129 -0
  2. package/dist/collection-scope.js +201 -0
  3. package/dist/commands/agent-session-report.js +3 -0
  4. package/dist/commands/agreements-render.js +61 -0
  5. package/dist/commands/agreements.js +196 -0
  6. package/dist/commands/analyze.js +2 -0
  7. package/dist/commands/backfill-scan.js +16 -11
  8. package/dist/commands/backfill-scope.js +16 -4
  9. package/dist/commands/backfill-session-report.js +2 -0
  10. package/dist/commands/books-files.js +159 -0
  11. package/dist/commands/books-usage.js +14 -0
  12. package/dist/commands/books.js +214 -0
  13. package/dist/commands/cli-io.js +1 -3
  14. package/dist/commands/docs-share.js +21 -11
  15. package/dist/commands/doctor-access.js +7 -0
  16. package/dist/commands/doctor-registration.js +39 -0
  17. package/dist/commands/doctor-report.js +1 -0
  18. package/dist/commands/doctor-shelf.js +62 -0
  19. package/dist/commands/doctor-update.js +2 -0
  20. package/dist/commands/doctor-usage-telemetry.js +18 -2
  21. package/dist/commands/doctor.js +9 -0
  22. package/dist/commands/heartbeat-post.js +19 -0
  23. package/dist/commands/heartbeat.js +13 -2
  24. package/dist/commands/install-receipts.js +4 -0
  25. package/dist/commands/local-args-collector-setup.js +2 -0
  26. package/dist/commands/local-args-collector-status.js +7 -21
  27. package/dist/commands/local-args-tower-agreements.js +80 -0
  28. package/dist/commands/local-args-tower-docs-msg.js +17 -5
  29. package/dist/commands/local-args-tower.js +2 -0
  30. package/dist/commands/local-args.js +6 -1
  31. package/dist/commands/local-command-shapes-agreements.js +5 -0
  32. package/dist/commands/local-discovery.js +2 -2
  33. package/dist/commands/local-help-agreements.js +24 -0
  34. package/dist/commands/local-help-commands-tower-ops.js +3 -3
  35. package/dist/commands/local-help-commands-tower.js +13 -6
  36. package/dist/commands/local-help-commands.js +20 -5
  37. package/dist/commands/local-help.js +8 -2
  38. package/dist/commands/local.js +9 -0
  39. package/dist/commands/memory-capture-prompt.js +130 -0
  40. package/dist/commands/memory-install-claude.js +51 -4
  41. package/dist/commands/memory-install-report.js +19 -3
  42. package/dist/commands/memory-install-toml.js +22 -0
  43. package/dist/commands/memory-install.js +110 -11
  44. package/dist/commands/memory-store.js +5 -0
  45. package/dist/commands/memory-switch.js +165 -0
  46. package/dist/commands/memory-uninstall.js +187 -0
  47. package/dist/commands/memory-verb-list.js +6 -2
  48. package/dist/commands/onboard.js +5 -0
  49. package/dist/commands/public-root.js +1 -1
  50. package/dist/commands/roots-label.js +85 -0
  51. package/dist/commands/roots.js +28 -10
  52. package/dist/commands/session-sync-plan.js +8 -4
  53. package/dist/commands/session-sync-scan.js +25 -3
  54. package/dist/commands/session-sync-shelf.js +143 -0
  55. package/dist/commands/session-sync-upload.js +6 -2
  56. package/dist/commands/session-sync.js +68 -8
  57. package/dist/commands/settings.js +1 -1
  58. package/dist/commands/shelf.js +165 -0
  59. package/dist/commands/sources-check.js +2 -2
  60. package/dist/commands/sources-reconciliation.js +2 -1
  61. package/dist/commands/speed-seen.js +2 -2
  62. package/dist/commands/sync-doctor.js +2 -1
  63. package/dist/commands/sync-receipt.js +1 -0
  64. package/dist/commands/sync-report.js +1 -0
  65. package/dist/commands/sync-stub-heal.js +2 -2
  66. package/dist/commands/sync.js +12 -0
  67. package/dist/error-message.js +11 -0
  68. package/dist/evidence-reconcile-client.js +2 -0
  69. package/dist/evidence-redelivery-plan.js +6 -0
  70. package/dist/evidence-redelivery.js +6 -1
  71. package/dist/evidence-stubs-client.js +2 -0
  72. package/dist/evidence-transcripts-client.js +2 -0
  73. package/dist/evidence-upload-transport.js +2 -0
  74. package/dist/founder-standing-file.js +52 -0
  75. package/dist/founder-standing.js +117 -0
  76. package/dist/image-describe-client.js +2 -1
  77. package/dist/live-founder-memo.js +57 -0
  78. package/dist/local-state-config.js +13 -7
  79. package/dist/memory-capture-choice.js +73 -0
  80. package/dist/root-work.js +55 -7
  81. package/dist/shelf-decision.js +52 -0
  82. package/dist/shelf-headers.js +15 -0
  83. package/dist/shelf-reports.js +167 -0
  84. package/dist/shelf-state.js +112 -0
  85. package/dist/source-opt-out.js +83 -6
  86. package/dist/state-dir-audit.js +2 -0
  87. package/dist/tower-client.js +4 -1
  88. package/dist/upload-agent-artifacts.js +2 -0
  89. package/dist/upload-envelope-build.js +2 -2
  90. package/dist/upload-ingest-receipt.js +2 -0
  91. package/dist/upload-session-reports-wire.js +2 -0
  92. package/package.json +4 -4
@@ -1,8 +1,53 @@
1
+ /**
2
+ * The one reader of a machine's explicit source opt-outs (ticket 22557).
3
+ *
4
+ * Collection defaults ON and self-heals. The only things that withhold a
5
+ * session are an operator's own words, written into the collector config:
6
+ *
7
+ * - `collect_claude_jsonl: false` Claude transcripts stay on the machine
8
+ * - `collect_codex_jsonl: false` Codex transcripts stay on the machine
9
+ * - `raw_evidence_upload: "disabled"` neither source uploads
10
+ *
11
+ * Each survives every config rewrite (`local-state-config.ts`) until the
12
+ * operator turns it back on. Every caller that withholds something logs the
13
+ * reason label this module returns, so a withheld source is never silent.
14
+ *
15
+ * FOUNDERS ONLY (BLI-22640, Jeetesh's review of 2026-10-25). Work on BLI
16
+ * accounts and BLI hardware is BLI's and goes to Tower; only a founder's
17
+ * opt-out is obeyed. For anybody else (Tower's LIVE answer,
18
+ * `founder-standing.ts`) every source stays on, the config value is left
19
+ * exactly as written, and `ignored` names what was found so the caller, the
20
+ * heartbeat and doctor can say so. When Tower cannot answer, the opt-out is
21
+ * HELD (`owner_check_unavailable`): the sources it names send nothing until
22
+ * Tower answers. The saved `founder-standing.json` is never read here
23
+ * (`founder-standing-contract.ts`).
24
+ *
25
+ * A new sibling must also join `scripts/build-public-cli.mjs` `runtimeFiles`.
26
+ */
27
+ import { optOutVerdict, } from "@bli-cockpit/telemetry-core";
28
+ import { liveStandingForDecision } from "./live-founder-memo.js";
1
29
  export const CODEX_OPT_OUT_REASON = "collect_codex_jsonl_false";
2
30
  export const CLAUDE_OPT_OUT_REASON = "collect_claude_jsonl_false";
3
31
  export const ALL_SOURCES_OPT_OUT_REASON = "raw_evidence_upload_disabled";
4
- /** A missing or unreadable config is "no opt-out": collection stays on. */
5
- export function resolveSourceSwitches(config) {
32
+ /** Every opt-out label the config holds, obeyed or not. */
33
+ export function configOptOutLabels(config) {
34
+ return [
35
+ ...(config?.collect_claude_jsonl === false ? ["collect_claude_jsonl_false"] : []),
36
+ ...(config?.collect_codex_jsonl === false ? ["collect_codex_jsonl_false"] : []),
37
+ ...(config?.raw_evidence_upload === "disabled" ? ["raw_evidence_upload_disabled"] : []),
38
+ ];
39
+ }
40
+ /**
41
+ * A missing or unreadable config is "no opt-out": collection stays on.
42
+ * `standing` is Tower's answer for this machine's person; only a founder's
43
+ * opt-out turns a source off.
44
+ */
45
+ export function resolveSourceSwitches(config, standing) {
46
+ const labels = configOptOutLabels(config);
47
+ const verdict = labels.length > 0 ? optOutVerdict(standing) : null;
48
+ if (verdict && !verdict.obeyed) {
49
+ return { codex: true, claude: true, codexReason: null, claudeReason: null, ignored: labels, verdict: verdict.reason, held: false };
50
+ }
6
51
  const all = config?.raw_evidence_upload === "disabled";
7
52
  const codexReason = config?.collect_codex_jsonl === false
8
53
  ? CODEX_OPT_OUT_REASON
@@ -15,11 +60,38 @@ export function resolveSourceSwitches(config) {
15
60
  claude: claudeReason === null,
16
61
  codexReason,
17
62
  claudeReason,
63
+ ignored: [],
64
+ verdict: verdict?.reason ?? null,
65
+ held: verdict?.held ?? false,
18
66
  };
19
67
  }
68
+ /**
69
+ * `resolveSourceSwitches` on Tower's LIVE answer for this machine's person,
70
+ * asked now (or the answer this process got in the last minute; the sync tick
71
+ * asks first, `refreshFounderStandingForOptOuts`). A config with no opt-out
72
+ * asks nothing. Logs once when an opt-out is ignored and once when it is held.
73
+ */
74
+ export async function readSourceSwitches(config, paths, caller, ask = {}) {
75
+ if (configOptOutLabels(config).length === 0)
76
+ return resolveSourceSwitches(config, "unknown");
77
+ const answer = await liveStandingForDecision(paths, ask);
78
+ const switches = resolveSourceSwitches(config, answer.standing);
79
+ if (switches.ignored.length > 0) {
80
+ console.error("[source-opt-out] opt-out ignored; only a founder's opt-out is obeyed, so these sessions upload", JSON.stringify({ reason: switches.verdict, ignored: switches.ignored, caller }));
81
+ }
82
+ if (switches.held) {
83
+ console.error("[source-opt-out] opt-out held; Tower could not say whether this person is a founder, so the sources it names send nothing this run", JSON.stringify({
84
+ reason: switches.verdict,
85
+ detail: answer.standing === "unknown" ? answer.reason : null,
86
+ held: configOptOutLabels(config),
87
+ caller,
88
+ }));
89
+ }
90
+ return switches;
91
+ }
20
92
  /** The reason label for one source, or null when that source is on. */
21
- export function sourceOptOutReason(config, source) {
22
- const switches = resolveSourceSwitches(config);
93
+ export function sourceOptOutReason(config, source, standing) {
94
+ const switches = resolveSourceSwitches(config, standing);
23
95
  return source === "codex" ? switches.codexReason : switches.claudeReason;
24
96
  }
25
97
  /**
@@ -28,6 +100,11 @@ export function sourceOptOutReason(config, source) {
28
100
  * keeps a config file every CLI version can read. Every config writer uses this.
29
101
  */
30
102
  export function configForDisk(config) {
31
- const { collect_codex_jsonl: codexSwitch, ...rest } = config;
32
- return codexSwitch === false ? { ...rest, collect_codex_jsonl: false } : rest;
103
+ const { collect_codex_jsonl: codexSwitch, collection_scope: scope, ...rest } = config;
104
+ // `collection_scope` (BLI-23590) follows the same rule: only `roots_only` reaches disk.
105
+ return {
106
+ ...rest,
107
+ ...(codexSwitch === false ? { collect_codex_jsonl: false } : {}),
108
+ ...(scope === "roots_only" ? { collection_scope: "roots_only" } : {}),
109
+ };
33
110
  }
@@ -57,6 +57,8 @@ export const STATE_DIR_RULES = [
57
57
  { name: "session-needs-login.json", match: "exact", class: "state", bound: "one small note, overwritten at most daily and removed when the sign-in is valid again (sync-heal.ts)" },
58
58
  { name: "agent-plugins.json", match: "exact", class: "state", bound: "one small file, overwritten: per host the last install attempt, its failure label and the last daily refresh (agent-plugins-converge.ts, BLI-18200)" },
59
59
  { name: "autostart-uninstalled", match: "exact", class: "state", bound: "a zero-byte marker written by `cockpit autostart uninstall` and removed by install or doctor" },
60
+ { name: "memory-capture.json", match: "exact", class: "state", bound: "one small file, this machine's BLI Memory on/off choice; rewritten only by `cockpit memory on|off` or the one-time doctor question (BLI-22640)" },
61
+ { name: "founder-standing.json", match: "exact", class: "state", bound: "one small file, overwritten: Tower's last answer to \"is this machine's person a founder?\", written only from a Tower reply (heartbeat or the founder-standing door), for display only: no decision reads it (BLI-22640)" },
60
62
  { name: "last-turn-trace.json", match: "exact", class: "state", bound: "one file, overwritten" },
61
63
  { name: "sync.lock", match: "exact", class: "state", bound: "lock file, removed when the tick ends" },
62
64
  { name: "backfill.lock", match: "exact", class: "state", bound: "lock file, removed when the backfill ends" },
@@ -4,10 +4,11 @@
4
4
  * carry named reasons. Warm reads reuse the daemon and writes invalidate cache.
5
5
  */
6
6
  import { invalidateReadCacheSurface, readCacheSurfaceForPath, } from "@bli-cockpit/telemetry-core/node-read-cache";
7
- import { errorMessage } from "./commands/cli-io.js";
7
+ import { errorMessage } from "./error-message.js";
8
8
  import { getCollectorRuntimePaths, readLocalCollectorSessionFile, } from "./local-state.js";
9
9
  import { normalizeDashboardUrl, readResponseJson, responseErrorMessage, } from "./upload-http.js";
10
10
  import { isWarmRoutableRead, tryWarmTowerFetch } from "./warm-tower-fetch.js";
11
+ import { collectorShelfHeaders } from "./shelf-headers.js";
11
12
  const defaultLog = (line) => {
12
13
  console.error(line);
13
14
  };
@@ -63,6 +64,8 @@ export async function towerRequest(options) {
63
64
  const headers = {
64
65
  ...(options.headers ?? {}),
65
66
  authorization: `Bearer ${options.deviceToken}`,
67
+ // BLI-23478: `x-tower-shelf: team`, caller `collector`, the CLI release (Tower's founder lock reads them).
68
+ ...collectorShelfHeaders(),
66
69
  };
67
70
  // fetch derives the multipart boundary from a FormData body; declaring the
68
71
  // content-type by hand would strip it and the server would parse nothing.
@@ -5,6 +5,7 @@
5
5
  import { AgentImageArtifactReportRequestSchema, } from "@bli-cockpit/telemetry-core";
6
6
  import { describeError } from "./health-detail.js";
7
7
  import { readResponseJson, serverFailureDetail } from "./upload-http.js";
8
+ import { collectorShelfHeaders } from "./shelf-headers.js";
8
9
  export async function reportAgentImageArtifacts(options) {
9
10
  const artifacts = agentArtifactsFromEvidence(options);
10
11
  if (artifacts.length === 0) {
@@ -26,6 +27,7 @@ export async function reportAgentImageArtifacts(options) {
26
27
  headers: {
27
28
  "Authorization": `Bearer ${options.deviceToken}`,
28
29
  "Content-Type": "application/json",
30
+ ...collectorShelfHeaders(),
29
31
  },
30
32
  body: JSON.stringify(payload),
31
33
  });
@@ -3,7 +3,7 @@
3
3
  * Read the workspace context, describe transcript images, collect sources,
4
4
  * then sanitize and assemble the event. Blockers retain their retry commands.
5
5
  */
6
- import { resolveSourceSwitches } from "./source-opt-out.js";
6
+ import { readSourceSwitches } from "./source-opt-out.js";
7
7
  import { priorSnapshotsFromCursor, } from "./adapters/raw-evidence-snapshot-supersede.js";
8
8
  import { TelemetryIngestEnvelopeSchema, } from "@bli-cockpit/telemetry-core";
9
9
  import path from "node:path";
@@ -145,7 +145,7 @@ function assembleLocalEnvelope(options, collector, uploadContext, repoLabel, sou
145
145
  async function collectEnvelopeSources(options, config, paths, repoRoot, uploadContext, session, activeContext, rawEvidenceImages, now) {
146
146
  // The last gate before any transcript byte is read for upload: a source the
147
147
  // operator opted out of is never collected, whoever the caller is (ticket 22557).
148
- const switches = resolveSourceSwitches(config);
148
+ const switches = await readSourceSwitches(config, paths, "upload_envelope");
149
149
  if (!switches.codex || !switches.claude) {
150
150
  console.error("[upload-envelope] source withheld by operator opt-out", JSON.stringify({ codex_reason: switches.codexReason, claude_reason: switches.claudeReason }));
151
151
  }
@@ -1,6 +1,7 @@
1
1
  import { describeError } from "./health-detail.js";
2
2
  import { isNonEmptyString, readResponseJson, serverFailureDetail, } from "./upload-http.js";
3
3
  import { SyncDeliveryError } from "./upload-failure-reason.js";
4
+ import { collectorShelfHeaders } from "./shelf-headers.js";
4
5
  export async function postEnvelopeToIngest(options) {
5
6
  let response;
6
7
  try {
@@ -9,6 +10,7 @@ export async function postEnvelopeToIngest(options) {
9
10
  headers: {
10
11
  "Authorization": `Bearer ${options.built.device_token}`,
11
12
  "Content-Type": "application/json",
13
+ ...collectorShelfHeaders(),
12
14
  },
13
15
  body: JSON.stringify(options.envelope),
14
16
  });
@@ -8,6 +8,7 @@ import { getCollectorRuntimePaths, readLocalCollectorConfig, readLocalCollectorS
8
8
  import { makeCollectorProvenance, makeUploadWorkContext, safeRepoLabel, } from "./upload-envelope.js";
9
9
  import { normalizeDashboardUrl, readResponseJson } from "./upload-http.js";
10
10
  import { describeError } from "./health-detail.js";
11
+ import { collectorShelfHeaders } from "./shelf-headers.js";
11
12
  /** Attempts per chunk, and the linear 250ms-per-attempt backoff between them. */
12
13
  const DEFAULT_REPORT_ATTEMPTS = 3;
13
14
  const REPORT_RETRY_BACKOFF_STEP_MS = 250;
@@ -202,6 +203,7 @@ function postSessionAttributionRequest(options) {
202
203
  headers: {
203
204
  "Authorization": `Bearer ${options.deviceToken}`,
204
205
  "Content-Type": "application/json",
206
+ ...collectorShelfHeaders(),
205
207
  },
206
208
  body: JSON.stringify({
207
209
  schema_version: "ambient-codex-session-attributions.v1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.286",
3
+ "version": "0.2.288",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,9 +27,9 @@
27
27
  "test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-verb-help.mjs && node ../../scripts/assert-public-cli-surface-doors.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-exit-contract.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-cli-doctor-json.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
28
28
  },
29
29
  "dependencies": {
30
- "@bli-cockpit/memory-mcp": "0.1.88",
31
- "@bli-cockpit/mcp": "0.1.173",
32
- "@bli-cockpit/telemetry-core": "0.1.94",
30
+ "@bli-cockpit/memory-mcp": "0.1.89",
31
+ "@bli-cockpit/mcp": "0.1.175",
32
+ "@bli-cockpit/telemetry-core": "0.1.95",
33
33
  "unpdf": "1.8.1"
34
34
  },
35
35
  "repository": {