@bli-cockpit/cli 0.1.17 → 0.1.18
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 +8 -5
- package/dist/agent-rules.js +80 -21
- package/dist/commands/local-args.js +9 -2
- package/dist/commands/local.js +36 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,17 +40,20 @@ staging, a custom dashboard, or deliberately forcing a different dashboard
|
|
|
40
40
|
pairing. Already-onboarded users update with
|
|
41
41
|
`npm install -g @bli-cockpit/cli@latest`, then run
|
|
42
42
|
`cockpit sync --workspace "$PWD" --json`. `--repo <path>` remains supported
|
|
43
|
-
for older prompts and the
|
|
43
|
+
for older prompts and the agent ticket-binding guardrail.
|
|
44
44
|
|
|
45
|
-
On machines where Codex agents will do ticketed work, install the
|
|
46
|
-
agent rule once:
|
|
45
|
+
On machines where Codex or Claude agents will do ticketed work, install the
|
|
46
|
+
user-scope agent rule once:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
49
|
cockpit agent-rules install
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
That updates `~/.codex/AGENTS.md` with the Cockpit
|
|
53
|
-
tickets before edits, or ask once when the ticket ID
|
|
52
|
+
That updates `~/.codex/AGENTS.md` and `~/.claude/CLAUDE.md` with the Cockpit
|
|
53
|
+
rule to bind known Linear tickets before edits, or ask once when the ticket ID
|
|
54
|
+
is missing. That binding starts attributing the session's work to the specific
|
|
55
|
+
ticket in Cockpit. It checks for the managed block first, so current files are
|
|
56
|
+
left unchanged and stale managed blocks are replaced.
|
|
54
57
|
|
|
55
58
|
Parent mode scans child git repos/worktrees (3 folder levels deep, up to 50
|
|
56
59
|
repos by default — tune with `--max-depth` / `--max-repos`; a warning prints
|
package/dist/agent-rules.js
CHANGED
|
@@ -4,56 +4,88 @@ import path from "node:path";
|
|
|
4
4
|
const MANAGED_BLOCK_START = "<!-- BLI_COCKPIT_AGENT_RULES:START -->";
|
|
5
5
|
const MANAGED_BLOCK_END = "<!-- BLI_COCKPIT_AGENT_RULES:END -->";
|
|
6
6
|
export async function installCodexAgentRules(options = {}) {
|
|
7
|
-
|
|
7
|
+
return installAgentRulesForHost("codex", options);
|
|
8
|
+
}
|
|
9
|
+
export async function installClaudeAgentRules(options = {}) {
|
|
10
|
+
return installAgentRulesForHost("claude", options);
|
|
11
|
+
}
|
|
12
|
+
export async function installAgentRules(options = {}) {
|
|
13
|
+
const targets = await Promise.all(targetHosts(options.hosts).map((host) => installAgentRulesForHost(host, options)));
|
|
14
|
+
return aggregateAgentRulesResult(targets);
|
|
15
|
+
}
|
|
16
|
+
async function installAgentRulesForHost(host, options = {}) {
|
|
17
|
+
const rulesFile = agentRulesFile(host, options.homeDir);
|
|
8
18
|
const block = cockpitAgentRulesBlock();
|
|
9
19
|
let existing = "";
|
|
10
20
|
let existed = true;
|
|
11
21
|
try {
|
|
12
|
-
existing = await readFile(
|
|
22
|
+
existing = await readFile(rulesFile, "utf8");
|
|
13
23
|
}
|
|
14
24
|
catch {
|
|
15
25
|
existed = false;
|
|
16
26
|
}
|
|
17
27
|
const next = upsertManagedBlock(existing, block);
|
|
18
28
|
if (next === existing) {
|
|
19
|
-
return
|
|
29
|
+
return agentRulesResult(host, rulesFile, "unchanged", block);
|
|
20
30
|
}
|
|
21
|
-
await mkdir(path.dirname(
|
|
22
|
-
await writeFile(
|
|
23
|
-
return
|
|
31
|
+
await mkdir(path.dirname(rulesFile), { recursive: true });
|
|
32
|
+
await writeFile(rulesFile, next, "utf8");
|
|
33
|
+
return agentRulesResult(host, rulesFile, existed ? "updated" : "created", block);
|
|
24
34
|
}
|
|
25
35
|
export async function uninstallCodexAgentRules(options = {}) {
|
|
26
|
-
|
|
36
|
+
return uninstallAgentRulesForHost("codex", options);
|
|
37
|
+
}
|
|
38
|
+
export async function uninstallClaudeAgentRules(options = {}) {
|
|
39
|
+
return uninstallAgentRulesForHost("claude", options);
|
|
40
|
+
}
|
|
41
|
+
export async function uninstallAgentRules(options = {}) {
|
|
42
|
+
const targets = await Promise.all(targetHosts(options.hosts).map((host) => uninstallAgentRulesForHost(host, options)));
|
|
43
|
+
return aggregateAgentRulesResult(targets);
|
|
44
|
+
}
|
|
45
|
+
async function uninstallAgentRulesForHost(host, options = {}) {
|
|
46
|
+
const rulesFile = agentRulesFile(host, options.homeDir);
|
|
27
47
|
const block = cockpitAgentRulesBlock();
|
|
28
48
|
let existing = "";
|
|
29
49
|
try {
|
|
30
|
-
existing = await readFile(
|
|
50
|
+
existing = await readFile(rulesFile, "utf8");
|
|
31
51
|
}
|
|
32
52
|
catch {
|
|
33
|
-
return
|
|
53
|
+
return agentRulesResult(host, rulesFile, "missing", block);
|
|
34
54
|
}
|
|
35
55
|
const next = removeManagedBlock(existing);
|
|
36
56
|
if (next === existing) {
|
|
37
|
-
return
|
|
57
|
+
return agentRulesResult(host, rulesFile, "missing", block);
|
|
38
58
|
}
|
|
39
|
-
await writeFile(
|
|
40
|
-
return
|
|
59
|
+
await writeFile(rulesFile, next, "utf8");
|
|
60
|
+
return agentRulesResult(host, rulesFile, "updated", block);
|
|
41
61
|
}
|
|
42
62
|
export async function inspectCodexAgentRules(options = {}) {
|
|
43
|
-
|
|
63
|
+
return inspectAgentRulesForHost("codex", options);
|
|
64
|
+
}
|
|
65
|
+
export async function inspectClaudeAgentRules(options = {}) {
|
|
66
|
+
return inspectAgentRulesForHost("claude", options);
|
|
67
|
+
}
|
|
68
|
+
export async function inspectAgentRules(options = {}) {
|
|
69
|
+
const targets = await Promise.all(targetHosts(options.hosts).map((host) => inspectAgentRulesForHost(host, options)));
|
|
70
|
+
return {
|
|
71
|
+
...aggregateAgentRulesResult(targets),
|
|
72
|
+
installed: targets.every((target) => target.installed),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async function inspectAgentRulesForHost(host, options = {}) {
|
|
76
|
+
const rulesFile = agentRulesFile(host, options.homeDir);
|
|
44
77
|
const block = cockpitAgentRulesBlock();
|
|
45
78
|
let existing = "";
|
|
46
79
|
try {
|
|
47
|
-
existing = await readFile(
|
|
80
|
+
existing = await readFile(rulesFile, "utf8");
|
|
48
81
|
}
|
|
49
82
|
catch {
|
|
50
|
-
return {
|
|
83
|
+
return { ...agentRulesResult(host, rulesFile, "missing", block), installed: false };
|
|
51
84
|
}
|
|
85
|
+
const installed = hasManagedBlock(existing);
|
|
52
86
|
return {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
block,
|
|
56
|
-
installed: hasManagedBlock(existing),
|
|
87
|
+
...agentRulesResult(host, rulesFile, installed ? "unchanged" : "missing", block),
|
|
88
|
+
installed,
|
|
57
89
|
};
|
|
58
90
|
}
|
|
59
91
|
export function cockpitAgentRulesBlock() {
|
|
@@ -61,7 +93,7 @@ export function cockpitAgentRulesBlock() {
|
|
|
61
93
|
MANAGED_BLOCK_START,
|
|
62
94
|
"## Cockpit Ticket Binding",
|
|
63
95
|
"",
|
|
64
|
-
"- For implementation, debugging, review, PR, or ship work tied to a clear Linear ticket, run `cockpit start --ticket <ticket-id> --repo \"$PWD\"` before the first code edit or mutating tool call.",
|
|
96
|
+
"- For implementation, debugging, review, PR, or ship work tied to a clear Linear ticket, run `cockpit start --ticket <ticket-id> --repo \"$PWD\"` before the first code edit or mutating tool call. This starts attributing the session's work to that specific ticket in Cockpit.",
|
|
65
97
|
"- Use `--ticket`; do not invent `--ticketId` or other flag shapes.",
|
|
66
98
|
"- If the user mentions ticketed work but no ticket ID is visible, ask once for the Linear ticket ID before editing. Agents cannot reliably infer it from context.",
|
|
67
99
|
"- If there is truly no ticket, state that the work remains in general ambient capture and do not invent a ticket.",
|
|
@@ -86,9 +118,36 @@ export function removeManagedBlock(contents) {
|
|
|
86
118
|
return contents;
|
|
87
119
|
return contents.replace(managedBlockPattern(), "").replace(/\n{3,}/gu, "\n\n").trimEnd() + "\n";
|
|
88
120
|
}
|
|
89
|
-
function
|
|
121
|
+
function agentRulesFile(host, homeDir = os.homedir()) {
|
|
122
|
+
if (host === "claude") {
|
|
123
|
+
return path.join(homeDir, ".claude", "CLAUDE.md");
|
|
124
|
+
}
|
|
90
125
|
return path.join(homeDir, ".codex", "AGENTS.md");
|
|
91
126
|
}
|
|
127
|
+
function agentRulesResult(host, rulesFile, status, block) {
|
|
128
|
+
return {
|
|
129
|
+
host,
|
|
130
|
+
status,
|
|
131
|
+
rules_file: rulesFile,
|
|
132
|
+
agents_file: rulesFile,
|
|
133
|
+
block,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function targetHosts(hosts) {
|
|
137
|
+
return hosts && hosts.length > 0 ? hosts : ["codex", "claude"];
|
|
138
|
+
}
|
|
139
|
+
function aggregateAgentRulesResult(targets) {
|
|
140
|
+
if (targets.every((target) => target.status === "unchanged")) {
|
|
141
|
+
return { status: "unchanged", targets };
|
|
142
|
+
}
|
|
143
|
+
if (targets.every((target) => target.status === "missing")) {
|
|
144
|
+
return { status: "missing", targets };
|
|
145
|
+
}
|
|
146
|
+
if (targets.every((target) => target.status === "created")) {
|
|
147
|
+
return { status: "created", targets };
|
|
148
|
+
}
|
|
149
|
+
return { status: "updated", targets };
|
|
150
|
+
}
|
|
92
151
|
function managedBlockPattern() {
|
|
93
152
|
return new RegExp(`${escapeRegExp(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegExp(MANAGED_BLOCK_END)}`, "u");
|
|
94
153
|
}
|
|
@@ -345,8 +345,8 @@ function parseAutostartArgs(args) {
|
|
|
345
345
|
}
|
|
346
346
|
function parseAgentRulesArgs(args) {
|
|
347
347
|
const values = parseNamedArgs(args, {
|
|
348
|
-
allowedFlags: ["--home", "--json"],
|
|
349
|
-
valueFlags: ["--home"],
|
|
348
|
+
allowedFlags: ["--home", "--host", "--json"],
|
|
349
|
+
valueFlags: ["--home", "--host"],
|
|
350
350
|
});
|
|
351
351
|
if (values.positionals.length > 1) {
|
|
352
352
|
throw new Error("agent-rules accepts at most one action (install|uninstall|status).");
|
|
@@ -358,10 +358,17 @@ function parseAgentRulesArgs(args) {
|
|
|
358
358
|
return {
|
|
359
359
|
kind: "agent-rules",
|
|
360
360
|
action,
|
|
361
|
+
host: parseAgentRulesHost(values.flags.get("--host")),
|
|
361
362
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
362
363
|
json: values.booleans.has("--json"),
|
|
363
364
|
};
|
|
364
365
|
}
|
|
366
|
+
function parseAgentRulesHost(value) {
|
|
367
|
+
const host = value?.trim().toLowerCase() || "all";
|
|
368
|
+
if (host === "codex" || host === "claude" || host === "all")
|
|
369
|
+
return host;
|
|
370
|
+
throw new Error("agent-rules --host must be codex, claude, or all.");
|
|
371
|
+
}
|
|
365
372
|
function parseNamedArgs(args, options) {
|
|
366
373
|
const allowed = new Set(options.allowedFlags);
|
|
367
374
|
const valueFlags = new Set(options.valueFlags);
|
package/dist/commands/local.js
CHANGED
|
@@ -2,7 +2,7 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createCollectorServer } from "../server.js";
|
|
5
|
-
import {
|
|
5
|
+
import { inspectAgentRules, installAgentRules, uninstallAgentRules, } from "../agent-rules.js";
|
|
6
6
|
import { parseLocalArgs, normalizeUrl } from "./local-args.js";
|
|
7
7
|
import { autostartStatus, installAutostartAgent, uninstallAutostartAgent } from "../autostart.js";
|
|
8
8
|
import { DEFAULT_DASHBOARD_URL, getCollectorRuntimePaths, inspectLocalCollectorStatus, installLocalCollector, logoutLocalCollector, pairLocalCollector, readLocalCollectorSessionFile, readLocalSessionReference, startLocalWorkContext } from "../local-state.js";
|
|
@@ -86,7 +86,7 @@ export function localCommandHelp(command) {
|
|
|
86
86
|
" cockpit sessions [--source codex|claude] [--workspace <path>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
87
87
|
" cockpit serve [--port <port>] [--workspace <path>]",
|
|
88
88
|
" cockpit autostart [install|uninstall|status] [--workspace <path>] [--dashboard-url <url>] [--interval-seconds <n>] [--json]",
|
|
89
|
-
" cockpit agent-rules [install|uninstall|status] [--json]",
|
|
89
|
+
" cockpit agent-rules [install|uninstall|status] [--host codex|claude|all] [--json]",
|
|
90
90
|
"",
|
|
91
91
|
`Default dashboard: ${DEFAULT_DASHBOARD_URL}. Omit --dashboard-url for normal production use; pass it only for staging/custom dashboards or to force a different pairing.`,
|
|
92
92
|
].join("\n");
|
|
@@ -208,11 +208,13 @@ function localSubcommandHelp(command) {
|
|
|
208
208
|
[
|
|
209
209
|
"agent-rules",
|
|
210
210
|
[
|
|
211
|
-
"Usage: cockpit agent-rules [install|uninstall|status] [--json]",
|
|
211
|
+
"Usage: cockpit agent-rules [install|uninstall|status] [--host codex|claude|all] [--json]",
|
|
212
212
|
"",
|
|
213
|
-
"Installs a managed Cockpit Ticket Binding block into ~/.codex/AGENTS.md
|
|
214
|
-
"
|
|
215
|
-
"
|
|
213
|
+
"Installs a managed Cockpit Ticket Binding block into ~/.codex/AGENTS.md",
|
|
214
|
+
"and ~/.claude/CLAUDE.md by default. Pass --host to manage only one.",
|
|
215
|
+
"This gives Codex and Claude agents a user-scope rule to run",
|
|
216
|
+
"`cockpit start --ticket <id>` before ticketed implementation work, or ask",
|
|
217
|
+
"once when the ticket ID is missing.",
|
|
216
218
|
"Action defaults to `install`.",
|
|
217
219
|
],
|
|
218
220
|
],
|
|
@@ -1032,39 +1034,47 @@ async function runAutostart(command, io) {
|
|
|
1032
1034
|
return result.status === "unsupported" ? 1 : 0;
|
|
1033
1035
|
}
|
|
1034
1036
|
async function runAgentRules(command, io) {
|
|
1037
|
+
const hosts = agentRuleHosts(command.host);
|
|
1035
1038
|
const result = command.action === "install"
|
|
1036
|
-
? await
|
|
1039
|
+
? await installAgentRules({ homeDir: command.homeDir, hosts })
|
|
1037
1040
|
: command.action === "uninstall"
|
|
1038
|
-
? await
|
|
1039
|
-
: await
|
|
1041
|
+
? await uninstallAgentRules({ homeDir: command.homeDir, hosts })
|
|
1042
|
+
: await inspectAgentRules({ homeDir: command.homeDir, hosts });
|
|
1040
1043
|
if (command.json) {
|
|
1041
1044
|
writeLine(io.stdout, JSON.stringify(result, null, 2));
|
|
1042
1045
|
return 0;
|
|
1043
1046
|
}
|
|
1044
1047
|
if ("installed" in result) {
|
|
1045
1048
|
writeLine(io.stdout, result.installed
|
|
1046
|
-
? "Cockpit
|
|
1047
|
-
: "Cockpit
|
|
1049
|
+
? "Cockpit agent rules are installed."
|
|
1050
|
+
: "Cockpit agent rules are not installed.");
|
|
1048
1051
|
}
|
|
1049
1052
|
else {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
case "updated":
|
|
1055
|
-
writeLine(io.stdout, "Cockpit Codex agent rules updated.");
|
|
1056
|
-
break;
|
|
1057
|
-
case "unchanged":
|
|
1058
|
-
writeLine(io.stdout, "Cockpit Codex agent rules already current.");
|
|
1059
|
-
break;
|
|
1060
|
-
case "missing":
|
|
1061
|
-
writeLine(io.stdout, "Cockpit Codex agent rules were not installed.");
|
|
1062
|
-
break;
|
|
1063
|
-
}
|
|
1053
|
+
writeLine(io.stdout, agentRulesStatusLine(result));
|
|
1054
|
+
}
|
|
1055
|
+
for (const target of result.targets) {
|
|
1056
|
+
writeLine(io.stdout, `${agentRuleHostLabel(target.host)}: ${target.rules_file}`);
|
|
1064
1057
|
}
|
|
1065
|
-
writeLine(io.stdout, `AGENTS.md: ${result.agents_file}`);
|
|
1066
1058
|
return 0;
|
|
1067
1059
|
}
|
|
1060
|
+
function agentRuleHosts(host) {
|
|
1061
|
+
return host === "all" ? ["codex", "claude"] : [host];
|
|
1062
|
+
}
|
|
1063
|
+
function agentRulesStatusLine(result) {
|
|
1064
|
+
switch (result.status) {
|
|
1065
|
+
case "created":
|
|
1066
|
+
return "Cockpit agent rules installed.";
|
|
1067
|
+
case "updated":
|
|
1068
|
+
return "Cockpit agent rules updated.";
|
|
1069
|
+
case "unchanged":
|
|
1070
|
+
return "Cockpit agent rules already current.";
|
|
1071
|
+
case "missing":
|
|
1072
|
+
return "Cockpit agent rules were not installed.";
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
function agentRuleHostLabel(host) {
|
|
1076
|
+
return host === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
1077
|
+
}
|
|
1068
1078
|
function writeAutostartResult(io, result) {
|
|
1069
1079
|
switch (result.status) {
|
|
1070
1080
|
case "installed":
|