@bli-cockpit/cli 0.2.7 → 0.2.9
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 +1 -1
- package/dist/agent-rules.js +24 -10
- package/dist/commands/local.js +17 -12
- package/dist/commands/public-root.js +1 -1
- package/dist/local-state.js +37 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ families before its exact version is promoted to npm `latest`.
|
|
|
64
64
|
| `cockpit sync` | Captures and uploads once, right now. This is what the background agent runs every 15 min — you almost never type it yourself. | Named for what it does: synchronize local session files up to the dashboard. |
|
|
65
65
|
| `cockpit analyze` | Runs a fresh sync, then queues batch analysis of your latest uploaded sessions. It returns immediately; status and results appear in My Work. | One command when you want newly finished work uploaded and judged now. |
|
|
66
66
|
| `cockpit start --ticket <id>` | Tags your CURRENT work with a Linear ticket so sessions attribute to it. `--clear-ticket` returns to general capture. | "Start (working on) X." Agents usually run this for you per the agent rules. |
|
|
67
|
-
| `cockpit login` / `cockpit pair` | Just the sign-in + device-registration step, standalone (onboard already includes it). Two names, one command: `login` is what humans guess, `pair` is what the dashboard's device screen calls it. | Recovery path when a session expires or you switch accounts. |
|
|
67
|
+
| `cockpit login` / `cockpit pair` | Just the sign-in + device-registration step, standalone (onboard already includes it). Works on a fresh machine too: it writes a minimal local config first (no collection roots — run `cockpit onboard` afterwards to register those). Two names, one command: `login` is what humans guess, `pair` is what the dashboard's device screen calls it. | Recovery path when a session expires or you switch accounts, or sign-in first on a brand-new machine. |
|
|
68
68
|
| `cockpit logout` | Deletes this machine's session. | The undo of login. |
|
|
69
69
|
| `cockpit update` / `cockpit upgrade` | Updates the CLI itself from npm, then reruns onboard. Two names, one command — aliases because half of people guess each. | Self-update; run when Edward announces a new version. |
|
|
70
70
|
| `cockpit install` | Writes local config only (step 1 of onboard, standalone). | Plumbing; exists so onboard's pieces are individually runnable. You'll likely never type it. |
|
package/dist/agent-rules.js
CHANGED
|
@@ -107,20 +107,25 @@ async function inspectAgentRulesForHost(host, options = {}) {
|
|
|
107
107
|
export function cockpitAgentRulesBlock(options = {}) {
|
|
108
108
|
const scopePaths = normalizeScopePaths(options);
|
|
109
109
|
const scopeLine = scopePaths.length === 1
|
|
110
|
-
? `-
|
|
110
|
+
? `- Only applies when the current working directory is inside the Cockpit-onboarded workspace/repo: \`${scopePaths[0]}\`. Outside that folder, do not run Cockpit ticket binding or sync commands for private chats or unrelated repos.`
|
|
111
111
|
: scopePaths.length > 1
|
|
112
|
-
? `-
|
|
113
|
-
: "-
|
|
112
|
+
? `- Only applies when the current working directory is inside one of these Cockpit-onboarded workspace roots: ${scopePaths.map((scopePath) => `\`${scopePath}\``).join(", ")}. Outside those folders, do not run Cockpit ticket binding or sync commands for private chats or unrelated repos.`
|
|
113
|
+
: "- Only applies when the current working directory is inside the workspace/repo that ran `cockpit onboard` or `cockpit agent-rules install`. Outside that folder, do not run Cockpit ticket binding or sync commands for private chats or unrelated repos.";
|
|
114
114
|
return [
|
|
115
115
|
MANAGED_BLOCK_START,
|
|
116
116
|
"## Cockpit Ticket Binding",
|
|
117
117
|
"",
|
|
118
118
|
scopeLine,
|
|
119
|
-
"-
|
|
120
|
-
"-
|
|
121
|
-
"-
|
|
122
|
-
"
|
|
123
|
-
"
|
|
119
|
+
"- Ticketed work (implement / debug / review / PR / ship): run `cockpit start --ticket <ticket-id> --workspace \"$PWD\"` before the first code edit (the flag is `--ticket`). No ticket ID visible → search Linear first; none exists and the work is ticket-worthy → create a narrow Linear ticket, then bind.",
|
|
120
|
+
"- Truly no ticket → say the session stays in general ambient capture; never invent one.",
|
|
121
|
+
"- After the first meaningful checkpoint, run `cockpit sync --workspace \"$PWD\" --json`.",
|
|
122
|
+
"",
|
|
123
|
+
"## Definition of done — QA receipts",
|
|
124
|
+
"",
|
|
125
|
+
"- A change a user can see or click is NOT done until a computer-use QA pass has exercised it and left receipts (screenshots or logs of the actual run). Green CI alone does not close a user-visible change.",
|
|
126
|
+
"- Computer use first, always. The human can take over the browser at any point; when that happens, stop driving, observe, and record what the human verified.",
|
|
127
|
+
"- A reviewer or QA agent spawned by the builder does not count as independent verification by itself — the receipt is what counts, not the reviewer's opinion.",
|
|
128
|
+
"- QA catches the cheap majority of bugs, not all of them. Do the pass, attach the receipts, ship; remaining bugs get fixed as normal bugs.",
|
|
124
129
|
MANAGED_BLOCK_END,
|
|
125
130
|
].join("\n");
|
|
126
131
|
}
|
|
@@ -233,16 +238,25 @@ function hasEquivalentUnmanagedTicketBinding(contents, scopePaths = []) {
|
|
|
233
238
|
return false;
|
|
234
239
|
if (!hasTicketLookupOrCreationCue(text))
|
|
235
240
|
return false;
|
|
241
|
+
// Contents without the QA-receipts rule are the older vocabulary and must
|
|
242
|
+
// read as stale — otherwise machines with a hand-written ticket-binding
|
|
243
|
+
// section never receive the definition-of-done rule.
|
|
244
|
+
if (!hasQaReceiptsCue(text))
|
|
245
|
+
return false;
|
|
236
246
|
const signals = [
|
|
237
247
|
/cockpit\s+start\s+--ticket\b/u,
|
|
238
248
|
/before\s+(?:the\s+)?first\s+code\s+edit|before\s+ticketed\s+implementation/u,
|
|
239
249
|
/general\s+ambient/u,
|
|
240
|
-
/cockpit\s+sync\s+--repo|fresh\s+ticket\/session\s+binding\s+metadata/u,
|
|
241
|
-
/use\s+--ticket|do\s+not\s+invent\s+--ticketid/u,
|
|
250
|
+
/cockpit\s+sync\s+--repo|cockpit\s+sync\s+--workspace|fresh\s+ticket\/session\s+binding\s+metadata/u,
|
|
251
|
+
/use\s+--ticket|the\s+flag\s+is\s+--ticket|do\s+not\s+invent\s+--ticketid/u,
|
|
242
252
|
];
|
|
243
253
|
const score = signals.filter((signal) => signal.test(text)).length;
|
|
244
254
|
return score >= 4;
|
|
245
255
|
}
|
|
256
|
+
function hasQaReceiptsCue(text) {
|
|
257
|
+
return /computer-use\s+qa\s+pass/u.test(text)
|
|
258
|
+
&& /receipts/u.test(text);
|
|
259
|
+
}
|
|
246
260
|
function hasTicketLookupOrCreationCue(text) {
|
|
247
261
|
return /search\s+linear|create\s+(?:a\s+)?(?:narrow\s+)?linear\s+ticket|new\s+linear\s+ticket/u.test(text);
|
|
248
262
|
}
|
package/dist/commands/local.js
CHANGED
|
@@ -9,7 +9,7 @@ import { runDoctor } from "./doctor.js";
|
|
|
9
9
|
import { inspectBackfillLock } from "../backfill-lock.js";
|
|
10
10
|
import { parseLocalArgs, normalizeUrl } from "./local-args.js";
|
|
11
11
|
import { autostartStatus, installAutostartAgent, uninstallAutostartAgent, } from "../autostart.js";
|
|
12
|
-
import { DEFAULT_DASHBOARD_URL, getCollectorRuntimePaths, inspectLocalCollectorStatus, installLocalCollector, logoutLocalCollector, pairLocalCollector, LOCAL_COLLECTOR_VERSION, readLocalCollectorConfig, readLocalCollectorSessionFile, readLocalSessionReference, startLocalWorkContext, } from "../local-state.js";
|
|
12
|
+
import { DEFAULT_DASHBOARD_URL, ensureLocalCollectorConfig, getCollectorRuntimePaths, inspectLocalCollectorStatus, installLocalCollector, logoutLocalCollector, pairLocalCollector, LOCAL_COLLECTOR_VERSION, readLocalCollectorConfig, readLocalCollectorSessionFile, readLocalSessionReference, startLocalWorkContext, } from "../local-state.js";
|
|
13
13
|
import { CODEX_ATTRIBUTION_SCAN_WINDOW_SESSION_LIMIT, CODEX_ATTRIBUTION_SCAN_WINDOW_MINUTES, defaultCodexSessionDirs, scanAndAttributeCodexSessions, } from "../adapters/codex-attribution.js";
|
|
14
14
|
import { scanAndAttributeClaudeSessions } from "../adapters/claude-attribution.js";
|
|
15
15
|
import { backfillCompletionCovers, emptyBackfillCursorState, prepareBackfillCursorForScope, readBackfillCompletionMarker, readBackfillCursor, } from "../cursors/backfill-cursor.js";
|
|
@@ -855,7 +855,7 @@ const OTP_INVALID_MESSAGE = [
|
|
|
855
855
|
" Cockpit expects digits only, length 6 to 10.",
|
|
856
856
|
"What you can do:",
|
|
857
857
|
" 1) Rerun and paste the latest email code.",
|
|
858
|
-
" 2) Use manual approval:
|
|
858
|
+
" 2) Use manual approval: rerun this command with --no-auth --email <you@buildlaunchiterate.ca>",
|
|
859
859
|
].join("\n");
|
|
860
860
|
async function resolveOnboardEmail(command, roots, config, io) {
|
|
861
861
|
if (command.claimedOwnerEmail)
|
|
@@ -1774,8 +1774,18 @@ function worktreeSyncRow(outcome, run) {
|
|
|
1774
1774
|
};
|
|
1775
1775
|
}
|
|
1776
1776
|
async function runLogin(command, io) {
|
|
1777
|
+
// Standalone `cockpit login` must work on a fresh machine: bootstrap a
|
|
1778
|
+
// minimal rootless config when onboard/install has not run yet, instead of
|
|
1779
|
+
// failing on the missing config file.
|
|
1780
|
+
const { config, paths, created } = await ensureLocalCollectorConfig({
|
|
1781
|
+
homeDir: command.homeDir,
|
|
1782
|
+
dashboardUrl: command.dashboardUrl,
|
|
1783
|
+
});
|
|
1784
|
+
const dashboardUrl = command.dashboardUrl ?? config.dashboard_url;
|
|
1785
|
+
if (created && !command.json) {
|
|
1786
|
+
writeLine(io.stdout, `First login on this machine; wrote collector config: ${paths.config_file}`);
|
|
1787
|
+
}
|
|
1777
1788
|
const claimedOwnerEmail = await resolveInteractiveLoginEmail(command, io);
|
|
1778
|
-
const dashboardUrl = await resolveLoginDashboardUrl(command);
|
|
1779
1789
|
const pairingAccessToken = await requestPairingAccessToken({
|
|
1780
1790
|
dashboardUrl,
|
|
1781
1791
|
email: claimedOwnerEmail,
|
|
@@ -1784,7 +1794,7 @@ async function runLogin(command, io) {
|
|
|
1784
1794
|
}, io);
|
|
1785
1795
|
const result = await pairLocalCollectorWithAuthFallback({
|
|
1786
1796
|
homeDir: command.homeDir,
|
|
1787
|
-
dashboardUrl
|
|
1797
|
+
dashboardUrl,
|
|
1788
1798
|
claimedOwnerEmail,
|
|
1789
1799
|
deviceName: command.deviceName,
|
|
1790
1800
|
pollIntervalMs: command.pollIntervalMs,
|
|
@@ -1808,16 +1818,11 @@ async function runLogin(command, io) {
|
|
|
1808
1818
|
writeLine(io.stdout, `Session: ${result.session_file}`);
|
|
1809
1819
|
writeLine(io.stdout, `User: ${result.session.email ?? result.session.auth_subject_id}`);
|
|
1810
1820
|
writeLine(io.stdout, `Device: ${result.session.device_name ?? result.session.device_id ?? "unknown"}`);
|
|
1811
|
-
writeLine(io.stdout,
|
|
1821
|
+
writeLine(io.stdout, config.default_repo_paths.length > 0
|
|
1822
|
+
? "Next: run `cockpit start` inside the repo."
|
|
1823
|
+
: "Next: run `cockpit onboard` from your repo root so Cockpit knows which repos to collect.");
|
|
1812
1824
|
return 0;
|
|
1813
1825
|
}
|
|
1814
|
-
async function resolveLoginDashboardUrl(command) {
|
|
1815
|
-
if (command.dashboardUrl)
|
|
1816
|
-
return command.dashboardUrl;
|
|
1817
|
-
const paths = getCollectorRuntimePaths(command.homeDir);
|
|
1818
|
-
const config = await readLocalCollectorConfig(paths);
|
|
1819
|
-
return config.dashboard_url;
|
|
1820
|
-
}
|
|
1821
1826
|
function writePairingInstructions(io, request) {
|
|
1822
1827
|
writeLine(io.stdout, "2/5 Device pairing started.");
|
|
1823
1828
|
writeLine(io.stdout, `Open: ${request.approve_url}`);
|
package/dist/local-state.js
CHANGED
|
@@ -74,12 +74,48 @@ function normalizeRepoRoots(repoRoots) {
|
|
|
74
74
|
}
|
|
75
75
|
return normalizeCollectionRoots(candidates);
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Reads the collector config, creating a minimal rootless one when the file
|
|
79
|
+
* does not exist yet (fresh machine where `cockpit onboard`/`install` has not
|
|
80
|
+
* run). Never registers collection roots: root consent stays with
|
|
81
|
+
* onboard/install. A corrupt existing config is never overwritten.
|
|
82
|
+
*/
|
|
83
|
+
export async function ensureLocalCollectorConfig(options = {}) {
|
|
84
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
85
|
+
const paths = getCollectorRuntimePaths(homeDir);
|
|
86
|
+
await ensureRuntimeDirectories(paths);
|
|
87
|
+
let existing = null;
|
|
88
|
+
try {
|
|
89
|
+
existing = await readLocalCollectorConfig(paths);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (!isMissingFileError(error)) {
|
|
93
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
94
|
+
throw new Error(`Local collector config at ${paths.config_file} is unreadable (${reason}). ` +
|
|
95
|
+
"Run `cockpit onboard` to repair it.");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (existing)
|
|
99
|
+
return { config: existing, paths, created: false };
|
|
100
|
+
const config = LocalCollectorConfigSchema.parse({
|
|
101
|
+
schema_version: "telemetry-core.v1",
|
|
102
|
+
dashboard_url: normalizeDashboardUrl(options.dashboardUrl ?? DEFAULT_DASHBOARD_URL),
|
|
103
|
+
collector_version: LOCAL_COLLECTOR_VERSION,
|
|
104
|
+
device_id: `device-${crypto.randomUUID()}`,
|
|
105
|
+
device_name: defaultDeviceName(),
|
|
106
|
+
default_repo_paths: [],
|
|
107
|
+
session_file_path: paths.session_file,
|
|
108
|
+
state_dir_path: paths.state_dir,
|
|
109
|
+
});
|
|
110
|
+
await writeJsonFile(paths.config_file, config);
|
|
111
|
+
return { config, paths, created: true };
|
|
112
|
+
}
|
|
77
113
|
export async function pairLocalCollector(options = {}) {
|
|
78
114
|
const homeDir = options.homeDir ?? os.homedir();
|
|
79
115
|
const paths = getCollectorRuntimePaths(homeDir);
|
|
80
116
|
await ensureRuntimeDirectories(paths);
|
|
81
117
|
const config = await readLocalCollectorConfig(paths).catch(() => {
|
|
82
|
-
throw new Error("Local config missing. Run `cockpit install`
|
|
118
|
+
throw new Error("Local config missing. Run `cockpit onboard` (or `cockpit install`) first, then retry `cockpit login`.");
|
|
83
119
|
});
|
|
84
120
|
const dashboardUrl = normalizeDashboardUrl(options.dashboardUrl ?? config.dashboard_url);
|
|
85
121
|
const deviceId = config.device_id ?? `device-${crypto.randomUUID()}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
"test": "node dist/cli.js --help && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@bli-cockpit/telemetry-core": "0.1.
|
|
29
|
+
"@bli-cockpit/telemetry-core": "0.1.15"
|
|
30
30
|
}
|
|
31
31
|
}
|