@bli-cockpit/cli 0.2.201 → 0.2.203
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/issue.js +3 -0
- package/dist/commands/local-args-tower-admin.js +15 -1
- package/dist/commands/local-args-tower-logs.js +11 -0
- package/dist/commands/local-args-tower-memory.js +7 -1
- package/dist/commands/local-args-tower-usage.js +6 -6
- package/dist/commands/local-args-tower-work.js +16 -3
- package/dist/commands/local-help-commands-tower-ops.js +2 -1
- package/dist/commands/local-help-commands-tower.js +10 -5
- package/dist/commands/local-help-commands.js +7 -1
- package/dist/commands/local-help.js +3 -2
- package/dist/commands/logs.js +10 -0
- package/dist/commands/memory-chain.js +55 -0
- package/dist/commands/memory-consolidation.js +35 -0
- package/dist/commands/memory-map.js +29 -0
- package/dist/commands/memory-rules.js +69 -23
- package/dist/commands/memory-store.js +9 -6
- package/dist/commands/memory-verb-list.js +9 -3
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/speed-pages.js +6 -9
- package/dist/commands/team.js +41 -0
- package/dist/commands/tickets-board.js +43 -0
- package/dist/commands/usage.js +7 -2
- package/package.json +4 -4
package/dist/commands/issue.js
CHANGED
|
@@ -29,12 +29,15 @@ import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor, } from "./ag
|
|
|
29
29
|
import { writeLine } from "./cli-io.js";
|
|
30
30
|
import { assigneeLabel, beginAssigneeDirectoryFetch, finishAssigneeDirectory, withAssigneeIdentity } from "./issue-directory.js";
|
|
31
31
|
import { cacheAgeLabel, cacheCoverage, readThroughCache } from "./read-cache.js";
|
|
32
|
+
import { readTicketBoard } from "./tickets-board.js";
|
|
32
33
|
import { showIssue } from "./issue-show.js";
|
|
33
34
|
import { READ_DEADLINE_MS, TAG, resolveProjectId, sizeForEstimate, withSizeLabel, } from "./issue-contracts.js";
|
|
34
35
|
import { commentIssue, createIssue, moveIssue, updateIssue } from "./issue-write.js";
|
|
35
36
|
export async function runIssue(command, io) {
|
|
36
37
|
const door = await openAgentDoor("issue", command, io);
|
|
37
38
|
switch (command.action) {
|
|
39
|
+
case "board":
|
|
40
|
+
return readTicketBoard(command, door);
|
|
38
41
|
case "list":
|
|
39
42
|
return listIssues(command, door);
|
|
40
43
|
case "show":
|
|
@@ -293,9 +293,23 @@ export function parseTeamArgs(args) {
|
|
|
293
293
|
json: values.booleans.has("--json"),
|
|
294
294
|
};
|
|
295
295
|
const [verb, ...rest] = values.positionals;
|
|
296
|
+
if (verb === "github-login") {
|
|
297
|
+
const [operation, personRef, githubLogin, ...extra] = rest;
|
|
298
|
+
if (!["add", "remove", "list"].includes(operation ?? "") || !personRef || extra.length ||
|
|
299
|
+
(operation === "list" ? githubLogin !== undefined : !githubLogin)) {
|
|
300
|
+
throw new Error("team github-login takes add|remove <person> <login>, or list <person>.");
|
|
301
|
+
}
|
|
302
|
+
return { kind: "team", action: `github-login-${operation}`, personRef, githubLogin, ...common };
|
|
303
|
+
}
|
|
296
304
|
if (verb === "device") {
|
|
297
305
|
return parseTeamDeviceArgs(rest, values.flags, common);
|
|
298
306
|
}
|
|
307
|
+
if (verb === "grant" || verb === "revoke") {
|
|
308
|
+
const [person, section, level = "see", ...extra] = rest;
|
|
309
|
+
if (!person || section !== "careers" || !["see", "act"].includes(level) || extra.length)
|
|
310
|
+
throw new Error("team grant|revoke <person uuid> careers [see|act]");
|
|
311
|
+
return { kind: "team", action: verb, targetUserId: person, section: "careers", level: level, ...common };
|
|
312
|
+
}
|
|
299
313
|
const [subject, ...extra] = rest;
|
|
300
314
|
if (extra.length > 0)
|
|
301
315
|
throw new Error("team does not accept that many arguments.");
|
|
@@ -321,7 +335,7 @@ export function parseTeamArgs(args) {
|
|
|
321
335
|
throw new Error("team role needs --role <role>.");
|
|
322
336
|
return { kind: "team", action: "role", targetUserId: subject, role, ...common };
|
|
323
337
|
}
|
|
324
|
-
throw new Error("team takes one verb: members, invite, role, or device.");
|
|
338
|
+
throw new Error("team takes one verb: members, invite, role, grant, revoke, or device.");
|
|
325
339
|
}
|
|
326
340
|
/**
|
|
327
341
|
* `cockpit team device [list] [--json]` or
|
|
@@ -22,6 +22,17 @@ export const LOGS_SOURCES = ["collector", "dashboard", "railway", "railway:<serv
|
|
|
22
22
|
const LOCAL_SOURCE_PATTERN = /^(collector|dashboard|railway|railway:[A-Za-z0-9][A-Za-z0-9._-]{0,63}|-)$/;
|
|
23
23
|
const SINCE_PATTERN = /^(\d{1,4}[smhd]|\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:\d{2})?)?)$/;
|
|
24
24
|
export function parseLogsArgs(args) {
|
|
25
|
+
if (args[0] === "keys") {
|
|
26
|
+
const action = args[1];
|
|
27
|
+
const source = args[2];
|
|
28
|
+
if ((action !== "mint" && action !== "rotate") || !source)
|
|
29
|
+
throw new Error("Use cockpit logs keys mint|rotate <source>.");
|
|
30
|
+
const values = parseNamedArgs(args.slice(3), { allowedFlags: ["--json", "--home", "--dashboard-url"], valueFlags: ["--home", "--dashboard-url"] });
|
|
31
|
+
if (values.positionals.length)
|
|
32
|
+
throw new Error("Unexpected logs keys argument.");
|
|
33
|
+
return { kind: "logs", source, keyAction: action, stored: false, list: false, allLevels: false,
|
|
34
|
+
json: values.booleans.has("--json"), homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")) };
|
|
35
|
+
}
|
|
25
36
|
const values = parseNamedArgs(args, {
|
|
26
37
|
allowedFlags: ["--home", "--since", "--raw", "--limit", "--table", "--all-levels", "--stored", "--list", "--dashboard-url", "--json"],
|
|
27
38
|
valueFlags: ["--home", "--since", "--raw", "--limit", "--table", "--dashboard-url"],
|
|
@@ -31,9 +31,11 @@
|
|
|
31
31
|
* MCP server and three hooks) and these act on Tower. `memory-store.ts` runs
|
|
32
32
|
* them; `parseMemoryArgs` in `local-args-collector-status.ts` routes to here.
|
|
33
33
|
*/
|
|
34
|
+
import { parseMemoryMapArgs } from "./memory-map.js";
|
|
34
35
|
import { parseMemoryRulesArgs } from "./memory-rules.js";
|
|
35
36
|
import { optionalNonEmpty, optionalUrl, parseNamedArgs, workRootFlagValue, } from "./local-arg-values.js";
|
|
36
37
|
export const MEMORY_STORE_ACTIONS = new Set([
|
|
38
|
+
"map",
|
|
37
39
|
"rules",
|
|
38
40
|
"knowledge",
|
|
39
41
|
"search",
|
|
@@ -50,6 +52,8 @@ export const MEMORY_STORE_ACTIONS = new Set([
|
|
|
50
52
|
// write a memory. It runs no pass and changes nothing — the pass is a
|
|
51
53
|
// Railway cron; this is how a person or an agent asks what it did.
|
|
52
54
|
"consolidation",
|
|
55
|
+
// BLI-13376: the originals behind a consolidated memory, one id.
|
|
56
|
+
"chain",
|
|
53
57
|
"requeue",
|
|
54
58
|
]);
|
|
55
59
|
const ALLOWED_FLAGS = [
|
|
@@ -89,6 +93,8 @@ const VALUE_FLAGS = [
|
|
|
89
93
|
"--source",
|
|
90
94
|
];
|
|
91
95
|
export function parseMemoryStoreArgs(action, args) {
|
|
96
|
+
if (action === "map")
|
|
97
|
+
return parseMemoryMapArgs(args);
|
|
92
98
|
if (action === "rules" || action === "knowledge")
|
|
93
99
|
return parseMemoryRulesArgs(args, action);
|
|
94
100
|
const values = parseNamedArgs(args, { allowedFlags: ALLOWED_FLAGS, valueFlags: VALUE_FLAGS });
|
|
@@ -186,7 +192,7 @@ export function parseMemoryStoreArgs(action, args) {
|
|
|
186
192
|
if (action === "consolidation") {
|
|
187
193
|
return { ...command, action };
|
|
188
194
|
}
|
|
189
|
-
if (action === "private" || action === "public") {
|
|
195
|
+
if (action === "private" || action === "public" || action === "chain") {
|
|
190
196
|
// BLI-12927: one memory, named by the id `cockpit memory search` printed.
|
|
191
197
|
const memoryId = optionalNonEmpty(rest[0]);
|
|
192
198
|
if (!memoryId) {
|
|
@@ -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-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"] });
|
|
3
|
+
const values = parseNamedArgs(args, { allowedFlags: ["--by-task", "--task", "--by-subject", "--subject", "--tool", "--cursor", "--limit", "--repo", "--session", "--from-window", "--min-confidence", "--by-topic", "--by-repo", "--person", "--all", "--since", "--until", "--include-automated", "--detail", "--home", "--dashboard-url", "--json"], valueFlags: ["--task", "--subject", "--tool", "--cursor", "--limit", "--repo", "--session", "--from-window", "--min-confidence", "--person", "--since", "--until", "--home", "--dashboard-url"] });
|
|
4
4
|
if (["--by-task", "--by-repo", "--by-topic", "--by-subject"].filter(flag => values.booleans.has(flag)).length > 1)
|
|
5
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
|
-
if (!["people", "sessions"].includes(action) || values.positionals.length > 1)
|
|
11
|
-
throw new Error("usage takes people or
|
|
12
|
-
if (action === "
|
|
13
|
-
throw new Error("usage
|
|
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") };
|
|
10
|
+
if (!["people", "sessions", "session"].includes(action) || values.positionals.length > (action === "session" ? 2 : 1))
|
|
11
|
+
throw new Error("usage takes people, sessions or session <id>.");
|
|
12
|
+
if (action === "session" && !values.positionals[1])
|
|
13
|
+
throw new Error("usage session requires an id.");
|
|
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")), tool: optionalNonEmpty(values.flags.get("--tool")), cursor: optionalNonEmpty(values.flags.get("--cursor")), limit: optionalNonEmpty(values.flags.get("--limit")), session: action === "session" ? values.positionals[1] : 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 === "session" ? "sessions" : 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
|
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, } from "./local-arg-values.js";
|
|
19
19
|
import { estimateForSize } from "./issue-contracts.js";
|
|
20
20
|
const ISSUE_ACTIONS = new Set([
|
|
21
|
+
"board",
|
|
21
22
|
"list",
|
|
22
23
|
"show",
|
|
23
24
|
"create",
|
|
@@ -52,6 +53,8 @@ export function parseIssueArgs(args) {
|
|
|
52
53
|
"--state",
|
|
53
54
|
"--assignee",
|
|
54
55
|
"--project",
|
|
56
|
+
"--label",
|
|
57
|
+
"--rows",
|
|
55
58
|
"--limit",
|
|
56
59
|
"--title",
|
|
57
60
|
"--priority",
|
|
@@ -67,6 +70,8 @@ export function parseIssueArgs(args) {
|
|
|
67
70
|
"--state",
|
|
68
71
|
"--assignee",
|
|
69
72
|
"--project",
|
|
73
|
+
"--label",
|
|
74
|
+
"--rows",
|
|
70
75
|
"--limit",
|
|
71
76
|
"--title",
|
|
72
77
|
"--priority",
|
|
@@ -80,6 +85,12 @@ export function parseIssueArgs(args) {
|
|
|
80
85
|
validateIssueState(moveState, "issue move state must be one of: ");
|
|
81
86
|
const stateFilter = readIssueStateFilter(action, values);
|
|
82
87
|
const title = readIssueTitle(action, values);
|
|
88
|
+
if (action !== "board" && (values.flags.has("--label") || values.flags.has("--rows"))) {
|
|
89
|
+
throw new Error("--label and --rows filter cockpit tickets board only.");
|
|
90
|
+
}
|
|
91
|
+
const boardProject = optionalNonEmpty(values.flags.get("--project"));
|
|
92
|
+
if (action === "board" && boardProject && !UUID_PATTERN.test(boardProject))
|
|
93
|
+
throw new Error("board --project requires a uuid.");
|
|
83
94
|
const priority = readIssuePriority(values);
|
|
84
95
|
const estimate = readIssueSize(values);
|
|
85
96
|
const assignee = readIssueAssignee(action, values);
|
|
@@ -90,7 +101,7 @@ function readIssueAction(values) {
|
|
|
90
101
|
const first = values.positionals[0];
|
|
91
102
|
const action = (first === undefined ? "list" : first);
|
|
92
103
|
if (!ISSUE_ACTIONS.has(action)) {
|
|
93
|
-
throw new Error(`Unknown issue command: ${first}. Try list, show, create, update, move, comment, or history.`);
|
|
104
|
+
throw new Error(`Unknown issue command: ${first}. Try board, list, show, create, update, move, comment, or history.`);
|
|
94
105
|
}
|
|
95
106
|
return { action, rest: values.positionals.slice(first === undefined ? 0 : 1) };
|
|
96
107
|
}
|
|
@@ -137,7 +148,7 @@ function validateIssueState(state, errorPrefix) {
|
|
|
137
148
|
function readIssueStateFilter(action, values) {
|
|
138
149
|
const stateFilter = optionalNonEmpty(values.flags.get("--state"));
|
|
139
150
|
validateIssueState(stateFilter, "--state must be one of: ");
|
|
140
|
-
if (stateFilter !== undefined && action !== "list") {
|
|
151
|
+
if (stateFilter !== undefined && action !== "list" && action !== "board") {
|
|
141
152
|
throw new Error("--state filters `cockpit issue list`; to move an issue use `cockpit issue move <id> <state>`.");
|
|
142
153
|
}
|
|
143
154
|
return stateFilter;
|
|
@@ -173,7 +184,7 @@ function readIssueAssignee(action, values) {
|
|
|
173
184
|
if (assignee !== undefined && assignee !== "me" && assignee !== "unassigned" && !UUID_PATTERN.test(assignee)) {
|
|
174
185
|
throw new Error('--assignee must be "me", "unassigned", or a person\'s uuid (see `cockpit team`).');
|
|
175
186
|
}
|
|
176
|
-
if (assignee === "unassigned" && action !== "list") {
|
|
187
|
+
if (assignee === "unassigned" && action !== "list" && action !== "board") {
|
|
177
188
|
throw new Error('--assignee unassigned filters `cockpit issue list`; to clear an assignee pass --assignee "" is not supported yet.');
|
|
178
189
|
}
|
|
179
190
|
return assignee;
|
|
@@ -196,6 +207,8 @@ function buildIssueCommand(values, action, issueRef, issueRefs, moveState, state
|
|
|
196
207
|
...(moveState ? { moveState } : {}),
|
|
197
208
|
...(stateFilter ? { stateFilter } : {}),
|
|
198
209
|
...(assignee ? { assignee } : {}),
|
|
210
|
+
label: optionalNonEmpty(values.flags.get("--label")),
|
|
211
|
+
rows: optionalNonEmpty(values.flags.get("--rows")),
|
|
199
212
|
project: optionalNonEmpty(values.flags.get("--project")),
|
|
200
213
|
...(limit === undefined ? {} : { limit }),
|
|
201
214
|
title,
|
|
@@ -41,7 +41,8 @@ export const TOWER_OPS_COMMAND_HELP = [
|
|
|
41
41
|
"project unknown_project, since_exceeds_retention, stored_read_failed, not_paired, ...",
|
|
42
42
|
"--json writes one object to stdout: { ok, kind, source, window, lines, templates[], routine, notes, text, measure };",
|
|
43
43
|
"a refusal is { ok: false, error, detail } and exits 1. Run list and teaching: docs/runbooks/tower-logs.md.",
|
|
44
|
-
"
|
|
44
|
+
"cockpit logs keys mint|rotate <source> [--json]: super admin only; prints the source ingest key once. Rotation invalidates the old key.",
|
|
45
|
+
"The bli-tower MCP server has the twins: logs_digest and logs_key_mint.",
|
|
45
46
|
],
|
|
46
47
|
],
|
|
47
48
|
[
|
|
@@ -93,10 +93,11 @@ export const TOWER_COMMAND_HELP = [
|
|
|
93
93
|
[
|
|
94
94
|
"issue",
|
|
95
95
|
[
|
|
96
|
-
"Usage: cockpit issue [list|show <id> [<id>...]|create|update <id>|move <id> <state>|comment <id>|history <id>] [flags]",
|
|
96
|
+
"Usage: cockpit issue [board|list|show <id> [<id>...]|create|update <id>|move <id> <state>|comment <id>|history <id>] [flags]",
|
|
97
97
|
"",
|
|
98
98
|
"Tower's issue tracker. <id> is a BLI-#### identifier (BLI-3654) or an issue's uuid — both work everywhere an issue is named.",
|
|
99
99
|
"list [--state <s>] [--assignee me|unassigned|<uuid>] [--project <name|id>] [--limit <n>] — the issues you may see, most recently updated first. Each row carries assignee_name/assignee_email alongside the bare assignee_id (BLI-10450, one extra `cockpit team members` call for the whole page, never per row), and a `size` label (S/M/L) alongside the raw `estimate` when it matches Tower's own scale (BLI-10575). --json shape: { issues: [{ id, identifier, title, state, priority, estimate, size, assignee_id, assignee_name, assignee_email, project_id, parent_id, updated_at }] }.",
|
|
100
|
+
"board [--json] [--state <s>] [--assignee me|unassigned|<uuid>] [--project <uuid>] [--label <name>] [--rows <state:n,...>] — the Tickets board, counts, coverage and top five cards per column, through GET /api/work/board. Also cockpit tickets board.",
|
|
100
101
|
"show <id> [<id>...] — one or more issues: title, state, priority, size, assignee (name and email, not just its id), description, and every comment on each. Several ids read in one call — one bad id never loses the rest.",
|
|
101
102
|
" --json shape: one id answers { issue, comments }, unchanged from before BLI-10450 (plus `estimate`/`size`, BLI-10575). Several ids answer { issues: [{ ref, ok, issue, comments } | { ref, ok: false, error, detail }] }.",
|
|
102
103
|
"create --title \"<title>\" [--project <name|id>] [--priority 0-4] [--size S|M|L] [--assignee me|<uuid>] [--parent <id>] [--file <path>] — the DESCRIPTION comes from --file or stdin (`cat plan.md | cockpit issue create --title \"...\"`); neither means no description. --size maps onto the existing `estimate` column, Tower's own scale: S=1, M=3, L=5 (BLI-10575) — an estimate outside that scale (an imported Linear point value, say) just has no size label on a read. --json shape: { issue }.",
|
|
@@ -189,7 +190,7 @@ export const TOWER_COMMAND_HELP = [
|
|
|
189
190
|
"(nearest rank). Also the HTTP status, the response size and the door's own Server-Timing total.",
|
|
190
191
|
"Slowest first. A page your account may not open (Careers for a member) reads \"not permitted\", not as a failure.",
|
|
191
192
|
"--page <name> limits the run to one page; repeat it for several. Names: today, meeting-notes, careers, scout,",
|
|
192
|
-
"messages, mail, docs, memory, tickets, setup, usage, calendar, workbook, settings.",
|
|
193
|
+
"messages, mail, docs, memory-knowledge, memory-rules, tickets, tickets-list, setup, usage, calendar, workbook, settings.",
|
|
193
194
|
"--runs <n> sets the requests per page (default 5, at most 50).",
|
|
194
195
|
"--json writes one object to stdout ({ ok, runs, pages: [...] }); every log line stays on stderr.",
|
|
195
196
|
"It times the DATA DOOR the page opens on, not the page's HTML or paint, and it never uses the local read cache.",
|
|
@@ -207,8 +208,8 @@ export const TOWER_COMMAND_HELP = [
|
|
|
207
208
|
"usage",
|
|
208
209
|
[
|
|
209
210
|
"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]",
|
|
210
|
-
" cockpit usage sessions [--task <BLI-NNNN> | --subject <name>] [--repo <label>] [--since <window>] [--json]",
|
|
211
|
-
" cockpit usage
|
|
211
|
+
" cockpit usage sessions [--person <exact-name>] [--task <BLI-NNNN> | --subject <name>] [--repo <label>] [--tool claude|codex] [--since <window>] [--until <iso>] [--cursor <cursor>] [--limit <n>] [--json]",
|
|
212
|
+
" cockpit usage session <id> [--from-window <n>] [--json]",
|
|
212
213
|
" --by-topic groups work types; --by-subject groups open subjects from session summaries.",
|
|
213
214
|
"",
|
|
214
215
|
"--by-topic adds topic rows, including (unlabelled). Choose only one grouping. --by-repo adds project rows under each person (top 10; --all shows every repo). --person me uses your signed-in email.",
|
|
@@ -406,8 +407,10 @@ export const TOWER_COMMAND_HELP = [
|
|
|
406
407
|
[
|
|
407
408
|
"memory",
|
|
408
409
|
[
|
|
409
|
-
"Usage: cockpit memory [rules|search|save|update|forget|private|public|profile|consolidation|requeue|install|status|log] [flags]",
|
|
410
|
+
"Usage: cockpit memory [map|knowledge|rules|search|save|update|forget|private|public|profile|consolidation|chain|requeue|install|status|log] [flags]",
|
|
410
411
|
"",
|
|
412
|
+
" map [--json] — projects, topics, memory counts and importance from the shared map.",
|
|
413
|
+
" knowledge [--person <name>] [--project <name>] [--by-project] [--limit <n>] [--json] — ranked project ideas.",
|
|
411
414
|
" rules [--person <name>] [--project <name>] [--limit <n>] [--json] — standing rules grouped by person.",
|
|
412
415
|
"The STORE, over the same /api/memory/* doors the bli-memory MCP server calls:",
|
|
413
416
|
" search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent]",
|
|
@@ -432,6 +435,8 @@ export const TOWER_COMMAND_HELP = [
|
|
|
432
435
|
" Clusters looked at, edits by verb, refusals by reason, model and cost.",
|
|
433
436
|
" Read-only: the pass is a nightly cron, it never deletes a memory, and",
|
|
434
437
|
" report mode changes nothing on the shelf at all.",
|
|
438
|
+
" chain <id> the originals behind a consolidated memory (\"from 4 memories by Edward, Vee\").",
|
|
439
|
+
" Private originals show only to their owner or a super admin; the rest are counted.",
|
|
435
440
|
" requeue --since <iso> [--until <iso>] [--source hook|harvest] [--reason <label>] [--apply]",
|
|
436
441
|
" puts finished extractions back in the queue after the store refused their",
|
|
437
442
|
" writes. Super admins only. A DRY RUN unless you pass --apply, because an",
|
|
@@ -184,15 +184,21 @@ export function localSubcommandHelp(command) {
|
|
|
184
184
|
[
|
|
185
185
|
"team",
|
|
186
186
|
[
|
|
187
|
-
"Usage: cockpit team [members | invite <email> --role <role> | role <userId> --role <role> | device [list] | device revoke <id|name> --reason <label>] [--json]",
|
|
187
|
+
"Usage: cockpit team [members | invite <email> --role <role> | role <userId> --role <role> | grant|revoke <person> careers [see|act] | device [list] | device revoke <id|name> --reason <label>] [--json]",
|
|
188
188
|
"",
|
|
189
189
|
" cockpit team [members]",
|
|
190
190
|
" Everyone on your team — roster and accounts together, with each person's id.",
|
|
191
191
|
" Open to every role; it says so when the view is read-only for you.",
|
|
192
|
+
" cockpit team github-login add|remove <person> <login> [--json] — exact roster UUID, name or email; super admin or a member managing their own extra logins. History stays.",
|
|
193
|
+
" cockpit team github-login list <person> [--json] — primary and extra GitHub logins.",
|
|
192
194
|
" cockpit team invite <email> --role <role> [--team-id <uuid>]",
|
|
193
195
|
" Admin only. Defaults to your own team; Tower emails them a sign-in link.",
|
|
194
196
|
" cockpit team role <userId> --role <role> [--yes]",
|
|
195
197
|
" Admin only. Asks first unless --yes. You cannot change your own role.",
|
|
198
|
+
" cockpit team grant <person-uuid> careers [see|act] [--json]",
|
|
199
|
+
" cockpit team revoke <person-uuid> careers [see|act] [--json]",
|
|
200
|
+
" Super admins manage separate See and Act grants. Default level: see.",
|
|
201
|
+
" Find the account uuid with cockpit team members --json. Revocation retains history.",
|
|
196
202
|
" cockpit team device [list] [--json]",
|
|
197
203
|
" cockpit team device doctor <id|name> [--json]",
|
|
198
204
|
" Every paired collector device: name, id, trust state, owner, and why",
|
|
@@ -113,6 +113,7 @@ export function localCommandHelp(command) {
|
|
|
113
113
|
" cockpit ops [status [--job <id>] [--skips] [--memory] [--models] | recompile --person <email|name|id> [--dry-run]] [--dashboard-url <url>] [--json]",
|
|
114
114
|
" cockpit slack [connect [--workspace bli|blue_pearl] | disconnect --workspace bli|blue_pearl | coverage [--workspace bli|blue_pearl] [--stale-only] [--list] | read [--person <p>] [--channel <c>] [--query <text> [--word]] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--format compact|full]] [--json]",
|
|
115
115
|
" cockpit settings [personal [--chat-model <key>] [--brief-model <key>] | switches [set <key> <value>] | models [set --chat <key>] [--memory <id>] | cli-floor [<version>] | env list|set --project <p> --file <f> --content-stdin|delete --id <uuid> [--yes]] [--json]",
|
|
116
|
+
" cockpit team github-login add|remove <person> <login> | list <person> [--json]",
|
|
116
117
|
" cockpit team [members | invite <email> --role <role> [--team-id <uuid>] | role <userId> --role <role> [--yes] | device [list] | device revoke <id|name> --reason <label> [--note \"<text>\"] [--yes]] [--json]",
|
|
117
118
|
" cockpit workbook [<project> [<doc>]] [--section <id>] [--markdown] [--width <n>] [--dashboard-url <url>] [--json]",
|
|
118
119
|
" cockpit brief [edit|rewrite|history] [--for <person>] [--date <YYYY-MM-DD>] [--delta [--against <YYYY-MM-DD>]] [--version <pageId>] [--tldr|--full] [--versions] [--claims] [--days <n>] [--reason \"<why>\"] [--wait|--no-wait] [--dashboard-url <url>] [--json]",
|
|
@@ -145,8 +146,8 @@ export function localCommandHelp(command) {
|
|
|
145
146
|
" cockpit logs <collector|dashboard|railway|railway:<service>|-> [--since 15m|1h|2d|<iso>] [--raw <template-id> [--limit <n>]] [--all-levels] [--table <file>] [--json]",
|
|
146
147
|
" cockpit brain status [--project <id>] [--show open|running|needs_human|fixed|not_actionable|cleared] [--dashboard-url <url>] [--json]",
|
|
147
148
|
" cockpit setup [--since <7d|30d|date>] [--details] [--person <name|email>] [--compare <name,name>] [--json]",
|
|
148
|
-
" cockpit usage sessions [--task <BLI-NNNN> | --subject <name>] [--repo <label>] [--since <window>] [--json]",
|
|
149
|
-
" cockpit usage
|
|
149
|
+
" cockpit usage sessions [--person <exact-name>] [--task <BLI-NNNN> | --subject <name>] [--repo <label>] [--tool claude|codex] [--since <window>] [--until <iso>] [--cursor <cursor>] [--limit <n>] [--json]",
|
|
150
|
+
" cockpit usage session <id> [--from-window <n>] [--json]",
|
|
150
151
|
" --by-topic groups work types; --by-subject groups open subjects from session summaries.",
|
|
151
152
|
" cockpit ticket [same verbs and flags as cockpit issue] (the Tickets page; projects: `cockpit project list`)",
|
|
152
153
|
" cockpit tickets ... same as cockpit ticket",
|
package/dist/commands/logs.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { readFile } from "node:fs/promises";
|
|
16
16
|
import { ClassTable, LOCAL_ACCESS_REFUSALS, digestSource } from "@bli-cockpit/telemetry-core/log-digest";
|
|
17
|
+
import { asRecord, callTower, openTower, writeCommandFailure } from "./tower-command.js";
|
|
17
18
|
import { getCollectorRuntimePaths } from "../local-state-paths.js";
|
|
18
19
|
import { isInteractiveStdin, readPipedText, writeLine } from "./cli-io.js";
|
|
19
20
|
import { runStoredLogs, storedProject } from "./logs-stored.js";
|
|
@@ -28,6 +29,15 @@ function refuse(io, json, reason, detail) {
|
|
|
28
29
|
return 1;
|
|
29
30
|
}
|
|
30
31
|
export async function runLogs(command, io, deps = {}) {
|
|
32
|
+
if (command.keyAction) {
|
|
33
|
+
const tower = await openTower("logs keys", command, io);
|
|
34
|
+
const result = await callTower(tower, { path: "/api/settings/log-ingest-keys", method: "POST", body: { source: command.source, action: command.keyAction }, label: "logs keys" });
|
|
35
|
+
if (!result.ok)
|
|
36
|
+
return writeCommandFailure(io, command.json, result);
|
|
37
|
+
// Deliberate one-time credential output; never stderr, cache or log.
|
|
38
|
+
writeLine(io.stdout, command.json ? JSON.stringify(result.body) : String(asRecord(result.body).key));
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
31
41
|
// BLI-12855: a project name (or --stored, or --list) reads the digests Tower's cron stored, not a local log.
|
|
32
42
|
const stored = storedProject(command);
|
|
33
43
|
if (command.list || stored)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit memory chain <id>` (BLI-13376): the memories the nightly
|
|
3
|
+
* consolidation folded into this one.
|
|
4
|
+
*
|
|
5
|
+
* A READ over `GET /api/memory/chain?id=<id>`, the same door the
|
|
6
|
+
* `memory_chain` MCP tool calls, with this machine's device token. The door
|
|
7
|
+
* decides what you may see: a private original shows only to its owner or a
|
|
8
|
+
* super admin, and the rest are counted ("1 private original not shown"),
|
|
9
|
+
* never printed. This verb prints the door's answer and its reason labels and
|
|
10
|
+
* writes nothing.
|
|
11
|
+
*/
|
|
12
|
+
import { askAgentDoor, emitAgentDoor, failAgentDoor } from "./agent-door.js";
|
|
13
|
+
import { writeLine } from "./cli-io.js";
|
|
14
|
+
const TAG = "[memory cli]";
|
|
15
|
+
const READ_DEADLINE_MS = 30_000;
|
|
16
|
+
export async function runMemoryChain(command, door) {
|
|
17
|
+
if (!command.memoryId) {
|
|
18
|
+
return failAgentDoor(door, TAG, "id_required", "memory chain needs the memory id `cockpit memory search` printed.");
|
|
19
|
+
}
|
|
20
|
+
const answer = await askAgentDoor(door, {
|
|
21
|
+
path: `/api/memory/chain?id=${encodeURIComponent(command.memoryId)}`,
|
|
22
|
+
method: "GET",
|
|
23
|
+
label: "memory chain",
|
|
24
|
+
timeoutMs: READ_DEADLINE_MS,
|
|
25
|
+
});
|
|
26
|
+
if (!answer.ok)
|
|
27
|
+
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
28
|
+
const body = answer.body;
|
|
29
|
+
const originals = body.originals ?? [];
|
|
30
|
+
// Counts only: a memory's text never enters a log line.
|
|
31
|
+
writeLine(door.io.stderr, `${TAG} chain ${JSON.stringify({
|
|
32
|
+
consolidated: body.consolidated ?? false, count: body.count ?? 0, shown: originals.length,
|
|
33
|
+
hidden_private: body.hiddenPrivate ?? 0, hidden_forgotten: body.hiddenForgotten ?? 0, missing: body.missing ?? 0,
|
|
34
|
+
})}`);
|
|
35
|
+
if (door.json)
|
|
36
|
+
return emitAgentDoor(door, answer.body);
|
|
37
|
+
if (!body.consolidated) {
|
|
38
|
+
writeLine(door.io.stdout, `Memory ${command.memoryId} was not written by the nightly consolidation, so it has no originals.`);
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
const by = (body.people ?? []).length > 0 ? ` by ${(body.people ?? []).join(", ")}` : "";
|
|
42
|
+
writeLine(door.io.stdout, `Memory ${command.memoryId} is from ${body.count ?? originals.length} ${body.count === 1 ? "memory" : "memories"}${by}.`);
|
|
43
|
+
for (const original of originals) {
|
|
44
|
+
writeLine(door.io.stdout, ` [${(original.createdAt ?? "").slice(0, 10)}]${original.project ? ` [${original.project}]` : ""}${original.savedBy ? ` ${original.savedBy}:` : ""} ${original.text ?? ""}`);
|
|
45
|
+
}
|
|
46
|
+
const hiddenPrivate = body.hiddenPrivate ?? 0;
|
|
47
|
+
if (hiddenPrivate > 0)
|
|
48
|
+
writeLine(door.io.stdout, `${hiddenPrivate} private ${hiddenPrivate === 1 ? "original" : "originals"} not shown.`);
|
|
49
|
+
const gone = (body.hiddenForgotten ?? 0) + (body.missing ?? 0);
|
|
50
|
+
if (gone > 0)
|
|
51
|
+
writeLine(door.io.stdout, `${gone} ${gone === 1 ? "original is" : "originals are"} no longer available.`);
|
|
52
|
+
if (body.truncated)
|
|
53
|
+
writeLine(door.io.stdout, "Only the first originals are listed.");
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
@@ -54,6 +54,14 @@ export async function runMemoryConsolidation(command, door) {
|
|
|
54
54
|
: "No consolidation runs are visible to this account. The receipts are readable by a super_admin; this is the access rule answering, not an outage.");
|
|
55
55
|
return 0;
|
|
56
56
|
}
|
|
57
|
+
if (body.night && body.nightLine) {
|
|
58
|
+
// Last night first (BLI-13376): one line for the whole night, then a row per scope.
|
|
59
|
+
writeLine(door.io.stdout, body.nightLine);
|
|
60
|
+
writeLine(door.io.stdout, nightTable(body.nightScopes ?? []));
|
|
61
|
+
writeLine(door.io.stdout, "");
|
|
62
|
+
writeLine(door.io.stdout, "merged/rewritten/corrected are what an apply run wrote, or what a report run only proposed (mode column). kept is always a proposal. Nothing is ever deleted. `--json` has every run.");
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
57
65
|
for (const run of runs) {
|
|
58
66
|
writeLine(door.io.stdout, `${run.containerTag ?? "(no tag)"} ${run.mode ?? "?"}`);
|
|
59
67
|
writeLine(door.io.stdout, ` ${run.startedAt ?? "?"} -> ${run.finishedAt ?? "unfinished"} ${run.reason ?? "no reason recorded"}`);
|
|
@@ -80,4 +88,31 @@ function countsOf(value) {
|
|
|
80
88
|
}
|
|
81
89
|
function moneyOf(value) {
|
|
82
90
|
return value === null || value === undefined ? "$0.000000" : `$${Number(value).toFixed(6)}`;
|
|
91
|
+
}
|
|
92
|
+
const NIGHT_HEADERS = ["scope", "mode", "clusters", "merged", "rewritten", "corrected", "kept", "refused", "cross-person", "cost"];
|
|
93
|
+
/** Aligned columns: the scope key first (left), every count after it (right). */
|
|
94
|
+
function nightTable(scopes) {
|
|
95
|
+
const rows = scopes.map((scope) => [
|
|
96
|
+
scope.label ?? scope.containerTag ?? "(no tag)",
|
|
97
|
+
scope.mode ?? "?",
|
|
98
|
+
String(scope.clusters ?? 0),
|
|
99
|
+
String(scope.merged ?? 0),
|
|
100
|
+
String(scope.rewritten ?? 0),
|
|
101
|
+
String(scope.corrected ?? 0),
|
|
102
|
+
String(scope.kept ?? 0),
|
|
103
|
+
refusedCell(scope),
|
|
104
|
+
String(scope.crossPersonClusters ?? 0),
|
|
105
|
+
moneyOf(scope.costUsd),
|
|
106
|
+
]);
|
|
107
|
+
const widths = NIGHT_HEADERS.map((header, column) => Math.max(header.length, ...rows.map((row) => row[column].length)));
|
|
108
|
+
const render = (cells) => cells.map((cell, column) => (column <= 1 ? cell.padEnd(widths[column]) : cell.padStart(widths[column]))).join(" ").trimEnd();
|
|
109
|
+
return [render(NIGHT_HEADERS), ...rows.map(render)].join("\n");
|
|
110
|
+
}
|
|
111
|
+
/** `7 (stale 4, empty 3)`: the total, then the biggest labels. */
|
|
112
|
+
function refusedCell(scope) {
|
|
113
|
+
const total = scope.refused ?? 0;
|
|
114
|
+
if (total === 0)
|
|
115
|
+
return "0";
|
|
116
|
+
const labels = (scope.topRefused ?? []).map((item) => `${item.label} ${item.count}`).join(", ");
|
|
117
|
+
return labels ? `${total} (${labels})` : String(total);
|
|
83
118
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { askAgentDoor, emitAgentDoor, failAgentDoor } from "./agent-door.js";
|
|
2
|
+
import { writeLine } from "./cli-io.js";
|
|
3
|
+
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
4
|
+
/** The map takes no filters: the backend answers with the caller's visible projects and topics. */
|
|
5
|
+
export function parseMemoryMapArgs(args) {
|
|
6
|
+
const values = parseNamedArgs(args, { allowedFlags: ["--json", "--home", "--dashboard-url"], valueFlags: ["--home", "--dashboard-url"] });
|
|
7
|
+
if (values.positionals.slice(1).length)
|
|
8
|
+
throw new Error("memory map takes no positionals.");
|
|
9
|
+
return { kind: "memory-store", action: "map", containerTags: [], json: values.booleans.has("--json"),
|
|
10
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")) };
|
|
11
|
+
}
|
|
12
|
+
export async function runMemoryMap(door) {
|
|
13
|
+
const answer = await askAgentDoor(door, { method: "GET", path: "/api/memory/map", label: "memory map", timeoutMs: 60_000 });
|
|
14
|
+
if (!answer.ok)
|
|
15
|
+
return failAgentDoor(door, "[memory map cli]", answer.reason, answer.detail);
|
|
16
|
+
const body = answer.body;
|
|
17
|
+
writeLine(door.io.stderr, `[memory map cli] answered ${JSON.stringify({ reason: body.capped ? "capped" : "exhausted", examined: body.examined, total: body.total })}`);
|
|
18
|
+
if (door.json)
|
|
19
|
+
return emitAgentDoor(door, body);
|
|
20
|
+
for (const project of body.projects) {
|
|
21
|
+
writeLine(door.io.stdout, `${project.project} (${project.count} memories)`);
|
|
22
|
+
for (const topic of project.topics)
|
|
23
|
+
writeLine(door.io.stdout, ` ${topic.label}: ${topic.count}${topic.averageImportance === null ? " (unscored)" : ` (importance ${Math.round(topic.averageImportance)}/100)`}`);
|
|
24
|
+
}
|
|
25
|
+
writeLine(door.io.stdout, `${body.examined} of ${body.total} visible memories counted; as of ${body.computedAt}.${body.capped ? " Scan capped at 10,000." : ""}`);
|
|
26
|
+
if (!body.projects.length)
|
|
27
|
+
writeLine(door.io.stdout, "No project knowledge yet.");
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
@@ -1,19 +1,66 @@
|
|
|
1
1
|
import { askAgentDoor, emitAgentDoor, failAgentDoor } from "./agent-door.js";
|
|
2
2
|
import { writeLine } from "./cli-io.js";
|
|
3
3
|
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
4
|
+
/** A whole number inside a range, or a refusal that names the flag. */
|
|
5
|
+
function boundedNumber(flag, raw, max) {
|
|
6
|
+
if (raw === undefined)
|
|
7
|
+
return undefined;
|
|
8
|
+
if (!/^\d+$/.test(raw) || Number(raw) < 1 || Number(raw) > max)
|
|
9
|
+
throw new Error(`${flag} must be between 1 and ${max}.`);
|
|
10
|
+
return Number(raw);
|
|
11
|
+
}
|
|
4
12
|
export function parseMemoryRulesArgs(args, action = "rules") {
|
|
5
|
-
const values = parseNamedArgs(args, { allowedFlags: ["--person", "--project", "--limit", "--json", "--by-project", "--home", "--dashboard-url"],
|
|
6
|
-
valueFlags: ["--person", "--project", "--limit", "--home", "--dashboard-url"] });
|
|
13
|
+
const values = parseNamedArgs(args, { allowedFlags: ["--person", "--project", "--limit", "--per-project", "--json", "--by-project", "--machine", "--home", "--dashboard-url"],
|
|
14
|
+
valueFlags: ["--person", "--project", "--limit", "--per-project", "--home", "--dashboard-url"] });
|
|
7
15
|
if (values.positionals.slice(1).length)
|
|
8
16
|
throw new Error(`memory ${action} takes no positionals; use --person and --project.`);
|
|
9
|
-
|
|
10
|
-
if (raw !== undefined && (!/^\d+$/.test(raw) || Number(raw) < 1 || Number(raw) > 1000))
|
|
11
|
-
throw new Error("--limit must be between 1 and 1000.");
|
|
12
|
-
return { kind: "memory-store", action, byProject: values.booleans.has("--by-project"), containerTags: [],
|
|
17
|
+
return { kind: "memory-store", action, byProject: values.booleans.has("--by-project"), machine: values.booleans.has("--machine"), containerTags: [],
|
|
13
18
|
person: optionalNonEmpty(values.flags.get("--person")), project: optionalNonEmpty(values.flags.get("--project")),
|
|
14
|
-
limit:
|
|
19
|
+
limit: boundedNumber("--limit", values.flags.get("--limit"), 1000), perProject: boundedNumber("--per-project", values.flags.get("--per-project"), 100),
|
|
20
|
+
json: values.booleans.has("--json"),
|
|
15
21
|
homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")) };
|
|
16
22
|
}
|
|
23
|
+
/** " (from 4 memories by Edward, Vee)" for a memory the nightly pass wrote (BLI-13376); empty otherwise. */
|
|
24
|
+
function originOf(idea) {
|
|
25
|
+
const c = idea.consolidated;
|
|
26
|
+
return c ? ` (from ${c.count} ${c.count === 1 ? "memory" : "memories"}${c.people.length ? ` by ${c.people.join(", ")}` : ""})` : "";
|
|
27
|
+
}
|
|
28
|
+
/** The sentence a person reads: the plain one, or the machine wording marked as the original when no plain one was written yet. */
|
|
29
|
+
function personWording(idea) {
|
|
30
|
+
return (idea.human_text ? idea.human_text : `${idea.text} (original wording)`) + originOf(idea);
|
|
31
|
+
}
|
|
32
|
+
/** What an agent reads: the machine wording, how many times it was said, its score and its space. */
|
|
33
|
+
function agentWording(idea) {
|
|
34
|
+
const said = (idea.repetition ?? 1) > 1 ? ` (said ${idea.repetition} times)` : "";
|
|
35
|
+
const score = idea.importance === null || idea.importance === undefined ? "" : ` [importance ${idea.importance}/100]`;
|
|
36
|
+
return `${idea.text}${originOf(idea)}${said}${score}${idea.container ? ` (space: ${idea.container})` : ""}`;
|
|
37
|
+
}
|
|
38
|
+
function printHeader(body, write) {
|
|
39
|
+
for (const line of [body.words?.summary, body.words?.ranking, body.freshness?.words])
|
|
40
|
+
if (line)
|
|
41
|
+
write(line);
|
|
42
|
+
for (const note of body.notes ?? [])
|
|
43
|
+
write(note);
|
|
44
|
+
}
|
|
45
|
+
function printProjects(groups, machine, write) {
|
|
46
|
+
for (const group of groups) {
|
|
47
|
+
write("");
|
|
48
|
+
write(`${group.project}, ${group.count.toLocaleString("en-US")} memories`);
|
|
49
|
+
const topics = group.topics ?? [];
|
|
50
|
+
// Topic headings help only when topics rank the ideas; a lone "Other" heading is noise.
|
|
51
|
+
const headed = Boolean(group.ranked) && topics.length > 1;
|
|
52
|
+
if (topics.length)
|
|
53
|
+
for (const topic of topics) {
|
|
54
|
+
if (headed)
|
|
55
|
+
write(` ${topic.title}`);
|
|
56
|
+
for (const idea of topic.memories)
|
|
57
|
+
write(`${headed ? " " : " "}${machine ? agentWording(idea) : personWording(idea)}`);
|
|
58
|
+
}
|
|
59
|
+
else
|
|
60
|
+
for (const idea of group.memories ?? [])
|
|
61
|
+
write(` ${machine ? agentWording(idea) : personWording(idea)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
17
64
|
export async function runMemoryRules(command, door) {
|
|
18
65
|
const params = new URLSearchParams();
|
|
19
66
|
if (command.person)
|
|
@@ -22,33 +69,32 @@ export async function runMemoryRules(command, door) {
|
|
|
22
69
|
params.set("project", command.project);
|
|
23
70
|
if (command.limit !== undefined)
|
|
24
71
|
params.set("limit", String(command.limit));
|
|
72
|
+
if (command.perProject !== undefined)
|
|
73
|
+
params.set("per_project", String(command.perProject));
|
|
25
74
|
const answer = await askAgentDoor(door, { path: `/api/memory/${command.action}${params.size ? `?${params}` : ""}`,
|
|
26
75
|
method: "GET", label: `memory ${command.action}`, timeoutMs: 60_000 });
|
|
27
76
|
if (!answer.ok)
|
|
28
77
|
return failAgentDoor(door, "[memory cli]", answer.reason, answer.detail);
|
|
29
78
|
const body = answer.body;
|
|
30
|
-
|
|
79
|
+
const projects = body.projectGroups?.length ?? 0;
|
|
80
|
+
writeLine(door.io.stderr, `[memory cli] ${command.action} ${JSON.stringify({ reason: body.capped ? "capped" : "exhausted", returned: body.returned, projects,
|
|
81
|
+
groups: projects || (body.groups?.length ?? 0), per_project: command.perProject ?? body.perProject ?? null, machine: Boolean(command.machine) })}`);
|
|
82
|
+
// JSON is the whole answer, untouched: both wordings, every field. --machine only changes the human text.
|
|
31
83
|
if (door.json)
|
|
32
84
|
return emitAgentDoor(door, answer.body);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
writeLine(door.io.stdout, ` ${topic.title}`);
|
|
38
|
-
for (const memory of topic.memories)
|
|
39
|
-
writeLine(door.io.stdout, ` ${memory.text}${memory.repetition > 1 ? ` (said ${memory.repetition} times)` : ""}${memory.importance !== null ? ` [importance ${memory.importance}/100]` : ""}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
85
|
+
const write = (line) => writeLine(door.io.stdout, line);
|
|
86
|
+
printHeader(body, write);
|
|
87
|
+
if (projects)
|
|
88
|
+
printProjects(body.projectGroups ?? [], Boolean(command.machine), write);
|
|
43
89
|
else
|
|
44
|
-
for (const group of body.groups) {
|
|
45
|
-
|
|
90
|
+
for (const group of body.groups ?? []) {
|
|
91
|
+
write(group.owner);
|
|
46
92
|
for (const rule of group.rules)
|
|
47
|
-
|
|
93
|
+
write(` [${rule.created_at.slice(0, 10)}] ${rule.project ? `[${rule.project}] ` : ""}${command.machine ? rule.text : personWording(rule)}`);
|
|
48
94
|
}
|
|
49
95
|
if (!body.returned)
|
|
50
|
-
|
|
96
|
+
write(`No ${command.action === "knowledge" ? "project knowledge" : "ground rules"} matched.`);
|
|
51
97
|
if (body.capped)
|
|
52
|
-
|
|
98
|
+
write("Result capped. Narrow by person or project, or raise --limit (max 1000) or --per-project (max 100).");
|
|
53
99
|
return 0;
|
|
54
100
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `cockpit memory search|save|update|forget|profile|consolidation|requeue` —
|
|
2
|
+
* `cockpit memory search|save|update|forget|profile|consolidation|chain|requeue` —
|
|
3
3
|
* BLI Memory at a terminal (BLI-4047; `profile` added by BLI-4533,
|
|
4
4
|
* `consolidation` by BLI-4534 and `requeue` by BLI-4539, the last two running
|
|
5
5
|
* in `memory-consolidation.ts` and `memory-requeue.ts` next door).
|
|
@@ -42,12 +42,14 @@
|
|
|
42
42
|
* `ps` shows an argument list, shell history keeps it, and a memory with a
|
|
43
43
|
* newline in it cannot be quoted correctly anyway.
|
|
44
44
|
*/
|
|
45
|
+
import { runMemoryMap } from "./memory-map.js";
|
|
45
46
|
import { runMemoryRules } from "./memory-rules.js";
|
|
46
47
|
import { memoryProjectPrefix } from "./memory-project-label.js";
|
|
47
48
|
import { memorySaveOutsideRoots } from "@bli-cockpit/memory-mcp/dist/recorded-outside-roots.js";
|
|
48
49
|
import { resolveContainerTag } from "@bli-cockpit/memory-mcp/dist/container-tag.js";
|
|
49
50
|
import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor, } from "./agent-door.js";
|
|
50
51
|
import { isInteractiveStdin, readPipedText, writeLine } from "./cli-io.js";
|
|
52
|
+
import { runMemoryChain } from "./memory-chain.js";
|
|
51
53
|
import { runMemoryConsolidation } from "./memory-consolidation.js";
|
|
52
54
|
import { cacheAgeLabel, cacheCoverage, readThroughCache } from "./read-cache.js";
|
|
53
55
|
import { runMemoryRequeue } from "./memory-requeue.js";
|
|
@@ -65,6 +67,8 @@ const FORGET_MAX_CHARS = 10_000;
|
|
|
65
67
|
export async function runMemoryStore(command, io) {
|
|
66
68
|
const door = await openAgentDoor("memory", command, io);
|
|
67
69
|
switch (command.action) {
|
|
70
|
+
case "map":
|
|
71
|
+
return runMemoryMap(door);
|
|
68
72
|
case "knowledge":
|
|
69
73
|
case "rules":
|
|
70
74
|
return runMemoryRules(command, door);
|
|
@@ -79,14 +83,13 @@ export async function runMemoryStore(command, io) {
|
|
|
79
83
|
case "profile":
|
|
80
84
|
return profileMemory(command, door);
|
|
81
85
|
case "consolidation":
|
|
82
|
-
// BLI-4534. Its own module:
|
|
83
|
-
// different shape, and is the only memory verb that is about the JOB
|
|
84
|
-
// rather than about a memory.
|
|
86
|
+
// BLI-4534. Its own module: a different door and shape, about the JOB.
|
|
85
87
|
return runMemoryConsolidation(command, door);
|
|
86
88
|
case "requeue":
|
|
87
|
-
// Its own module
|
|
88
|
-
// verb that refuses nothing the others refuse (BLI-4539).
|
|
89
|
+
// Its own module: window-shaped, super-admin, dry-run by default (BLI-4539).
|
|
89
90
|
return runMemoryRequeue(command, io);
|
|
91
|
+
case "chain":
|
|
92
|
+
return runMemoryChain(command, door);
|
|
90
93
|
case "private":
|
|
91
94
|
case "public":
|
|
92
95
|
// BLI-12927: the narrowing switch. Its own module, so this file stays
|
|
@@ -26,7 +26,7 @@ export function runMemoryVerbList(io, json) {
|
|
|
26
26
|
ok: true,
|
|
27
27
|
noun: "memory",
|
|
28
28
|
machine_verbs: ["install", "status"],
|
|
29
|
-
store_verbs: ["rules", "knowledge", "search", "save", "update", "forget", "private", "public", "profile", "consolidation", "requeue"],
|
|
29
|
+
store_verbs: ["rules", "knowledge", "search", "save", "update", "forget", "private", "public", "profile", "consolidation", "chain", "requeue"],
|
|
30
30
|
log_verbs: ["log"],
|
|
31
31
|
}));
|
|
32
32
|
return 0;
|
|
@@ -44,12 +44,18 @@ export function runMemoryVerbList(io, json) {
|
|
|
44
44
|
" you can forget or update only your own memories (a super admin, any)",
|
|
45
45
|
" cockpit memory private <id> [--json] only you and super admins can read this memory",
|
|
46
46
|
" cockpit memory public <id> [--json] every member can read it again (how every memory starts)",
|
|
47
|
-
" cockpit memory
|
|
48
|
-
" cockpit memory
|
|
47
|
+
" cockpit memory map [--json]",
|
|
48
|
+
" cockpit memory rules [--person <name>] [--project <name-or-slug>] [--limit <n>] [--per-project <n>] [--machine] [--json]",
|
|
49
|
+
" cockpit memory knowledge [--person <name>] [--project <name-or-slug>] [--limit <n>] [--per-project <n>] [--machine] [--json]",
|
|
50
|
+
" plain sentences per project; --per-project 1..100 (default 15) is each project's own",
|
|
51
|
+
" quota, --limit 1..1000 caps the ideas in all; --machine prints the agent wording with",
|
|
52
|
+
" scores and spaces; --json is the whole answer, both wordings (--by-project is accepted)",
|
|
49
53
|
" cockpit memory profile [--container <tag>] [--json]",
|
|
50
54
|
" the compiled one-page briefing every session opens with; a read, never a compile",
|
|
51
55
|
" cockpit memory consolidation [--container <tag>] [--json]",
|
|
52
56
|
" what the nightly tidy-up pass did: clusters, edits by verb, refusals, cost",
|
|
57
|
+
" cockpit memory chain <id> [--json]",
|
|
58
|
+
" the memories the nightly pass folded into this one: who, when, what they said",
|
|
53
59
|
" cockpit memory requeue --since <iso> [--until <iso>] [--source hook|harvest] [--reason <label>] [--apply] [--json]",
|
|
54
60
|
" put a window of finished extractions back in the queue after the store refused",
|
|
55
61
|
" their writes; super admins only, and a DRY RUN unless you pass --apply",
|
|
@@ -21,7 +21,7 @@ export async function runCockpitCli(argv, io) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
24
|
-
writeLine(io?.stdout ?? process.stdout, "0.2.
|
|
24
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.203");
|
|
25
25
|
return 0;
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* (`packages/bli-cockpit-mcp/src/speed-tools.ts`) carries its own copy, and a
|
|
12
12
|
* test in that package fails when the two lists drift.
|
|
13
13
|
*/
|
|
14
|
-
/** The pages in rail order
|
|
15
|
-
export function speedPages(
|
|
14
|
+
/** The pages in rail order; Memory has one data door per tab. */
|
|
15
|
+
export function speedPages(_memoryContainer) {
|
|
16
16
|
return [
|
|
17
17
|
{ name: "today", label: "Today (JARVIS home)", path: "/api/jarvis/brief" },
|
|
18
18
|
{ name: "meeting-notes", label: "Meeting notes", path: "/api/notes/library" },
|
|
@@ -21,13 +21,10 @@ export function speedPages(memoryContainer) {
|
|
|
21
21
|
{ name: "messages", label: "Messages", path: "/api/msg/channels" },
|
|
22
22
|
{ name: "mail", label: "Mail", path: "/api/mail/inbox" },
|
|
23
23
|
{ name: "docs", label: "Docs", path: "/api/docs/tree" },
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
note: "the page reads agent_memories on the server; the profile door is the nearest read",
|
|
29
|
-
},
|
|
30
|
-
{ name: "tickets", label: "Tickets", path: "/api/work/issues" },
|
|
24
|
+
{ name: "memory-knowledge", label: "Memory knowledge", path: "/api/memory/knowledge" },
|
|
25
|
+
{ name: "memory-rules", label: "Memory rules", path: "/api/memory/rules" },
|
|
26
|
+
{ name: "tickets", label: "Tickets", path: "/api/work/board" },
|
|
27
|
+
{ name: "tickets-list", label: "Tickets list", path: "/api/work/issues" },
|
|
31
28
|
{ name: "setup", label: "Setup", path: "/api/setup" },
|
|
32
29
|
{ name: "usage", label: "Usage", path: "/api/usage/people?since=30d" },
|
|
33
30
|
{ name: "calendar", label: "Calendar", path: "/api/cal/week" },
|
package/dist/commands/team.js
CHANGED
|
@@ -23,10 +23,14 @@ import { confirmDestructive } from "./settings.js";
|
|
|
23
23
|
import { asRecord, callTower, openTower, writeCommandFailure, } from "./tower-command.js";
|
|
24
24
|
export async function runTeam(command, io) {
|
|
25
25
|
const tower = await openTower("team", command, io);
|
|
26
|
+
if (command.action.startsWith("github-login-"))
|
|
27
|
+
return githubLogin(command, tower, io);
|
|
26
28
|
if (command.action === "members")
|
|
27
29
|
return showMembers(command, tower, io);
|
|
28
30
|
if (command.action === "invite")
|
|
29
31
|
return invite(command, tower, io);
|
|
32
|
+
if (command.action === "grant" || command.action === "revoke")
|
|
33
|
+
return changeGrant(command, tower, io);
|
|
30
34
|
if (command.action === "role")
|
|
31
35
|
return changeRole(command, tower, io);
|
|
32
36
|
if (command.action === "device-list")
|
|
@@ -207,4 +211,41 @@ async function deviceDoctor(command, tower, io) {
|
|
|
207
211
|
return writeCommandFailure(io, command.json, result);
|
|
208
212
|
writeLine(io.stdout, command.json ? JSON.stringify({ ok: true, ...asRecord(result.body) }) : `Doctor requested for ${device.device_name ?? device.device_id}. Waiting for its next check-in.`);
|
|
209
213
|
return 0;
|
|
214
|
+
}
|
|
215
|
+
/** Exact directory labels locate a roster id; attribution remains the server resolver's job. */
|
|
216
|
+
async function githubLogin(command, tower, io) {
|
|
217
|
+
let personId = command.personRef ?? "";
|
|
218
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(personId)) {
|
|
219
|
+
const directory = await callTower(tower, { path: "/api/team/members", label: "team github-login lookup" });
|
|
220
|
+
if (!directory.ok)
|
|
221
|
+
return writeCommandFailure(io, command.json, directory);
|
|
222
|
+
const body = asRecord(directory.body);
|
|
223
|
+
if (body.rosterError)
|
|
224
|
+
return writeCommandFailure(io, command.json, { reason: "roster_read_failed", detail: "The roster could not be read. Use a person UUID or retry." });
|
|
225
|
+
const needle = personId.toLowerCase();
|
|
226
|
+
const matches = (Array.isArray(body.people) ? body.people : []).map(asRecord).filter(person => [person.rosterId, person.name, person.email].some(label => typeof label === "string" && label.toLowerCase() === needle));
|
|
227
|
+
const ids = [...new Set(matches.map(person => person.rosterId).filter((id) => typeof id === "string"))];
|
|
228
|
+
if (ids.length !== 1)
|
|
229
|
+
return writeCommandFailure(io, command.json, { reason: ids.length ? "ambiguous_person" : "person_not_found", detail: "Use an exact roster UUID, name or email from cockpit team members." });
|
|
230
|
+
personId = ids[0];
|
|
231
|
+
}
|
|
232
|
+
const method = command.action === "github-login-list" ? "GET" : command.action === "github-login-add" ? "POST" : "DELETE";
|
|
233
|
+
const result = await callTower(tower, { path: `/api/team/members/${encodeURIComponent(personId)}/github-logins`, method,
|
|
234
|
+
label: "team github-login", ...(method === "GET" ? {} : { body: { login: command.githubLogin } }) });
|
|
235
|
+
writeLine(io.stderr, `[team cli] github login ${JSON.stringify({ person_id: personId, method, reason: result.ok ? "answered" : result.reason })}`);
|
|
236
|
+
if (!result.ok)
|
|
237
|
+
return writeCommandFailure(io, command.json, result);
|
|
238
|
+
const body = asRecord(result.body);
|
|
239
|
+
writeLine(io.stdout, command.json ? JSON.stringify(body) :
|
|
240
|
+
`GitHub logins: ${[body.primary_login, ...(Array.isArray(body.extra_logins) ? body.extra_logins : [])].filter(Boolean).join(", ") || "none"}. History is preserved.`);
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
async function changeGrant(command, tower, io) {
|
|
244
|
+
const result = await callTower(tower, { path: "/api/team/grants", method: "POST", label: "team section grant",
|
|
245
|
+
body: { person_id: command.targetUserId, section: command.section, level: command.level ?? "see", action: command.action } });
|
|
246
|
+
writeLine(io.stderr, `[team cli] section grant ${JSON.stringify({ action: command.action, level: command.level ?? "see", reason: result.ok ? "ok" : result.reason })}`);
|
|
247
|
+
if (!result.ok)
|
|
248
|
+
return writeCommandFailure(io, command.json, result);
|
|
249
|
+
writeLine(io.stdout, command.json ? JSON.stringify({ ok: true, ...asRecord(result.body) }) : `Careers ${command.level ?? "see"} ${command.action === "grant" ? "granted to" : "revoked for"} ${command.targetUserId}.`);
|
|
250
|
+
return 0;
|
|
210
251
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { askAgentDoor, emitAgentDoor, failAgentDoor } from "./agent-door.js";
|
|
2
|
+
import { writeLine } from "./cli-io.js";
|
|
3
|
+
import { ISSUE_STATES } from "./local-args-tower-work.js";
|
|
4
|
+
import { READ_DEADLINE_MS } from "./issue-contracts.js";
|
|
5
|
+
export function ticketBoardLines(board, command) {
|
|
6
|
+
const lines = [];
|
|
7
|
+
for (const state of ISSUE_STATES) {
|
|
8
|
+
if (command.stateFilter && command.stateFilter !== state)
|
|
9
|
+
continue;
|
|
10
|
+
const cards = board.cards.filter(card => card.state === state).sort((a, b) => (a.priority || 5) - (b.priority || 5) || b.updatedAt.localeCompare(a.updatedAt));
|
|
11
|
+
const total = board.countsByState?.[state];
|
|
12
|
+
const countLine = command.label ? `${cards.length} loaded` :
|
|
13
|
+
total == null ? `${cards.length} loaded · total unavailable` : `${cards.length} loaded of ${total}`;
|
|
14
|
+
const failure = board.failedStates.includes(state) ? " · read failed" : "";
|
|
15
|
+
const preview = cards.length > 5 ? " · top 5 below" : "";
|
|
16
|
+
lines.push(`${state.toUpperCase().replaceAll("_", " ")}: ${countLine}${failure}${preview}`);
|
|
17
|
+
lines.push(...cards.slice(0, 5).map(card => ` ${card.identifier} ${card.title}`));
|
|
18
|
+
}
|
|
19
|
+
if (board.countsCoverage)
|
|
20
|
+
lines.push(`Counts: ${board.countsCoverage}`);
|
|
21
|
+
if (board.countsUnavailableReason)
|
|
22
|
+
lines.push(`Counts unavailable: ${board.countsUnavailableReason}`);
|
|
23
|
+
if (board.gaps.length)
|
|
24
|
+
lines.push(`Unavailable: ${board.gaps.join(", ")}`);
|
|
25
|
+
return lines;
|
|
26
|
+
}
|
|
27
|
+
export async function readTicketBoard(command, door) {
|
|
28
|
+
const query = new URLSearchParams();
|
|
29
|
+
for (const [key, value] of Object.entries({ state: command.stateFilter, assignee: command.assignee,
|
|
30
|
+
project: command.project, label: command.label, rows: command.rows }))
|
|
31
|
+
if (value)
|
|
32
|
+
query.set(key, value);
|
|
33
|
+
const suffix = query.toString();
|
|
34
|
+
const answer = await askAgentDoor(door, { path: `/api/work/board${suffix ? `?${suffix}` : ""}`, method: "GET", label: "tickets board", timeoutMs: READ_DEADLINE_MS });
|
|
35
|
+
if (!answer.ok)
|
|
36
|
+
return failAgentDoor(door, "[tickets board]", answer.reason, answer.detail);
|
|
37
|
+
writeLine(door.io.stderr, `[tickets board] read ${JSON.stringify({ reason: "board_read", cards: answer.body.cards.length })}`);
|
|
38
|
+
if (door.json)
|
|
39
|
+
return emitAgentDoor(door, answer.body);
|
|
40
|
+
for (const line of ticketBoardLines(answer.body, command))
|
|
41
|
+
writeLine(door.io.stdout, line);
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
package/dist/commands/usage.js
CHANGED
|
@@ -20,7 +20,7 @@ export async function runUsage(command, io) {
|
|
|
20
20
|
if (command.includeAutomated)
|
|
21
21
|
query.set("include_automated", "1");
|
|
22
22
|
if (command.action === "sessions") {
|
|
23
|
-
for (const [key, value] of [["task", command.task], ["subject", command.subject], ["repo", command.repo], ["session", command.session], ["fromWindow", command.fromWindow]])
|
|
23
|
+
for (const [key, value] of [["person", command.person], ["tool", command.tool], ["cursor", command.cursor], ["limit", command.limit], ["task", command.task], ["subject", command.subject], ["repo", command.repo], ["session", command.session], ["fromWindow", command.fromWindow]])
|
|
24
24
|
if (value)
|
|
25
25
|
query.set(key, value);
|
|
26
26
|
const response = await askAgentDoor(door, { path: `/api/usage/sessions?${query}`, method: "GET", label: "usage sessions", timeoutMs: 30_000 });
|
|
@@ -30,8 +30,13 @@ export async function runUsage(command, io) {
|
|
|
30
30
|
return emitAgentDoor(door, response.body);
|
|
31
31
|
const sessions = response.body.sessions ?? [];
|
|
32
32
|
for (const session of sessions)
|
|
33
|
-
writeLine(io.stdout, `${session.session_id} ${formatCount(session.tokens)} ${session.task_type} ${session.title}`);
|
|
33
|
+
writeLine(io.stdout, `${session.session_id} ${formatCount(session.tokens)} ${session.start_time ?? "start unknown"} ${session.tool ?? session.task_type} ${session.model ?? "model unknown"} ${session.title ?? "no title yet"}`);
|
|
34
34
|
writeLine(io.stdout, `${sessions.length} sessions`);
|
|
35
|
+
const page = response.body;
|
|
36
|
+
for (const line of page.read_coverage ?? [])
|
|
37
|
+
writeLine(io.stdout, line);
|
|
38
|
+
if (page.next_cursor)
|
|
39
|
+
writeLine(io.stdout, `Next page: ${page.window ? `--since ${page.window.since} --until ${page.window.until} ` : ""}--cursor ${page.next_cursor}`);
|
|
35
40
|
return 0;
|
|
36
41
|
}
|
|
37
42
|
const answer = await askAgentDoor(door, { path: `/api/usage/people?${query}`, method: "GET", label: "usage people", timeoutMs: 30_000 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.203",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,9 +27,9 @@
|
|
|
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-surface-doors.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-cli-doctor-json.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.61",
|
|
31
|
+
"@bli-cockpit/mcp": "0.1.115",
|
|
32
|
+
"@bli-cockpit/telemetry-core": "0.1.70"
|
|
33
33
|
},
|
|
34
34
|
"repository": {
|
|
35
35
|
"type": "git",
|