@bli-cockpit/cli 0.2.56 → 0.2.58

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 (43) hide show
  1. package/dist/commands/agent-door.js +85 -0
  2. package/dist/commands/docs.js +227 -0
  3. package/dist/commands/issue-contracts.js +99 -0
  4. package/dist/commands/issue-write.js +129 -0
  5. package/dist/commands/issue.js +189 -0
  6. package/dist/commands/local-args-tower-docs-msg.js +126 -0
  7. package/dist/commands/local-args-tower-work.js +178 -0
  8. package/dist/commands/local-args-tower.js +7 -1
  9. package/dist/commands/local-args.js +10 -2
  10. package/dist/commands/local-help.js +70 -0
  11. package/dist/commands/local.js +12 -0
  12. package/dist/commands/mcp-bin-resolve.js +102 -0
  13. package/dist/commands/memory-install-claude.js +13 -5
  14. package/dist/commands/memory-install-config.js +140 -0
  15. package/dist/commands/memory-install-report.js +89 -0
  16. package/dist/commands/memory-install.js +51 -362
  17. package/dist/commands/msg.js +188 -0
  18. package/dist/commands/notes-door.js +120 -0
  19. package/dist/commands/notes-reads.js +134 -0
  20. package/dist/commands/notes-writes.js +208 -0
  21. package/dist/commands/notes.js +16 -442
  22. package/dist/commands/ops-render.js +18 -2
  23. package/dist/commands/ops.js +9 -2
  24. package/dist/commands/project.js +38 -0
  25. package/dist/commands/public-root.js +1 -1
  26. package/dist/commands/tower-mcp-claude.js +30 -0
  27. package/dist/commands/tower-mcp-codex.js +100 -0
  28. package/dist/commands/tower-mcp-contract.js +39 -0
  29. package/dist/commands/tower-mcp-install.js +75 -0
  30. package/dist/repo-identity-fingerprint.js +88 -0
  31. package/dist/repo-identity-git.js +76 -0
  32. package/dist/repo-identity-linked-worktrees.js +81 -0
  33. package/dist/repo-identity.js +5 -222
  34. package/dist/upload-envelope-build.js +240 -0
  35. package/dist/upload-envelope-event.js +198 -0
  36. package/dist/upload-envelope.js +16 -427
  37. package/dist/upload-ingest-receipt.js +121 -0
  38. package/dist/upload-session-reports-queue.js +156 -0
  39. package/dist/upload-session-reports-wire.js +275 -0
  40. package/dist/upload-session-reports.js +14 -425
  41. package/dist/upload-sync.js +291 -0
  42. package/dist/upload.js +24 -396
  43. package/package.json +6 -5
package/dist/upload.js CHANGED
@@ -1,402 +1,30 @@
1
- import { getCollectorRuntimePaths } from "./local-state.js";
2
- import { uploadRawEvidenceFilesChunked, } from "./evidence-upload-client.js";
3
- import { markObjectCommitted, readRawEvidenceCursor, writeRawEvidenceCursor, } from "./cursors/raw-evidence-cursor.js";
4
- import { readRawEvidenceStagingState } from "./raw-evidence-staging.js";
5
- import { recordUploadBlocked, recordUploadFailure, recordUploadSuccess, } from "./spool/local-spool.js";
6
- import { buildLocalAmbientEnvelope, LocalUploadBlockedError, } from "./upload-envelope.js";
7
- import { applyRawEvidenceUploadOutcomes, hasRetryableEvidenceGap, logEvidenceBackoffBypassed, logEvidenceHeldByBackoff, logPermanentlyRejectedEvidence, partitionHeldEvidenceFiles, permanentEvidenceFailureReason, persistDeliveryAttempts, retrySourcesForFailedSync, retryableEvidenceGapReason, summarizeRawEvidenceDelivery, } from "./upload-evidence-delivery.js";
8
- import { reportAgentImageArtifacts } from "./upload-agent-artifacts.js";
9
- import { describeError } from "./health-detail.js";
10
- import { isNonEmptyString, readResponseJson, serverFailureDetail, } from "./upload-http.js";
11
- import { SyncDeliveryError, classifySyncFailure, } from "./upload-failure-reason.js";
12
- export { LocalUploadBlockedError, buildLocalAmbientEnvelope, } from "./upload-envelope.js";
13
- export { partitionHeldEvidenceFiles } from "./upload-evidence-delivery.js";
14
- export { reportAgentImageArtifacts } from "./upload-agent-artifacts.js";
15
- export { flushPendingCodexSessionReports, postCodexSessionReport, queueCodexSessionReport, reportCodexSessionAttributions, } from "./upload-session-reports.js";
16
- export async function syncLocalAmbientEnvelope(options = {}) {
17
- const attemptedAt = (options.now ?? new Date()).toISOString();
18
- const paths = getCollectorRuntimePaths(options.homeDir);
19
- const cursor = await readRawEvidenceCursor(paths);
20
- const built = await buildEnvelopeOrRecordBlocker(paths, options, cursor.objects, attemptedAt);
21
- const fetchImpl = options.fetch ?? globalThis.fetch;
22
- if (!fetchImpl) {
23
- throw new Error("global fetch is unavailable; use Node.js 20 or newer.");
24
- }
25
- const provenance = built.envelope.work_context.provenance;
26
- if (!provenance) {
27
- throw new Error("Ambient upload requires collector provenance.");
28
- }
29
- // Assigned progressively so the spool path below can still report what did
30
- // land if the upload pass throws part-way through.
31
- let uploadOutcomes = [];
32
- let uploadedChunkCount = 0;
33
- const attemptedDate = options.now ?? new Date(attemptedAt);
34
- const staging = await readRawEvidenceStagingState(paths.state_dir);
35
- try {
36
- if (built.raw_evidence_upload_files.length > 0) {
37
- // Objects still inside their backoff window are not offered at all. They
38
- // become held outcomes below, which keeps them out of the envelope, keeps
39
- // the sync in retry_pending, and names the reason — the 559th identical
40
- // attempt is what BLI-3066 made impossible. An operator-invoked retry
41
- // (`cockpit backfill`, doctor) offers them anyway and says so: the window
42
- // is the scheduler's cadence, not an answer to a person asking now
43
- // (BLI-3118).
44
- const { deliverable, held, bypassed } = partitionHeldEvidenceFiles(built.raw_evidence_upload_files, staging, attemptedDate, options.evidenceDeliveryMode);
45
- logEvidenceHeldByBackoff(held, attemptedAt);
46
- logEvidenceBackoffBypassed(bypassed, attemptedAt);
47
- uploadOutcomes = held;
48
- if (deliverable.length > 0) {
49
- const upload = await uploadRawEvidenceFilesChunked({
50
- fetchImpl,
51
- dashboardUrl: built.dashboard_url,
52
- deviceToken: built.device_token,
53
- provenance,
54
- generatedAt: built.envelope.generated_at,
55
- files: deliverable,
56
- });
57
- uploadOutcomes = [...held, ...upload.outcomes];
58
- uploadedChunkCount = upload.uploaded_chunk_count;
59
- }
60
- await persistDeliveryAttempts(paths.state_dir, staging, uploadOutcomes, attemptedDate);
61
- }
62
- const response = await postEnvelopeToIngest({
63
- fetchImpl,
64
- built,
65
- envelope: applyRawEvidenceUploadOutcomes(built.envelope, uploadOutcomes),
66
- });
67
- advanceRawEvidenceCursor(cursor, uploadOutcomes, attemptedAt);
68
- await writeRawEvidenceCursor(paths, cursor);
69
- await reportAgentImageArtifacts({
70
- fetchImpl,
71
- dashboardUrl: built.dashboard_url,
72
- deviceToken: built.device_token,
73
- provenance,
74
- generatedAt: built.envelope.generated_at,
75
- ticketId: built.ticket_id,
76
- repoFingerprint: built.envelope.work_context.repo_fingerprint,
77
- worktreeFingerprint: built.envelope.work_context.worktree_fingerprint,
78
- outcomes: uploadOutcomes,
79
- reused: built.raw_evidence_facts?.reused ?? [],
80
- cursorObjects: cursor.objects,
81
- }).catch((error) => {
82
- // The whole point of `agent_artifact_report_local_error` is that it is
83
- // NOT the network error the reporter already labels itself — it is a
84
- // throw from our own code on the way in. That distinction is only
85
- // useful if the throw says what it was (BLI-3238).
86
- console.error("[cockpit-sync] agent image artifact report threw locally", JSON.stringify({
87
- reason: "agent_artifact_report_local_error",
88
- ...describeError(error),
89
- }));
90
- return {
91
- posted: false,
92
- reason: "agent_artifact_report_local_error",
93
- recorded_count: 0,
94
- };
95
- });
96
- await recordIngestedSyncOutcome({
97
- paths,
98
- options,
99
- built,
100
- uploadOutcomes,
101
- attemptedAt,
102
- });
103
- // The success branch logs too. A line that only fires on failure cannot
104
- // answer "did anything land at all today?", which is the question that
105
- // would have caught BLI-2528 in a week instead of 57 days.
106
- console.error("[cockpit-sync] ingest accepted the envelope", JSON.stringify({
107
- reason: "ingest_accepted",
108
- http_status: response.status,
109
- event_count: built.event_count,
110
- raw_evidence_file_count: built.raw_evidence_upload_files.length,
111
- uploaded_chunk_count: uploadedChunkCount,
112
- failed_object_count: uploadOutcomes.filter((outcome) => outcome.upload_state === "upload_failed").length,
113
- }));
114
- return {
115
- status: "uploaded",
116
- dashboard_url: built.dashboard_url,
117
- ticket_id: built.ticket_id,
118
- work_context_id: built.envelope.work_context.work_context_id,
119
- head_sha: built.head_sha,
120
- event_count: built.event_count,
121
- source_scan_count: built.source_scan_count,
122
- risk_flag_count: built.risk_flag_count,
123
- http_status: response.status,
124
- ...summarizeRawEvidenceDelivery(built, uploadOutcomes, uploadedChunkCount, cursor, staging, attemptedDate),
125
- };
126
- }
127
- catch (error) {
128
- // The spool row used to store `error.message` verbatim, so every distinct
129
- // server sentence became its own "reason" and nothing could count how many
130
- // machines were failing the same way (BLI-3483). It now stores a label from
131
- // a closed set plus a bounded, redacted cause.
132
- const classified = classifySyncFailure(error);
133
- // A committed content-addressed object may be shared by concurrent syncs.
134
- // Never delete it from this error path: an ingest retry can reuse it, while
135
- // client-side cleanup cannot prove exclusive ownership without racing a
136
- // second sync that is about to index the same object.
137
- const spooledFailureReason = classified.failure_reason;
138
- console.error("[cockpit-sync] sync spooled for retry", JSON.stringify({
139
- reason: classified.reason,
140
- ...(classified.http_status === null
141
- ? {}
142
- : { http_status: classified.http_status }),
143
- ...(classified.detail ? { detail: classified.detail } : {}),
144
- event_count: built.event_count,
145
- raw_evidence_file_count: built.raw_evidence_upload_files.length,
146
- uploaded_chunk_count: uploadedChunkCount,
147
- }));
148
- const entry = await recordUploadFailure(paths, {
149
- last_attempt_at: attemptedAt,
150
- dashboard_url: built.dashboard_url,
151
- work_context_id: built.envelope.work_context.work_context_id,
152
- ticket_id: built.ticket_id,
153
- repo_label: built.repo_label,
154
- branch: built.envelope.work_context.branch,
155
- event_count: built.event_count,
156
- source_scan_count: built.source_scan_count,
157
- risk_flag_count: built.risk_flag_count,
158
- raw_evidence_file_count: built.raw_evidence_upload_files.length,
159
- retry_sources: retrySourcesForFailedSync(options, built.raw_evidence_facts),
160
- failure_reason: spooledFailureReason,
161
- retry_command: "cockpit sync",
162
- });
163
- return {
164
- status: "spooled",
165
- dashboard_url: built.dashboard_url,
166
- ticket_id: built.ticket_id,
167
- work_context_id: built.envelope.work_context.work_context_id,
168
- head_sha: built.head_sha,
169
- event_count: built.event_count,
170
- source_scan_count: built.source_scan_count,
171
- risk_flag_count: built.risk_flag_count,
172
- failure_reason: spooledFailureReason,
173
- failure_class: classified.reason,
174
- failure_http_status: classified.http_status,
175
- spool_entry_id: entry.spool_id,
176
- retry_command: entry.retry_command,
177
- ...summarizeRawEvidenceDelivery(built, uploadOutcomes, uploadedChunkCount, cursor, staging, attemptedDate),
178
- };
179
- }
180
- }
181
1
  /**
182
- * Assemble the envelope, and if this machine simply cannot upload, say so in
183
- * the spool before the error leaves. A blocker that only ever surfaced as a
184
- * thrown exception would be invisible to the next `cockpit status`.
185
- */
186
- async function buildEnvelopeOrRecordBlocker(paths, options, cursorObjects, attemptedAt) {
187
- try {
188
- return await buildLocalAmbientEnvelope({
189
- ...options,
190
- cursorObjects: options.cursorObjects ?? cursorObjects,
191
- });
192
- }
193
- catch (error) {
194
- if (error instanceof LocalUploadBlockedError) {
195
- await recordUploadBlocked(paths, {
196
- attemptedAt,
197
- reason: error.message,
198
- });
199
- }
200
- throw error;
201
- }
202
- }
203
- /**
204
- * POST the envelope and refuse to believe an unreadable answer.
2
+ * One ambient sync, end to end.
205
3
  *
206
- * A 2xx may have persisted the envelope even when a proxy or an incompatible
207
- * dashboard mangles the receipt. Uploaded objects are kept in that ambiguous
208
- * case they are durable either way — but the caller must not advance cursors
209
- * or clear retry state until the first-party route proves the exact submitted
210
- * row counts, which is what the receipt check demands.
211
- */
212
- async function postEnvelopeToIngest(options) {
213
- let response;
214
- try {
215
- response = await options.fetchImpl(`${options.built.dashboard_url}/api/ambient/ingest`, {
216
- method: "POST",
217
- headers: {
218
- "Authorization": `Bearer ${options.built.device_token}`,
219
- "Content-Type": "application/json",
220
- },
221
- body: JSON.stringify(options.envelope),
222
- });
223
- }
224
- catch (error) {
225
- // No status came back at all, so no server-side reason exists to read. The
226
- // error's own name and code (`TypeError` / `ENOTFOUND` / `ECONNREFUSED`)
227
- // are the whole answer, and they are what separates "this machine is
228
- // offline" from "the dashboard refused us" — two words that used to be the
229
- // same spool row.
230
- const described = describeError(error);
231
- console.error("[cockpit-sync] ingest request failed before a status came back", JSON.stringify({ reason: "ingest_transport_error", ...described }));
232
- throw new SyncDeliveryError("ingest_transport_error", {
233
- detail: [
234
- described.error_name,
235
- described.error_code,
236
- described.cause_code ?? described.cause_name,
237
- described.error_detail,
238
- ]
239
- .filter(Boolean)
240
- .join(" "),
241
- cause: error,
242
- });
243
- }
244
- const responseBody = await readResponseJson(response);
245
- if (!response.ok) {
246
- const serverReason = serverFailureDetail(responseBody);
247
- console.error("[cockpit-sync] ingest refused the envelope", JSON.stringify({
248
- reason: "ingest_rejected",
249
- http_status: response.status,
250
- server_reason: serverReason ?? "none",
251
- event_count: options.envelope.events.length,
252
- }));
253
- throw new SyncDeliveryError("ingest_rejected", {
254
- httpStatus: response.status,
255
- detail: `http ${response.status}; server reason ${serverReason ?? "none"}`,
256
- });
257
- }
258
- assertAmbientIngestReceipt(response, responseBody, options.envelope);
259
- return response;
260
- }
261
- /**
262
- * Remember every object that is now durable, so the next sync can reuse the
263
- * bytes instead of re-uploading them. A failed upload is deliberately absent:
264
- * recording it would make the next sync skip a file that never landed.
265
- */
266
- function advanceRawEvidenceCursor(cursor, outcomes, attemptedAt) {
267
- for (const outcome of outcomes) {
268
- if (outcome.upload_state === "upload_failed")
269
- continue;
270
- const contentHash = outcome.pointer.content_hash_sha256;
271
- if (!contentHash)
272
- continue;
273
- markObjectCommitted(cursor, contentHash, {
274
- object_key: outcome.object_key,
275
- byte_size: outcome.pointer.byte_size ?? 0,
276
- committed_at: attemptedAt,
277
- });
278
- }
279
- cursor.updated_at = attemptedAt;
280
- }
281
- /**
282
- * What the operator is owed after ingest accepted the envelope, as a decision
283
- * table on the evidence this sync left behind:
4
+ * This file is the delivery half of a sync. `upload-envelope.ts` assembles what
5
+ * gets sent; the siblings below are about getting it there and telling the
6
+ * truth about what happened:
284
7
  *
285
- * | evidence state | ledger |
286
- * |---------------------------------------|-------------------------------------|
287
- * | a gap a later sync could rescue | success, then a queued retry naming why |
288
- * | a gap no retry can rescue | success, then a blocker naming why, plus a stderr record |
289
- * | nothing missing | success, pending state cleared |
8
+ * - `upload-sync.ts` `syncLocalAmbientEnvelope`, the five-step sequence:
9
+ * assemble the envelope (or record why this machine cannot upload at all),
10
+ * deliver raw evidence objects, POST to ingest, advance the cursor and write
11
+ * the ledger row, and on any failure spool a retry that carries its own
12
+ * reason.
13
+ * - `upload-ingest-receipt.ts` — POST the envelope and refuse to believe a
14
+ * receipt we cannot read: `postEnvelopeToIngest` and the row-count proof
15
+ * behind it.
290
16
  *
291
- * The middle row is the one that matters. A permanently rejected object gets no
292
- * retry nothing would change but it must still be named, or `cockpit status`
293
- * reads clean on a machine with missing collection (BLI-2528).
294
- */
295
- async function recordIngestedSyncOutcome(options) {
296
- const { paths, built, uploadOutcomes, attemptedAt } = options;
297
- const retryableEvidenceGap = hasRetryableEvidenceGap(built.raw_evidence_facts, uploadOutcomes);
298
- await recordUploadSuccess(paths, {
299
- attemptedAt,
300
- workContextId: built.envelope.work_context.work_context_id,
301
- clearPendingForContext: !retryableEvidenceGap,
302
- });
303
- if (retryableEvidenceGap) {
304
- await recordUploadFailure(paths, {
305
- last_attempt_at: attemptedAt,
306
- dashboard_url: built.dashboard_url,
307
- work_context_id: built.envelope.work_context.work_context_id,
308
- ticket_id: built.ticket_id,
309
- repo_label: built.repo_label,
310
- branch: built.envelope.work_context.branch,
311
- event_count: built.event_count,
312
- source_scan_count: built.source_scan_count,
313
- risk_flag_count: built.risk_flag_count,
314
- raw_evidence_file_count: built.raw_evidence_upload_files.length,
315
- retry_sources: retrySourcesForFailedSync(options.options, built.raw_evidence_facts),
316
- failure_reason: retryableEvidenceGapReason(built.raw_evidence_facts, uploadOutcomes),
317
- retry_command: "cockpit sync",
318
- });
319
- return;
320
- }
321
- const permanentReason = permanentEvidenceFailureReason(uploadOutcomes);
322
- if (!permanentReason)
323
- return;
324
- await recordUploadBlocked(paths, {
325
- attemptedAt,
326
- reason: permanentReason,
327
- });
328
- logPermanentlyRejectedEvidence({
329
- reason: permanentReason,
330
- outcomes: uploadOutcomes,
331
- attemptedAt,
332
- });
333
- }
334
- /**
335
- * The receipt that proves ingest persisted exactly what was submitted.
17
+ * The whole file is built around one rule from the fleet contract: a sync that
18
+ * did not collect everything must never read green. Objects held by backoff are
19
+ * reported as named failures, permanent rejections are logged and blocked
20
+ * rather than silently retried forever, and the ledger row always says why.
336
21
  *
337
- * Anything short of a 202 whose counts match the envelope's own totals is
338
- * treated as no receipt at all a proxy's 200, a truncated body, or a
339
- * dashboard that accepted fewer rows than were sent all land here. They used to
340
- * land here under the SAME sentence, which is the BLI-3483 complaint: a
341
- * network appliance answering on the dashboard's behalf and a dashboard that
342
- * genuinely persisted three of four rows are opposite repairs. They are now two
343
- * reasons, and the second one names the field that disagreed and by how much.
344
- */
345
- function assertAmbientIngestReceipt(response, value, envelope) {
346
- if (response.status !== 202 || !value || typeof value !== "object") {
347
- const detail = response.status !== 202
348
- ? `http ${response.status}; the ingest route answers 202, so something in front of it replied`
349
- : "202 with a body that is not an object";
350
- console.error("[cockpit-sync] ingest answered without a durable receipt", JSON.stringify({
351
- reason: "ingest_receipt_not_202",
352
- http_status: response.status,
353
- body_is_object: Boolean(value) && typeof value === "object",
354
- }));
355
- throw new SyncDeliveryError("ingest_receipt_not_202", {
356
- httpStatus: response.status,
357
- detail,
358
- });
359
- }
360
- const record = value;
361
- const ingest = record["ingest"] && typeof record["ingest"] === "object"
362
- ? record["ingest"]
363
- : null;
364
- const expectedFactCount = envelope.events.length;
365
- const expectedRiskFlagCount = envelope.events.reduce((total, event) => total + event.risk_flags.length, 0);
366
- const expectedEvidenceRefCount = envelope.events.reduce((total, event) => total + event.raw_evidence_pointers.length, 0);
367
- const mismatches = [];
368
- if (record["ok"] !== true)
369
- mismatches.push("ok not true");
370
- if (!ingest)
371
- mismatches.push("no ingest block");
372
- if (ingest && !isNonEmptyString(ingest["work_session_id"])) {
373
- mismatches.push("blank work_session_id");
374
- }
375
- if (ingest) {
376
- mismatches.push(...countMismatch("fact_count", ingest["fact_count"], expectedFactCount), ...countMismatch("risk_flag_count", ingest["risk_flag_count"], expectedRiskFlagCount), ...countMismatch("evidence_ref_count", ingest["evidence_ref_count"], expectedEvidenceRefCount));
377
- }
378
- if (mismatches.length === 0)
379
- return;
380
- console.error("[cockpit-sync] ingest receipt does not match what was submitted", JSON.stringify({
381
- reason: "ingest_receipt_incomplete",
382
- http_status: response.status,
383
- mismatch_count: mismatches.length,
384
- mismatches,
385
- }));
386
- throw new SyncDeliveryError("ingest_receipt_incomplete", {
387
- httpStatus: response.status,
388
- detail: mismatches.join("; "),
389
- });
390
- }
391
- /**
392
- * `submitted 4, receipt 3` — the sentence that tells partial persistence from a
393
- * receipt that never carried the field at all. Counts are numbers this
394
- * collector computed and numbers the server echoed; neither is content.
22
+ * The public surface of the collector's upload path is re-exported below, so
23
+ * every consumer still imports from `./upload.js` regardless of which module a
24
+ * symbol now lives in.
395
25
  */
396
- function countMismatch(field, received, expected) {
397
- if (received === expected)
398
- return [];
399
- return [
400
- `${field} submitted ${expected}, receipt ${typeof received === "number" ? received : "absent"}`,
401
- ];
402
- }
26
+ export { LocalUploadBlockedError, buildLocalAmbientEnvelope, } from "./upload-envelope.js";
27
+ export { partitionHeldEvidenceFiles } from "./upload-evidence-delivery.js";
28
+ export { reportAgentImageArtifacts } from "./upload-agent-artifacts.js";
29
+ export { flushPendingCodexSessionReports, postCodexSessionReport, queueCodexSessionReport, reportCodexSessionAttributions, } from "./upload-session-reports.js";
30
+ export { syncLocalAmbientEnvelope } from "./upload-sync.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.56",
3
+ "version": "0.2.58",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,16 +18,17 @@
18
18
  "node": ">=20"
19
19
  },
20
20
  "scripts": {
21
- "prebuild": "npm run build --workspace=@bli-cockpit/local-collector",
21
+ "prebuild": "node ../../scripts/build-workspace-dep.mjs @bli-cockpit/local-collector",
22
22
  "build": "node ../../scripts/build-public-cli.mjs",
23
23
  "prepack": "npm run build",
24
- "pretypecheck": "npm run build",
24
+ "pretypecheck": "node ../../scripts/build-workspace-dep.mjs @bli-cockpit/cli",
25
25
  "typecheck": "node -e \"await import('./dist/commands/public-root.js')\"",
26
- "pretest": "npm run build",
26
+ "pretest": "node ../../scripts/build-workspace-dep.mjs @bli-cockpit/cli",
27
27
  "test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
28
28
  },
29
29
  "dependencies": {
30
- "@bli-cockpit/memory-mcp": "0.1.4",
30
+ "@bli-cockpit/memory-mcp": "0.1.6",
31
+ "@bli-cockpit/mcp": "0.1.1",
31
32
  "@bli-cockpit/telemetry-core": "0.1.28"
32
33
  }
33
34
  }