@bli-cockpit/cli 0.1.5 → 0.1.6
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/README.md +5 -2
- package/dist/commands/local.js +67 -17
- package/dist/local-state.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,9 +33,12 @@ npm exec --yes --package=@bli-cockpit/cli@latest -- cockpit onboard \
|
|
|
33
33
|
--repo "$PWD"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Parent mode scans child git repos/worktrees
|
|
36
|
+
Parent mode scans child git repos/worktrees (3 folder levels deep, up to 50
|
|
37
|
+
repos by default — tune with `--max-depth` / `--max-repos`; a warning prints
|
|
38
|
+
if the repo cap truncates discovery), creates one stable work context per
|
|
37
39
|
worktree, and the dashboard rolls them up under one repo row with worktree
|
|
38
|
-
drilldown.
|
|
40
|
+
drilldown. Repos cloned later are picked up automatically: `cockpit sync`
|
|
41
|
+
starts a general ambient work context for newly discovered repos on its own. Device approval is still one time per laptop/dashboard/email. Codex
|
|
39
42
|
JSONL transcripts are attributed to a repo/worktree deterministically (session
|
|
40
43
|
cwd, workspace roots, git origin/branch/commit signals) and upload only into
|
|
41
44
|
that repo's evidence lane; transcripts that cannot be attributed to exactly one
|
package/dist/commands/local.js
CHANGED
|
@@ -2,7 +2,7 @@ import os from "node:os";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createCollectorServer } from "../server.js";
|
|
4
4
|
import { DEFAULT_DASHBOARD_URL, getCollectorRuntimePaths, inspectLocalCollectorStatus, installLocalCollector, logoutLocalCollector, pairLocalCollector, readLocalCollectorSessionFile, readLocalSessionReference, startLocalWorkContext, } from "../local-state.js";
|
|
5
|
-
import { postCodexSessionReport, syncLocalAmbientEnvelope, } from "../upload.js";
|
|
5
|
+
import { LocalUploadBlockedError, postCodexSessionReport, syncLocalAmbientEnvelope, } from "../upload.js";
|
|
6
6
|
import { scanAndAttributeCodexSessions, } from "../adapters/codex-attribution.js";
|
|
7
7
|
import { countStaleSessions, readRawEvidenceCursor, recordSessionObservation, writeRawEvidenceCursor, } from "../cursors/raw-evidence-cursor.js";
|
|
8
8
|
import { discoverGitWorktrees, } from "../repo-identity.js";
|
|
@@ -61,14 +61,14 @@ export function localCommandHelp(command) {
|
|
|
61
61
|
if (command)
|
|
62
62
|
return localSubcommandHelp(command);
|
|
63
63
|
return [
|
|
64
|
-
" cockpit onboard [--ticket <id>] [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--repo <path>] [--branch <name>] [--json]",
|
|
64
|
+
" cockpit onboard [--ticket <id>] [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--repo <path>] [--branch <name>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
65
65
|
" cockpit install [--dashboard-url <url>] [--repo <path>] [--json]",
|
|
66
66
|
" cockpit login [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--json]",
|
|
67
67
|
" cockpit pair [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--json]",
|
|
68
68
|
" cockpit logout",
|
|
69
|
-
" cockpit start [--ticket <id>] [--repo <path>] [--branch <name>] [--json]",
|
|
70
|
-
" cockpit sync [--repo <path>] [--dashboard-url <url>] [--json]",
|
|
71
|
-
" cockpit status [--repo <path>] [--json]",
|
|
69
|
+
" cockpit start [--ticket <id>] [--repo <path>] [--branch <name>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
70
|
+
" cockpit sync [--repo <path>] [--dashboard-url <url>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
71
|
+
" cockpit status [--repo <path>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
72
72
|
" cockpit serve [--port <port>] [--repo <path>]",
|
|
73
73
|
].join("\n");
|
|
74
74
|
}
|
|
@@ -127,6 +127,9 @@ function localSubcommandHelp(command) {
|
|
|
127
127
|
"Parent folders sync each child git worktree; Codex JSONL transcripts are attributed",
|
|
128
128
|
"to repos deterministically and ambiguous transcripts are retained as unattributed",
|
|
129
129
|
"instead of being duplicated across repos.",
|
|
130
|
+
"Newly discovered repos get a general ambient work context automatically.",
|
|
131
|
+
"Discovery scans 3 folder levels and up to 50 repos by default; tune with",
|
|
132
|
+
"--max-depth and --max-repos.",
|
|
130
133
|
],
|
|
131
134
|
],
|
|
132
135
|
[
|
|
@@ -191,6 +194,8 @@ function parseOnboardArgs(args) {
|
|
|
191
194
|
"--json",
|
|
192
195
|
"--poll-interval-ms",
|
|
193
196
|
"--timeout-ms",
|
|
197
|
+
"--max-depth",
|
|
198
|
+
"--max-repos",
|
|
194
199
|
],
|
|
195
200
|
valueFlags: [
|
|
196
201
|
"--home",
|
|
@@ -202,6 +207,8 @@ function parseOnboardArgs(args) {
|
|
|
202
207
|
"--branch",
|
|
203
208
|
"--poll-interval-ms",
|
|
204
209
|
"--timeout-ms",
|
|
210
|
+
"--max-depth",
|
|
211
|
+
"--max-repos",
|
|
205
212
|
],
|
|
206
213
|
});
|
|
207
214
|
assertNoPositionals(values.positionals, "onboard");
|
|
@@ -217,6 +224,8 @@ function parseOnboardArgs(args) {
|
|
|
217
224
|
json: values.booleans.has("--json"),
|
|
218
225
|
pollIntervalMs: optionalPositiveInteger(values.flags.get("--poll-interval-ms"), "--poll-interval-ms"),
|
|
219
226
|
timeoutMs: optionalPositiveInteger(values.flags.get("--timeout-ms"), "--timeout-ms"),
|
|
227
|
+
maxDepth: optionalPositiveInteger(values.flags.get("--max-depth"), "--max-depth"),
|
|
228
|
+
maxRepos: optionalPositiveInteger(values.flags.get("--max-repos"), "--max-repos"),
|
|
220
229
|
};
|
|
221
230
|
}
|
|
222
231
|
function parseInstallArgs(args) {
|
|
@@ -294,6 +303,8 @@ function parseStartArgs(args) {
|
|
|
294
303
|
"--operator-id",
|
|
295
304
|
"--session-id",
|
|
296
305
|
"--json",
|
|
306
|
+
"--max-depth",
|
|
307
|
+
"--max-repos",
|
|
297
308
|
],
|
|
298
309
|
valueFlags: [
|
|
299
310
|
"--home",
|
|
@@ -302,6 +313,8 @@ function parseStartArgs(args) {
|
|
|
302
313
|
"--ticket",
|
|
303
314
|
"--operator-id",
|
|
304
315
|
"--session-id",
|
|
316
|
+
"--max-depth",
|
|
317
|
+
"--max-repos",
|
|
305
318
|
],
|
|
306
319
|
});
|
|
307
320
|
assertNoPositionals(values.positionals, "start");
|
|
@@ -314,12 +327,14 @@ function parseStartArgs(args) {
|
|
|
314
327
|
operatorId: optionalNonEmpty(values.flags.get("--operator-id")),
|
|
315
328
|
sessionId: optionalNonEmpty(values.flags.get("--session-id")),
|
|
316
329
|
json: values.booleans.has("--json"),
|
|
330
|
+
maxDepth: optionalPositiveInteger(values.flags.get("--max-depth"), "--max-depth"),
|
|
331
|
+
maxRepos: optionalPositiveInteger(values.flags.get("--max-repos"), "--max-repos"),
|
|
317
332
|
};
|
|
318
333
|
}
|
|
319
334
|
function parseSyncArgs(args) {
|
|
320
335
|
const values = parseNamedArgs(args, {
|
|
321
|
-
allowedFlags: ["--home", "--repo", "--dashboard-url", "--json"],
|
|
322
|
-
valueFlags: ["--home", "--repo", "--dashboard-url"],
|
|
336
|
+
allowedFlags: ["--home", "--repo", "--dashboard-url", "--json", "--max-depth", "--max-repos"],
|
|
337
|
+
valueFlags: ["--home", "--repo", "--dashboard-url", "--max-depth", "--max-repos"],
|
|
323
338
|
});
|
|
324
339
|
assertNoPositionals(values.positionals, "sync");
|
|
325
340
|
return {
|
|
@@ -328,12 +343,14 @@ function parseSyncArgs(args) {
|
|
|
328
343
|
repoRoot: optionalNonEmpty(values.flags.get("--repo")),
|
|
329
344
|
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
330
345
|
json: values.booleans.has("--json"),
|
|
346
|
+
maxDepth: optionalPositiveInteger(values.flags.get("--max-depth"), "--max-depth"),
|
|
347
|
+
maxRepos: optionalPositiveInteger(values.flags.get("--max-repos"), "--max-repos"),
|
|
331
348
|
};
|
|
332
349
|
}
|
|
333
350
|
function parseStatusArgs(args) {
|
|
334
351
|
const values = parseNamedArgs(args, {
|
|
335
|
-
allowedFlags: ["--home", "--repo", "--json"],
|
|
336
|
-
valueFlags: ["--home", "--repo"],
|
|
352
|
+
allowedFlags: ["--home", "--repo", "--json", "--max-depth", "--max-repos"],
|
|
353
|
+
valueFlags: ["--home", "--repo", "--max-depth", "--max-repos"],
|
|
337
354
|
});
|
|
338
355
|
assertNoPositionals(values.positionals, "status");
|
|
339
356
|
return {
|
|
@@ -341,6 +358,8 @@ function parseStatusArgs(args) {
|
|
|
341
358
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
342
359
|
repoRoot: optionalNonEmpty(values.flags.get("--repo")),
|
|
343
360
|
json: values.booleans.has("--json"),
|
|
361
|
+
maxDepth: optionalPositiveInteger(values.flags.get("--max-depth"), "--max-depth"),
|
|
362
|
+
maxRepos: optionalPositiveInteger(values.flags.get("--max-repos"), "--max-repos"),
|
|
344
363
|
};
|
|
345
364
|
}
|
|
346
365
|
function parseServeArgs(args) {
|
|
@@ -465,7 +484,7 @@ async function runOnboard(command, io) {
|
|
|
465
484
|
writeLine(io.stdout, `Device: ${pair.session.device_name ?? pair.session.device_id ?? "unknown"}`);
|
|
466
485
|
}
|
|
467
486
|
}
|
|
468
|
-
const worktrees = await discoverCommandWorktrees(command.repoRoot);
|
|
487
|
+
const worktrees = await discoverCommandWorktrees(command.repoRoot, { maxDepth: command.maxDepth, maxRepos: command.maxRepos }, io);
|
|
469
488
|
if (worktrees.length > 1) {
|
|
470
489
|
const multi = await runMultiRepoOnboard(command, io, worktrees);
|
|
471
490
|
if (command.json) {
|
|
@@ -626,7 +645,7 @@ async function runAttributedWorktreeSync(options) {
|
|
|
626
645
|
sessionId: options.sessionId,
|
|
627
646
|
});
|
|
628
647
|
}
|
|
629
|
-
const
|
|
648
|
+
const syncOptions = {
|
|
630
649
|
homeDir: options.homeDir,
|
|
631
650
|
repoRoot: worktree.repo_root,
|
|
632
651
|
dashboardUrl: options.dashboardUrl,
|
|
@@ -639,7 +658,29 @@ async function runAttributedWorktreeSync(options) {
|
|
|
639
658
|
codex_session_id: result.codex_session_id,
|
|
640
659
|
})),
|
|
641
660
|
fetch: options.fetchImpl,
|
|
642
|
-
}
|
|
661
|
+
};
|
|
662
|
+
let sync;
|
|
663
|
+
try {
|
|
664
|
+
sync = await syncLocalAmbientEnvelope(syncOptions);
|
|
665
|
+
}
|
|
666
|
+
catch (error) {
|
|
667
|
+
// A newly cloned repo has no work context yet. Capture is permissive
|
|
668
|
+
// and ticket binding comes later, so start general ambient capture for
|
|
669
|
+
// it instead of blocking every other repo's sync until someone runs
|
|
670
|
+
// `cockpit start` by hand.
|
|
671
|
+
if (error instanceof LocalUploadBlockedError &&
|
|
672
|
+
error.blocker === "missing_context") {
|
|
673
|
+
context = await startLocalWorkContext({
|
|
674
|
+
homeDir: options.homeDir,
|
|
675
|
+
repoRoot: worktree.repo_root,
|
|
676
|
+
branch: options.branch,
|
|
677
|
+
});
|
|
678
|
+
sync = await syncLocalAmbientEnvelope(syncOptions);
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
throw error;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
643
684
|
ok = ok && sync.status === "uploaded";
|
|
644
685
|
outcomes.push({ worktree, context, sync });
|
|
645
686
|
}
|
|
@@ -795,11 +836,20 @@ function rawEvidenceSyncLine(sync) {
|
|
|
795
836
|
function cursorStatusLine(sync) {
|
|
796
837
|
return `Cursor: ${sync.cursor_tracked_object_count} durable object(s) tracked`;
|
|
797
838
|
}
|
|
798
|
-
|
|
799
|
-
|
|
839
|
+
const DEFAULT_DISCOVERY_MAX_DEPTH = 3;
|
|
840
|
+
const DEFAULT_DISCOVERY_MAX_REPOS = 50;
|
|
841
|
+
async function discoverCommandWorktrees(repoRoot, discovery = {}, io) {
|
|
842
|
+
const maxWorktrees = discovery.maxRepos ?? DEFAULT_DISCOVERY_MAX_REPOS;
|
|
843
|
+
const worktrees = await discoverGitWorktrees(repoRoot ?? process.cwd(), {
|
|
844
|
+
maxDepth: discovery.maxDepth ?? DEFAULT_DISCOVERY_MAX_DEPTH,
|
|
845
|
+
maxWorktrees,
|
|
846
|
+
});
|
|
800
847
|
if (worktrees.length === 0) {
|
|
801
848
|
throw new Error("No git repos found. Run from a git repo, or from a parent folder containing git repos.");
|
|
802
849
|
}
|
|
850
|
+
if (io && worktrees.length >= maxWorktrees) {
|
|
851
|
+
writeLine(io.stderr, `Warning: repo discovery hit the cap of ${maxWorktrees}; additional repos may be excluded. Raise --max-repos if this folder really holds more.`);
|
|
852
|
+
}
|
|
803
853
|
return worktrees;
|
|
804
854
|
}
|
|
805
855
|
async function runMultiRepoOnboard(command, io, worktrees) {
|
|
@@ -959,7 +1009,7 @@ async function runLogout(command, io) {
|
|
|
959
1009
|
return 0;
|
|
960
1010
|
}
|
|
961
1011
|
async function runStart(command, io) {
|
|
962
|
-
const worktrees = await discoverCommandWorktrees(command.repoRoot);
|
|
1012
|
+
const worktrees = await discoverCommandWorktrees(command.repoRoot, { maxDepth: command.maxDepth, maxRepos: command.maxRepos }, io);
|
|
963
1013
|
if (worktrees.length > 1) {
|
|
964
1014
|
const contexts = await Promise.all(worktrees.map((worktree) => startLocalWorkContext({
|
|
965
1015
|
homeDir: command.homeDir,
|
|
@@ -992,7 +1042,7 @@ async function runStart(command, io) {
|
|
|
992
1042
|
return 0;
|
|
993
1043
|
}
|
|
994
1044
|
async function runSync(command, io) {
|
|
995
|
-
const worktrees = await discoverCommandWorktrees(command.repoRoot);
|
|
1045
|
+
const worktrees = await discoverCommandWorktrees(command.repoRoot, { maxDepth: command.maxDepth, maxRepos: command.maxRepos }, io);
|
|
996
1046
|
const run = await runAttributedWorktreeSync({
|
|
997
1047
|
homeDir: command.homeDir,
|
|
998
1048
|
dashboardUrl: command.dashboardUrl,
|
|
@@ -1052,7 +1102,7 @@ async function runSync(command, io) {
|
|
|
1052
1102
|
return 1;
|
|
1053
1103
|
}
|
|
1054
1104
|
async function runStatus(command, io) {
|
|
1055
|
-
const worktrees = await discoverCommandWorktrees(command.repoRoot);
|
|
1105
|
+
const worktrees = await discoverCommandWorktrees(command.repoRoot, { maxDepth: command.maxDepth, maxRepos: command.maxRepos }, io);
|
|
1056
1106
|
if (worktrees.length > 1) {
|
|
1057
1107
|
const statuses = await Promise.all(worktrees.map(async (worktree) => ({
|
|
1058
1108
|
...(await inspectLocalCollectorStatus({
|
package/dist/local-state.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { resolveRepoWorktreeIdentity, } from "./repo-identity.js";
|
|
7
7
|
import { summarizeLocalUploadSpool } from "./spool/local-spool.js";
|
|
8
|
-
export const LOCAL_COLLECTOR_VERSION = "0.1.
|
|
8
|
+
export const LOCAL_COLLECTOR_VERSION = "0.1.6";
|
|
9
9
|
export const DEFAULT_DASHBOARD_URL = "http://127.0.0.1:3100";
|
|
10
10
|
export function getCollectorRuntimePaths(homeDir = os.homedir()) {
|
|
11
11
|
const paths = getUserLocalCockpitPaths(homeDir);
|