@bli-cockpit/cli 0.2.111 → 0.2.112
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/dist/commands/agent-session-report.js +3 -0
- package/dist/commands/local-args-tower-usage.js +6 -6
- package/dist/commands/local-help-commands-tower.js +2 -2
- package/dist/commands/local-help.js +2 -2
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/session-sync.js +10 -0
- package/dist/commands/usage.js +12 -6
- package/package.json +4 -4
|
@@ -139,6 +139,8 @@ function sessionReportEntry(result, context) {
|
|
|
139
139
|
? (context.claudePriorDurablePointers.get(result.session_id) ?? null)
|
|
140
140
|
: null;
|
|
141
141
|
return {
|
|
142
|
+
ticket_id: context.ticketByWorktree?.get(result.worktree?.worktree_fingerprint ?? "") ?? null,
|
|
143
|
+
ticket_binding_source: context.ticketByWorktree?.has(result.worktree?.worktree_fingerprint ?? "") ? "explicit_bind" : null,
|
|
142
144
|
codex_session_id: result.session_id,
|
|
143
145
|
source: result.source,
|
|
144
146
|
observed_at: context.now.toISOString(),
|
|
@@ -225,6 +227,7 @@ export function buildAgentSessionReport(options) {
|
|
|
225
227
|
const uploadOutcomes = sessionUploadOutcomesByKey(options.outcomes);
|
|
226
228
|
return [...bestByKey.values()].map((result) => sessionReportEntry(result, {
|
|
227
229
|
...uploadOutcomes,
|
|
230
|
+
ticketByWorktree: options.ticketByWorktree,
|
|
228
231
|
claudePriorDurablePointers: options.claudePriorDurablePointers,
|
|
229
232
|
now: options.now,
|
|
230
233
|
}));
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
2
2
|
export function parseUsageArgs(args) {
|
|
3
|
-
const values = parseNamedArgs(args, { allowedFlags: ["--by-subject", "--subject", "--repo", "--session", "--from-window", "--min-confidence", "--by-topic", "--by-repo", "--person", "--all", "--since", "--until", "--include-automated", "--detail", "--home", "--dashboard-url", "--json"], valueFlags: ["--subject", "--repo", "--session", "--from-window", "--min-confidence", "--person", "--since", "--until", "--home", "--dashboard-url"] });
|
|
4
|
-
if (["--by-repo", "--by-topic", "--by-subject"].filter(flag => values.booleans.has(flag)).length > 1)
|
|
5
|
-
throw new Error("--by-repo and --by-topic cannot be used together or with --by-subject.");
|
|
3
|
+
const values = parseNamedArgs(args, { allowedFlags: ["--by-task", "--task", "--by-subject", "--subject", "--repo", "--session", "--from-window", "--min-confidence", "--by-topic", "--by-repo", "--person", "--all", "--since", "--until", "--include-automated", "--detail", "--home", "--dashboard-url", "--json"], valueFlags: ["--task", "--subject", "--repo", "--session", "--from-window", "--min-confidence", "--person", "--since", "--until", "--home", "--dashboard-url"] });
|
|
4
|
+
if (["--by-task", "--by-repo", "--by-topic", "--by-subject"].filter(flag => values.booleans.has(flag)).length > 1)
|
|
5
|
+
throw new Error("--by-repo and --by-topic cannot be used together or with --by-subject or --by-task.");
|
|
6
6
|
const minConfidence = values.flags.get("--min-confidence");
|
|
7
7
|
if (minConfidence !== undefined && !["high", "medium", "low"].includes(minConfidence))
|
|
8
8
|
throw new Error("--min-confidence accepts high, medium or low.");
|
|
9
9
|
const action = values.positionals[0] ?? "people";
|
|
10
10
|
if (!["people", "sessions"].includes(action) || values.positionals.length > 1)
|
|
11
11
|
throw new Error("usage takes people or sessions.");
|
|
12
|
-
if (action === "sessions" && !values.flags.get("--subject") && !values.flags.get("--session"))
|
|
13
|
-
throw new Error("usage sessions requires --subject or --session.");
|
|
14
|
-
return { bySubject: values.booleans.has("--by-subject"), subject: optionalNonEmpty(values.flags.get("--subject")), repo: optionalNonEmpty(values.flags.get("--repo")), session: optionalNonEmpty(values.flags.get("--session")), fromWindow: optionalNonEmpty(values.flags.get("--from-window")), kind: "usage", ...(minConfidence ? { minConfidence: minConfidence } : {}), byTopic: values.booleans.has("--by-topic"), byRepo: values.booleans.has("--by-repo"), person: optionalNonEmpty(values.flags.get("--person")), all: values.booleans.has("--all"), action: action, since: optionalNonEmpty(values.flags.get("--since")) ?? "30d", until: optionalNonEmpty(values.flags.get("--until")), detail: values.booleans.has("--detail"), includeAutomated: values.booleans.has("--include-automated"), homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")), json: values.booleans.has("--json") };
|
|
12
|
+
if (action === "sessions" && !values.flags.get("--subject") && !values.flags.get("--session") && !values.flags.get("--task"))
|
|
13
|
+
throw new Error("usage sessions requires --task, --subject or --session.");
|
|
14
|
+
return { byTask: values.booleans.has("--by-task"), task: optionalNonEmpty(values.flags.get("--task")), bySubject: values.booleans.has("--by-subject"), subject: optionalNonEmpty(values.flags.get("--subject")), repo: optionalNonEmpty(values.flags.get("--repo")), session: optionalNonEmpty(values.flags.get("--session")), fromWindow: optionalNonEmpty(values.flags.get("--from-window")), kind: "usage", ...(minConfidence ? { minConfidence: minConfidence } : {}), byTopic: values.booleans.has("--by-topic"), byRepo: values.booleans.has("--by-repo"), person: optionalNonEmpty(values.flags.get("--person")), all: values.booleans.has("--all"), action: action, since: optionalNonEmpty(values.flags.get("--since")) ?? "30d", until: optionalNonEmpty(values.flags.get("--until")), detail: values.booleans.has("--detail"), includeAutomated: values.booleans.has("--include-automated"), homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")), json: values.booleans.has("--json") };
|
|
15
15
|
}
|
|
@@ -143,8 +143,8 @@ export const TOWER_COMMAND_HELP = [
|
|
|
143
143
|
[
|
|
144
144
|
"usage",
|
|
145
145
|
[
|
|
146
|
-
"Usage: cockpit usage people [--by-repo | --by-topic | --by-subject] [--min-confidence high|medium|low] [--person <email|me>] [--all] [--since <n>d|<n>h|<iso>] [--detail] [--until <iso>] [--include-automated] [--json]",
|
|
147
|
-
" cockpit usage sessions --subject <name> [--repo <label>] [--since <window>] [--json]",
|
|
146
|
+
"Usage: cockpit usage people [--by-repo | --by-topic | --by-subject | --by-task] [--min-confidence high|medium|low] [--person <email|me>] [--all] [--since <n>d|<n>h|<iso>] [--detail] [--until <iso>] [--include-automated] [--json]",
|
|
147
|
+
" cockpit usage sessions [--task <BLI-NNNN> | --subject <name>] [--repo <label>] [--since <window>] [--json]",
|
|
148
148
|
" cockpit usage sessions --session <id> [--from-window <n>] [--json]",
|
|
149
149
|
" --by-topic groups work types; --by-subject groups open subjects from session summaries.",
|
|
150
150
|
"",
|
|
@@ -104,10 +104,10 @@ export function localCommandHelp(command) {
|
|
|
104
104
|
` cockpit search "<words>" [--kind ${SEARCH_KINDS.join(",")}] [--limit <n>] [--dashboard-url <url>] [--json]`,
|
|
105
105
|
" cockpit release [--dry-run] [--skip-checks] [--no-floor] [--tag <tag>] [--access <public|restricted>] [--otp <code>]",
|
|
106
106
|
" cockpit careers [list|show <id>|rescreen <id>] [--role <slug>] [--min-score <n>] [--since <date>] [--json]",
|
|
107
|
-
" cockpit usage sessions --subject <name> [--repo <label>] [--since <window>] [--json]",
|
|
107
|
+
" cockpit usage sessions [--task <BLI-NNNN> | --subject <name>] [--repo <label>] [--since <window>] [--json]",
|
|
108
108
|
" cockpit usage sessions --session <id> [--from-window <n>] [--json]",
|
|
109
109
|
" --by-topic groups work types; --by-subject groups open subjects from session summaries.",
|
|
110
|
-
" cockpit usage people [--by-repo | --by-topic | --by-subject] [--min-confidence high|medium|low] [--person <email|me>] [--all] [--detail] [--since <n>d|<n>h|<iso>] [--until <iso>] [--include-automated] [--dashboard-url <url>] [--json]",
|
|
110
|
+
" cockpit usage people [--by-repo | --by-topic | --by-subject | --by-task] [--min-confidence high|medium|low] [--person <email|me>] [--all] [--detail] [--since <n>d|<n>h|<iso>] [--until <iso>] [--include-automated] [--dashboard-url <url>] [--json]",
|
|
111
111
|
"",
|
|
112
112
|
`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.`,
|
|
113
113
|
].join("\n");
|
|
@@ -15,7 +15,7 @@ export async function runCockpitCli(argv, io) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
18
|
-
writeLine(io?.stdout ?? process.stdout, "0.2.
|
|
18
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.112");
|
|
19
19
|
return 0;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readLocalWorkContextForRepo } from "../local-state.js";
|
|
1
2
|
/**
|
|
2
3
|
* One attributed sync pass: scan both session stores, attribute what they hold,
|
|
3
4
|
* upload it through every target worktree, write down what happened, and judge
|
|
@@ -91,7 +92,16 @@ export async function runAttributedWorktreeSync(options) {
|
|
|
91
92
|
plan,
|
|
92
93
|
scan,
|
|
93
94
|
});
|
|
95
|
+
const ticketByWorktree = new Map();
|
|
96
|
+
for (const worktree of options.worktrees) {
|
|
97
|
+
const context = await readLocalWorkContextForRepo(paths, worktree.repo_root).catch(() => null);
|
|
98
|
+
const ticket = context?.linear_ticket_id ?? context?.active_ticket_id;
|
|
99
|
+
if (ticket)
|
|
100
|
+
ticketByWorktree.set(worktree.worktree_fingerprint, ticket);
|
|
101
|
+
}
|
|
102
|
+
console.error("[session-sync] bindings read", JSON.stringify({ bound_worktrees: ticketByWorktree.size, reason: "active_work_context" }));
|
|
94
103
|
const sessions = buildAgentSessionReport({
|
|
104
|
+
ticketByWorktree,
|
|
95
105
|
codexResults: scan.codexAttribution.results,
|
|
96
106
|
claudeResults: scan.claudeAttribution.results,
|
|
97
107
|
outcomes: worktreePass.outcomes,
|
package/dist/commands/usage.js
CHANGED
|
@@ -5,6 +5,8 @@ import { writeLine } from "./cli-io.js";
|
|
|
5
5
|
export async function runUsage(command, io) {
|
|
6
6
|
const door = await openAgentDoor("usage", command, io);
|
|
7
7
|
const query = new URLSearchParams({ since: command.since });
|
|
8
|
+
if (command.byTask)
|
|
9
|
+
query.set("groupBy", "ticket");
|
|
8
10
|
if (command.bySubject)
|
|
9
11
|
query.set("groupBy", "topic");
|
|
10
12
|
if (command.minConfidence)
|
|
@@ -18,7 +20,7 @@ export async function runUsage(command, io) {
|
|
|
18
20
|
if (command.includeAutomated)
|
|
19
21
|
query.set("include_automated", "1");
|
|
20
22
|
if (command.action === "sessions") {
|
|
21
|
-
for (const [key, value] of [["subject", command.subject], ["repo", command.repo], ["session", command.session], ["fromWindow", command.fromWindow]])
|
|
23
|
+
for (const [key, value] of [["task", command.task], ["subject", command.subject], ["repo", command.repo], ["session", command.session], ["fromWindow", command.fromWindow]])
|
|
22
24
|
if (value)
|
|
23
25
|
query.set(key, value);
|
|
24
26
|
const response = await askAgentDoor(door, { path: `/api/usage/sessions?${query}`, method: "GET", label: "usage sessions", timeoutMs: 30_000 });
|
|
@@ -50,12 +52,14 @@ export async function runUsage(command, io) {
|
|
|
50
52
|
return failAgentDoor(door, "[usage]", "topic_grouping_unavailable", "The dashboard returned person totals without topic rows. Deploy the dashboard topic split before using --by-topic.");
|
|
51
53
|
if (command.bySubject && body.people?.some(person => !Array.isArray(person.subjects)))
|
|
52
54
|
return failAgentDoor(door, "[usage]", "subject_grouping_unavailable", "Deploy the dashboard subject split before using --by-subject.");
|
|
55
|
+
if (command.byTask && body.people?.some(person => !Array.isArray(person.tickets)))
|
|
56
|
+
return failAgentDoor(door, "[usage]", "ticket_grouping_unavailable", "Deploy the dashboard task split before using --by-task.");
|
|
53
57
|
if (door.json)
|
|
54
58
|
return emitAgentDoor(door, body);
|
|
55
59
|
let nameWidth = 20;
|
|
56
|
-
if (command.byRepo || command.byTopic) {
|
|
60
|
+
if (command.byTask || command.byRepo || command.byTopic) {
|
|
57
61
|
for (const person of body.people ?? []) {
|
|
58
|
-
const repos = (command.byTopic ? person.task_types : person.repos) ?? [];
|
|
62
|
+
const repos = (command.byTask ? person.tickets : command.byTopic ? person.task_types : person.repos) ?? [];
|
|
59
63
|
for (const repo of command.all ? repos : repos.slice(0, 10))
|
|
60
64
|
nameWidth = Math.max(nameWidth, repo.repo_label.length + 2);
|
|
61
65
|
}
|
|
@@ -84,15 +88,17 @@ export async function runUsage(command, io) {
|
|
|
84
88
|
for (const sub of subject.children ?? [])
|
|
85
89
|
printRow([` ${sub.subtopic}`, formatCount(sub.tokens), formatUsageDollars(sub.api_list_price_equivalent_usd), `${sub.extracted_sessions}/${sub.sessions}`, ...(command.detail ? [sub.output, sub.input, sub.cache_read, sub.cache_creation].map(formatCount) : [])]);
|
|
86
90
|
}
|
|
87
|
-
if (command.byRepo || command.byTopic) {
|
|
88
|
-
const repos = (command.byTopic ? row.task_types : row.repos) ?? [];
|
|
91
|
+
if (command.byTask || command.byRepo || command.byTopic) {
|
|
92
|
+
const repos = (command.byTask ? row.tickets : command.byTopic ? row.task_types : row.repos) ?? [];
|
|
89
93
|
for (const repo of command.all ? repos : repos.slice(0, 10)) {
|
|
94
|
+
if (command.byTask)
|
|
95
|
+
writeLine(io.stdout, ` ${repo.ticket_title ?? ""} Sources: ${Object.entries(repo.binding_sources ?? {}).map(([source, count]) => `${source} ${count}`).join(", ")}`);
|
|
90
96
|
if (command.byTopic && repo.confidence)
|
|
91
97
|
writeLine(io.stdout, ` Confidence: ${repo.confidence.high} high, ${repo.confidence.medium} medium, ${repo.confidence.low} low`);
|
|
92
98
|
printRow([` ${repo.repo_label}`, formatCount(repo.tokens), formatUsageDollars(repo.api_list_price_equivalent_usd), `${repo.extracted_sessions}/${repo.sessions}`, ...(command.detail ? [repo.output, repo.input, repo.cache_read, repo.cache_creation].map(formatCount) : [])]);
|
|
93
99
|
}
|
|
94
100
|
if (!command.all && repos.length > 10)
|
|
95
|
-
writeLine(io.stdout, ` and ${repos.length - 10} more ${command.byTopic ? "topics" : "repos"}`);
|
|
101
|
+
writeLine(io.stdout, ` and ${repos.length - 10} more ${command.byTask ? "tasks" : command.byTopic ? "topics" : "repos"}`);
|
|
96
102
|
}
|
|
97
103
|
}
|
|
98
104
|
writeLine(io.stdout, "");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.112",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,8 +27,8 @@
|
|
|
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-runtime-files.mjs && node ../../scripts/assert-public-cli-exit-contract.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.
|
|
31
|
-
"@bli-cockpit/mcp": "0.1.
|
|
32
|
-
"@bli-cockpit/telemetry-core": "0.1.
|
|
30
|
+
"@bli-cockpit/memory-mcp": "0.1.30",
|
|
31
|
+
"@bli-cockpit/mcp": "0.1.40",
|
|
32
|
+
"@bli-cockpit/telemetry-core": "0.1.46"
|
|
33
33
|
}
|
|
34
34
|
}
|