@bli-cockpit/cli 0.2.137 → 0.2.141
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/docs-read.js +197 -0
- package/dist/commands/docs.js +26 -32
- package/dist/commands/issue-directory.js +63 -0
- package/dist/commands/issue-show.js +147 -0
- package/dist/commands/issue.js +54 -71
- package/dist/commands/local-args-tower-admin.js +25 -0
- package/dist/commands/local-args-tower-docs-msg.js +95 -12
- package/dist/commands/local-args-tower-work.js +17 -5
- package/dist/commands/local-help-commands-tower-ops.js +134 -0
- package/dist/commands/local-help-commands-tower.js +201 -14
- package/dist/commands/local-help-commands.js +4 -290
- package/dist/commands/local-help.js +3 -2
- package/dist/commands/local.js +8 -0
- package/dist/commands/mail.js +5 -1
- package/dist/commands/memory-store.js +10 -54
- package/dist/commands/memory-verb-list.js +68 -0
- package/dist/commands/msg.js +14 -3
- package/dist/commands/notes-reads.js +29 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/slack.js +59 -15
- package/dist/tower-client.js +29 -7
- package/package.json +3 -3
|
@@ -157,6 +157,7 @@ export const SLACK_WORKSPACE_KEYS = ["bli", "blue_pearl"];
|
|
|
157
157
|
* `cockpit slack read [--person|--channel|--query|--since|--until|--limit]`
|
|
158
158
|
* (BLI-3462).
|
|
159
159
|
*/
|
|
160
|
+
const SLACK_READ_FORMATS = ["compact", "full"];
|
|
160
161
|
export function parseSlackArgs(args) {
|
|
161
162
|
const values = parseNamedArgs(args, {
|
|
162
163
|
allowedFlags: [
|
|
@@ -164,12 +165,15 @@ export function parseSlackArgs(args) {
|
|
|
164
165
|
"--dashboard-url",
|
|
165
166
|
"--workspace",
|
|
166
167
|
"--stale-only",
|
|
168
|
+
"--list",
|
|
167
169
|
"--person",
|
|
168
170
|
"--channel",
|
|
169
171
|
"--query",
|
|
172
|
+
"--word",
|
|
170
173
|
"--since",
|
|
171
174
|
"--until",
|
|
172
175
|
"--limit",
|
|
176
|
+
"--format",
|
|
173
177
|
"--json",
|
|
174
178
|
],
|
|
175
179
|
valueFlags: [
|
|
@@ -182,6 +186,7 @@ export function parseSlackArgs(args) {
|
|
|
182
186
|
"--since",
|
|
183
187
|
"--until",
|
|
184
188
|
"--limit",
|
|
189
|
+
"--format",
|
|
185
190
|
],
|
|
186
191
|
});
|
|
187
192
|
if (values.positionals.length > 1) {
|
|
@@ -196,6 +201,12 @@ export function parseSlackArgs(args) {
|
|
|
196
201
|
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
197
202
|
json: values.booleans.has("--json"),
|
|
198
203
|
};
|
|
204
|
+
if (values.flags.has("--format") && rawAction !== "read") {
|
|
205
|
+
throw new Error("--format belongs to `cockpit slack read`.");
|
|
206
|
+
}
|
|
207
|
+
if (values.booleans.has("--list") && rawAction !== "coverage") {
|
|
208
|
+
throw new Error("--list belongs to `cockpit slack coverage`.");
|
|
209
|
+
}
|
|
199
210
|
if (rawAction === "coverage") {
|
|
200
211
|
// `--workspace` here is a Slack workspace key, NOT the collection-root
|
|
201
212
|
// `--workspace <path>` every setup command takes. Named at the point of use
|
|
@@ -209,6 +220,8 @@ export function parseSlackArgs(args) {
|
|
|
209
220
|
action: "coverage",
|
|
210
221
|
workspace,
|
|
211
222
|
staleOnly: values.booleans.has("--stale-only"),
|
|
223
|
+
// BLI-10450: counts alone could not answer "is there a Kaiben channel?".
|
|
224
|
+
list: values.booleans.has("--list"),
|
|
212
225
|
...base,
|
|
213
226
|
};
|
|
214
227
|
}
|
|
@@ -219,15 +232,27 @@ export function parseSlackArgs(args) {
|
|
|
219
232
|
throw new Error("slack read needs at least one of --person, --channel or --query. An unfiltered dump of every " +
|
|
220
233
|
"message is not something this command offers.");
|
|
221
234
|
}
|
|
235
|
+
const word = values.booleans.has("--word");
|
|
236
|
+
if (word && !query) {
|
|
237
|
+
throw new Error("--word needs --query: it says how the query text is matched.");
|
|
238
|
+
}
|
|
239
|
+
const format = optionalNonEmpty(values.flags.get("--format"));
|
|
240
|
+
if (format && !SLACK_READ_FORMATS.includes(format)) {
|
|
241
|
+
throw new Error(`slack read --format must be one of ${SLACK_READ_FORMATS.join(", ")}; got ${format}.`);
|
|
242
|
+
}
|
|
222
243
|
return {
|
|
223
244
|
kind: "slack",
|
|
224
245
|
action: "read",
|
|
225
246
|
person,
|
|
226
247
|
channel,
|
|
227
248
|
query,
|
|
249
|
+
...(word ? { word } : {}),
|
|
228
250
|
since: optionalNonEmpty(values.flags.get("--since")),
|
|
229
251
|
until: optionalNonEmpty(values.flags.get("--until")),
|
|
230
252
|
limit: optionalPositiveInteger(values.flags.get("--limit"), "--limit"),
|
|
253
|
+
// BLI-10450: every result printed twice (a summary, then blocks) — one
|
|
254
|
+
// rendering now, never both. `full` (default) keeps the detailed blocks.
|
|
255
|
+
format: format ?? "full",
|
|
231
256
|
...base,
|
|
232
257
|
};
|
|
233
258
|
}
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, } from "./local-arg-values.js";
|
|
10
10
|
const DOCS_ACTIONS = new Set(["list", "tree", "read", "create", "update", "link", "sync-status"]);
|
|
11
|
-
|
|
11
|
+
// BLI-10450: `read` moved to its own branch in `parseDocsDocumentReference` —
|
|
12
|
+
// it takes SEVERAL references now, so it no longer shares the "exactly one"
|
|
13
|
+
// rule `update`/`link` still enforce.
|
|
14
|
+
const DOCS_ACTIONS_NEEDING_A_DOC = new Set(["update", "link"]);
|
|
12
15
|
export function parseDocsArgs(args) {
|
|
13
16
|
const values = parseNamedArgs(args, {
|
|
14
17
|
allowedFlags: [
|
|
@@ -22,17 +25,56 @@ export function parseDocsArgs(args) {
|
|
|
22
25
|
"--file",
|
|
23
26
|
"--body-stdin",
|
|
24
27
|
"--query",
|
|
28
|
+
"--word",
|
|
25
29
|
"--limit",
|
|
26
30
|
"--clear",
|
|
31
|
+
"--bodies",
|
|
32
|
+
"--section",
|
|
33
|
+
"--offset",
|
|
27
34
|
"--json",
|
|
28
35
|
],
|
|
29
|
-
valueFlags: [
|
|
36
|
+
valueFlags: [
|
|
37
|
+
"--home",
|
|
38
|
+
"--dashboard-url",
|
|
39
|
+
"--title",
|
|
40
|
+
"--parent",
|
|
41
|
+
"--visibility",
|
|
42
|
+
"--file",
|
|
43
|
+
"--query",
|
|
44
|
+
"--limit",
|
|
45
|
+
"--section",
|
|
46
|
+
"--offset",
|
|
47
|
+
],
|
|
30
48
|
});
|
|
31
49
|
const { action, rest } = parseDocsAction(values.positionals);
|
|
32
50
|
const link = parseDocsLink(action, rest, values.booleans.has("--clear"));
|
|
33
|
-
const docRef = parseDocsDocumentReference(action, link.rest);
|
|
34
|
-
const { visibility, clearParent, parentId, allowEmpty, title, query, limit } = parseDocsFlags(action, values);
|
|
35
|
-
|
|
51
|
+
const { docRef, docRefs } = parseDocsDocumentReference(action, link.rest);
|
|
52
|
+
const { visibility, clearParent, parentId, allowEmpty, title, query, wholeWord, limit, bodies, section, offset } = parseDocsFlags(action, values);
|
|
53
|
+
if (action === "read")
|
|
54
|
+
validateDocsReadTargets(docRefs, parentId, bodies);
|
|
55
|
+
return buildDocsCommand(values, action, docRef, docRefs, visibility, clearParent, parentId, allowEmpty, title, query, wholeWord, limit, bodies, section, offset, link);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* BLI-10450: `read` takes either several document references OR
|
|
59
|
+
* `--parent <id> --bodies` (every document filed directly under that
|
|
60
|
+
* parent, with bodies) — never both, and never neither. Checked once flags
|
|
61
|
+
* are known, not in `parseDocsDocumentReference`, because "was --bodies
|
|
62
|
+
* given" decides which of the two empty-input refusals is the right one.
|
|
63
|
+
*/
|
|
64
|
+
function validateDocsReadTargets(docRefs, parentId, bodies) {
|
|
65
|
+
const hasRefs = Boolean(docRefs && docRefs.length > 0);
|
|
66
|
+
if (hasRefs && parentId) {
|
|
67
|
+
throw new Error("docs read takes either document references or --parent --bodies, not both.");
|
|
68
|
+
}
|
|
69
|
+
if (bodies && !parentId) {
|
|
70
|
+
throw new Error("--bodies needs --parent, e.g. `cockpit docs read --parent <id> --bodies`.");
|
|
71
|
+
}
|
|
72
|
+
if (parentId && !bodies) {
|
|
73
|
+
throw new Error("docs read --parent needs --bodies too (or use `cockpit docs list --parent <id>` for metadata only).");
|
|
74
|
+
}
|
|
75
|
+
if (!hasRefs && !(parentId && bodies)) {
|
|
76
|
+
throw new Error("docs read needs one or more document ids/slugs, or --parent <id> --bodies.");
|
|
77
|
+
}
|
|
36
78
|
}
|
|
37
79
|
/**
|
|
38
80
|
* BLI-10430: `docs link <id|slug> <notion-url|page-id>` ties a document to a
|
|
@@ -67,23 +109,31 @@ function parseDocsAction(positionals) {
|
|
|
67
109
|
return { action, rest: positionals.slice(first === undefined ? 0 : 1) };
|
|
68
110
|
}
|
|
69
111
|
function parseDocsDocumentReference(action, rest) {
|
|
112
|
+
// BLI-10450: `read` alone takes several references in one call — an agent
|
|
113
|
+
// reading a folder's worth of documents used to pay a resolve-then-read
|
|
114
|
+
// pair PER document. Emptiness is validated later, once `--parent`/
|
|
115
|
+
// `--bodies` are known (`validateDocsReadTargets`).
|
|
116
|
+
if (action === "read") {
|
|
117
|
+
const refs = rest.map((token) => optionalNonEmpty(token)).filter((token) => Boolean(token));
|
|
118
|
+
return { docRef: refs[0], docRefs: refs.length > 0 ? refs : undefined };
|
|
119
|
+
}
|
|
70
120
|
if (DOCS_ACTIONS_NEEDING_A_DOC.has(action)) {
|
|
71
121
|
const docRef = optionalNonEmpty(rest[0]);
|
|
72
122
|
if (!docRef)
|
|
73
123
|
throw new Error(`docs ${action} needs a document id or slug.`);
|
|
74
124
|
if (rest.length > 1)
|
|
75
125
|
throw new Error(`docs ${action} takes one document reference, not ${rest.length}.`);
|
|
76
|
-
return docRef;
|
|
126
|
+
return { docRef, docRefs: undefined };
|
|
77
127
|
}
|
|
78
128
|
// BLI-10430: `sync-status` reports the whole linked library, or one document.
|
|
79
129
|
if (action === "sync-status") {
|
|
80
130
|
if (rest.length > 1)
|
|
81
131
|
throw new Error(`docs sync-status takes at most one document reference, not ${rest.length}.`);
|
|
82
|
-
return optionalNonEmpty(rest[0]);
|
|
132
|
+
return { docRef: optionalNonEmpty(rest[0]), docRefs: undefined };
|
|
83
133
|
}
|
|
84
134
|
if (rest.length > 0)
|
|
85
135
|
throw new Error(`docs ${action} does not take "${rest[0]}".`);
|
|
86
|
-
return undefined;
|
|
136
|
+
return { docRef: undefined, docRefs: undefined };
|
|
87
137
|
}
|
|
88
138
|
function parseDocsFlags(action, values) {
|
|
89
139
|
const visibility = optionalNonEmpty(values.flags.get("--visibility"));
|
|
@@ -118,19 +168,48 @@ function parseDocsFlags(action, values) {
|
|
|
118
168
|
if (query && action !== "list") {
|
|
119
169
|
throw new Error("--query belongs to `cockpit docs list`.");
|
|
120
170
|
}
|
|
171
|
+
const wholeWord = values.booleans.has("--word");
|
|
172
|
+
if (wholeWord && !query) {
|
|
173
|
+
throw new Error("--word needs --query: it says how the query text is matched.");
|
|
174
|
+
}
|
|
175
|
+
// BLI-10450: `--limit` now also belongs to `read`, where it means two
|
|
176
|
+
// different things depending on the mode it is used in — a row cap in
|
|
177
|
+
// `--parent --bodies` mode (matching `list`'s own meaning), or a character
|
|
178
|
+
// cap when slicing an explicit reference's body. The two modes are mutually
|
|
179
|
+
// exclusive (`validateDocsReadTargets`), so the flag is never ambiguous in
|
|
180
|
+
// any one call, only overloaded across the command's two shapes.
|
|
121
181
|
const limit = optionalPositiveInteger(values.flags.get("--limit"), "--limit");
|
|
122
|
-
if (limit !== undefined && action !== "list") {
|
|
123
|
-
throw new Error("--limit belongs to `cockpit docs list`.");
|
|
182
|
+
if (limit !== undefined && action !== "list" && action !== "read") {
|
|
183
|
+
throw new Error("--limit belongs to `cockpit docs list` or `cockpit docs read`.");
|
|
184
|
+
}
|
|
185
|
+
// BLI-10450: `--bodies` pairs with `--parent` to bulk-read a folder;
|
|
186
|
+
// `--section`/`--offset` slice an explicit reference's body, mirroring
|
|
187
|
+
// `cockpit workbook --section`.
|
|
188
|
+
const bodies = values.booleans.has("--bodies");
|
|
189
|
+
if (bodies && action !== "read")
|
|
190
|
+
throw new Error("--bodies belongs to `cockpit docs read`.");
|
|
191
|
+
const section = optionalNonEmpty(values.flags.get("--section"));
|
|
192
|
+
if (section && action !== "read")
|
|
193
|
+
throw new Error("--section belongs to `cockpit docs read`.");
|
|
194
|
+
const offsetRaw = optionalNonEmpty(values.flags.get("--offset"));
|
|
195
|
+
let offset;
|
|
196
|
+
if (offsetRaw !== undefined) {
|
|
197
|
+
offset = Number(offsetRaw);
|
|
198
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
199
|
+
throw new Error("--offset must be a whole number 0 or greater.");
|
|
124
200
|
}
|
|
125
|
-
|
|
201
|
+
if (offset !== undefined && action !== "read")
|
|
202
|
+
throw new Error("--offset belongs to `cockpit docs read`.");
|
|
203
|
+
return { visibility, clearParent, parentId, allowEmpty, title, query, wholeWord, limit, bodies, section, offset };
|
|
126
204
|
}
|
|
127
|
-
function buildDocsCommand(values, action, docRef, visibility, clearParent, parentId, allowEmpty, title, query, limit, link) {
|
|
205
|
+
function buildDocsCommand(values, action, docRef, docRefs, visibility, clearParent, parentId, allowEmpty, title, query, wholeWord, limit, bodies, section, offset, link) {
|
|
128
206
|
return {
|
|
129
207
|
kind: "docs",
|
|
130
208
|
action,
|
|
131
209
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
132
210
|
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
133
211
|
...(docRef ? { docRef } : {}),
|
|
212
|
+
...(docRefs ? { docRefs } : {}),
|
|
134
213
|
title,
|
|
135
214
|
...(parentId ? { parentId } : {}),
|
|
136
215
|
clearParent,
|
|
@@ -139,7 +218,11 @@ function buildDocsCommand(values, action, docRef, visibility, clearParent, paren
|
|
|
139
218
|
filePath: optionalNonEmpty(values.flags.get("--file")),
|
|
140
219
|
bodyStdin: values.booleans.has("--body-stdin"),
|
|
141
220
|
...(query ? { query } : {}),
|
|
221
|
+
...(wholeWord ? { wholeWord } : {}),
|
|
142
222
|
...(limit === undefined ? {} : { limit }),
|
|
223
|
+
...(bodies ? { bodies } : {}),
|
|
224
|
+
...(section ? { section } : {}),
|
|
225
|
+
...(offset === undefined ? {} : { offset }),
|
|
143
226
|
...(link.notionPage ? { notionPage: link.notionPage } : {}),
|
|
144
227
|
...(link.clearLink ? { clearLink: true } : {}),
|
|
145
228
|
json: values.booleans.has("--json"),
|
|
@@ -73,14 +73,14 @@ export function parseIssueArgs(args) {
|
|
|
73
73
|
],
|
|
74
74
|
});
|
|
75
75
|
const { action, rest } = readIssueAction(values);
|
|
76
|
-
const { issueRef, moveState } = readIssueReference(action, rest);
|
|
76
|
+
const { issueRef, issueRefs, moveState } = readIssueReference(action, rest);
|
|
77
77
|
validateIssueState(moveState, "issue move state must be one of: ");
|
|
78
78
|
const stateFilter = readIssueStateFilter(action, values);
|
|
79
79
|
const title = readIssueTitle(action, values);
|
|
80
80
|
const priority = readIssuePriority(values);
|
|
81
81
|
const assignee = readIssueAssignee(action, values);
|
|
82
82
|
const limit = readIssueLimit(action, values);
|
|
83
|
-
return buildIssueCommand(values, action, issueRef, moveState, stateFilter, title, priority, assignee, limit);
|
|
83
|
+
return buildIssueCommand(values, action, issueRef, issueRefs, moveState, stateFilter, title, priority, assignee, limit);
|
|
84
84
|
}
|
|
85
85
|
function readIssueAction(values) {
|
|
86
86
|
const first = values.positionals[0];
|
|
@@ -92,8 +92,19 @@ function readIssueAction(values) {
|
|
|
92
92
|
}
|
|
93
93
|
function readIssueReference(action, rest) {
|
|
94
94
|
let issueRef;
|
|
95
|
+
let issueRefs;
|
|
95
96
|
let moveState;
|
|
96
|
-
|
|
97
|
+
// BLI-10450: `show` alone takes several references in one call — an agent
|
|
98
|
+
// reading a sprint's worth of tickets used to pay one process per issue.
|
|
99
|
+
// Every other verb here still names exactly one issue.
|
|
100
|
+
if (action === "show") {
|
|
101
|
+
const refs = rest.map((token) => optionalNonEmpty(token)).filter((token) => Boolean(token));
|
|
102
|
+
if (refs.length === 0)
|
|
103
|
+
throw new Error("issue show needs one or more issue ids or identifiers, e.g. BLI-3654.");
|
|
104
|
+
issueRefs = refs;
|
|
105
|
+
issueRef = refs[0];
|
|
106
|
+
}
|
|
107
|
+
else if (ISSUE_ACTIONS_NEEDING_AN_ISSUE.has(action)) {
|
|
97
108
|
issueRef = optionalNonEmpty(rest[0]);
|
|
98
109
|
if (!issueRef)
|
|
99
110
|
throw new Error(`issue ${action} needs an issue id or identifier, e.g. BLI-3654.`);
|
|
@@ -112,7 +123,7 @@ function readIssueReference(action, rest) {
|
|
|
112
123
|
else if (rest.length > 0) {
|
|
113
124
|
throw new Error(`issue ${action} does not take "${rest[0]}".`);
|
|
114
125
|
}
|
|
115
|
-
return { issueRef, moveState };
|
|
126
|
+
return { issueRef, issueRefs, moveState };
|
|
116
127
|
}
|
|
117
128
|
function validateIssueState(state, errorPrefix) {
|
|
118
129
|
if (state && !ISSUE_STATES.includes(state)) {
|
|
@@ -166,13 +177,14 @@ function readIssueLimit(action, values) {
|
|
|
166
177
|
}
|
|
167
178
|
return limit;
|
|
168
179
|
}
|
|
169
|
-
function buildIssueCommand(values, action, issueRef, moveState, stateFilter, title, priority, assignee, limit) {
|
|
180
|
+
function buildIssueCommand(values, action, issueRef, issueRefs, moveState, stateFilter, title, priority, assignee, limit) {
|
|
170
181
|
return {
|
|
171
182
|
kind: "issue",
|
|
172
183
|
action,
|
|
173
184
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
174
185
|
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
175
186
|
...(issueRef ? { issueRef } : {}),
|
|
187
|
+
...(issueRefs ? { issueRefs } : {}),
|
|
176
188
|
...(moveState ? { moveState } : {}),
|
|
177
189
|
...(stateFilter ? { stateFilter } : {}),
|
|
178
190
|
...(assignee ? { assignee } : {}),
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The long-form `cockpit <command> --help` text for JARVIS, the model cards,
|
|
3
|
+
* Scout and the ops doors.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `local-help-commands.ts` (BLI-10445) when that file crossed the
|
|
6
|
+
* 500-line readability ratchet, along the same seam BLI-3709 drew: these are
|
|
7
|
+
* Tower surfaces, not the collector's own commands.
|
|
8
|
+
*
|
|
9
|
+
* Every string moved verbatim. Help output is what an intern pastes back when
|
|
10
|
+
* something breaks, so it is a user-visible contract like any other.
|
|
11
|
+
*/
|
|
12
|
+
export const TOWER_OPS_COMMAND_HELP = [
|
|
13
|
+
[
|
|
14
|
+
"jarvis",
|
|
15
|
+
[
|
|
16
|
+
"Usage: cockpit jarvis [question] [--prompt <question>] [--as <person>] [--date <YYYY-MM-DD>] [--thread <name>] [--model <key>] [--image <path>|--file <path>] [--no-stream] [--threads|--history [--limit <n>]] [--trace <id|last>] [--dashboard-url <url>] [--json [--show-approval-code]]",
|
|
17
|
+
"",
|
|
18
|
+
"Chats with the same JARVIS used by Tower web chat and the BLI Slack DM.",
|
|
19
|
+
"--as selects the existing website person space; it changes who the chat is about, never who is authenticated.",
|
|
20
|
+
"--date <YYYY-MM-DD> binds the page that was live on one of that person's days (`today` and `yesterday` work too), so the turn is about that day's page instead of the newest one. A day with nothing compiled binds no page and JARVIS says so rather than answering from a different day.",
|
|
21
|
+
"--model <key> requests one provider:model pair (e.g. openai:gpt-5.6-terra); an unrecognised key is refused by the dashboard, not this command.",
|
|
22
|
+
"--image <path> (alias --file) attaches one local PNG, JPEG, or WEBP under 10 MB with the question, the same one-image gate the JARVIS panel uses. A bad path, an unsupported type, or an oversized file is refused with its own plain sentence — never a stack trace.",
|
|
23
|
+
"Run with no question for an interactive terminal conversation.",
|
|
24
|
+
"Agents can pass --prompt, positional text, or pipe one question on stdin.",
|
|
25
|
+
"Each answer prints a per-tool trace line and, only on a fallback or model mismatch, one model receipt line.",
|
|
26
|
+
"Trace lines arrive live while JARVIS works; pass --no-stream to wait for the whole answer in one piece instead.",
|
|
27
|
+
"--json writes one machine-readable response (including trace and model) to stdout; operational metadata stays on stderr. A --json run never streams fragments, whichever mode it is in.",
|
|
28
|
+
"A --json turn is treated as an AGENT surface: if it proposes a coding task, JARVIS answers with proposal_id and NOT the eight-character approval code, because a code a program can read is a code it could approve with. The person fetches the code themself — a Tower browser tab, their Slack DM, or an interactive `cockpit jarvis` — and hands it back. Pass --show-approval-code when a script really is driving a dispatch for a person; the run says on stderr that it did.",
|
|
29
|
+
"That object carries answer, sources (the Source: lines inside it), turn_id (pass it to --trace), thread_id (pass it to --thread), proposal_id, next_action, degraded and degraded_reasons — the SAME shape the bli-tower MCP server's jarvis_ask tool returns, so a script and an agent read one contract. reply/thread/traceId/model/trace/latency are still there beside it and still mean what they always meant.",
|
|
30
|
+
"proposal_id carries the coding-arm proposal a turn made, and is null when it made none — including when the arm refused. next_action is the sentence that says which of those it was and what a PERSON has to do about it: fetch the approval code for that proposal (a Tower tab, their Slack DM, or their own interactive `cockpit jarvis`), or approve a refused one where a person reads. A null proposal_id is never on its own. Do not send a proposal_id back on a --json turn: this is the surface the code was withheld from, and reading it back is refused here.",
|
|
31
|
+
"A turn that outlives the dashboard's own ceiling is reported as turn_timed_out — an incomplete answer is never printed as a finished one.",
|
|
32
|
+
"The command uses the existing paired device identity. It cannot override the caller, team, role, or person scope.",
|
|
33
|
+
"A thread is SINGLE-WRITER: two turns in the same conversation at the same time can read each other, and one can answer the other's question. Your own terminal is one conversation, named main, and stays that way. A --json turn is a program's, so it gets a FRESH conversation per call unless --thread names one, and the answer's thread_id is the name it used — pass that back with --thread to continue it deliberately. On a --json turn, --thread main asks for the shared default nobody chose, so it is treated as naming nothing and a fresh conversation is minted anyway.",
|
|
34
|
+
"--threads lists the terminal conversations on this account; --thread <name> --history replays one (the newest --limit <n> messages). The per-call conversations a --json turn minted are counted rather than listed, and any one of them still replays by name. Neither takes a question, and neither can reach anybody else's conversations.",
|
|
35
|
+
"--trace <id|last> prints the step tree of one turn — every model step, tool call and server-side memory step, with how long each took, which model ran it, the tokens it spent and what failed. `last` opens the turn this machine asked most recently. Like --threads it takes no question and reaches no model.",
|
|
36
|
+
"Run `cockpit login` first if this machine is not paired.",
|
|
37
|
+
],
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
"models",
|
|
41
|
+
[
|
|
42
|
+
"Usage: cockpit models [list|show <provider:model>|compare <id> <id> [...]] [--highlight] [--dashboard-url <url>] [--json]",
|
|
43
|
+
"",
|
|
44
|
+
"The model-card shelf: one card per model, with what the PROVIDER documents (context",
|
|
45
|
+
"window, max output, price per million, reasoning modes, tool calling, structured output,",
|
|
46
|
+
"quirks) beside what OUR machine measured (latency p50/p95, observed tokens/s, the last",
|
|
47
|
+
"belt and conformance probe verdicts).",
|
|
48
|
+
"Every value carries the page it was read from; a field the provider does not state reads",
|
|
49
|
+
"unknown with the reason, never a guess.",
|
|
50
|
+
"STALE:<field> means a measurement contradicted the documentation — reasoning-off refused",
|
|
51
|
+
"where the card says it can be turned off, the belt refused where the card says tool",
|
|
52
|
+
"calling. The line under it is the command that re-reads the provider.",
|
|
53
|
+
"compare puts two to four cards side by side: the same rows the page at /settings/models/",
|
|
54
|
+
"compare renders, including `$ per 100 JARVIS turns` (our own measured tokens a turn times",
|
|
55
|
+
"the provider's price, with the window it was measured over beside it — `not measured`",
|
|
56
|
+
"where no profile exists, never a per-million price relabelled) and the bench row, which",
|
|
57
|
+
"appears only when every model was judged in the SAME run against the SAME baseline;",
|
|
58
|
+
"otherwise it says `benched separately — not a head-to-head` rather than subtracting two",
|
|
59
|
+
"win rates. --highlight marks the winner in each scored row (lowest wins on price and",
|
|
60
|
+
"latency, highest on tokens/s, most wins on the bench); a row where any column is `—` or",
|
|
61
|
+
"`not measured` is not scored at all, and says why.",
|
|
62
|
+
"`cockpit models` is the SHELF; `cockpit model` is the org setting saying which model a",
|
|
63
|
+
"slot runs on. Different nouns, one letter apart, on purpose.",
|
|
64
|
+
"--json writes one object to stdout; operational lines stay on stderr.",
|
|
65
|
+
"Run `cockpit login` first if this machine is not paired.",
|
|
66
|
+
],
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
"scout",
|
|
70
|
+
[
|
|
71
|
+
"Usage: cockpit scout [board|start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
|
|
72
|
+
"",
|
|
73
|
+
"Prints the same Scout board the Tower page shows, in the same words: the standing",
|
|
74
|
+
"watch line, the cards waiting on a decision, and the raw signal watch underneath.",
|
|
75
|
+
"This is the board itself. To ask a QUESTION about it — what it means, whether it is",
|
|
76
|
+
"worth doing here — use `cockpit jarvis`, whose readScout tool reads the same rows.",
|
|
77
|
+
"`cockpit scout board` and a bare `cockpit scout` are the same command; the MCP twin",
|
|
78
|
+
"is `scout_board` on the bli-tower server, over the same GET /api/cockpit/scout door.",
|
|
79
|
+
"--days <n> widens or narrows the window (1 to 90; the board defaults to 14).",
|
|
80
|
+
"Reading follows the Scout page audience setting, so whoever can open /scout can run this.",
|
|
81
|
+
"start, dismiss, and undo move one card and are super_admin actions on every surface;",
|
|
82
|
+
"a refusal is printed as Tower worded it and the board stays readable underneath.",
|
|
83
|
+
"The id can be the first 6 or more characters printed beside each card; an ambiguous",
|
|
84
|
+
"prefix is refused with the candidates rather than acting on the first match.",
|
|
85
|
+
"--json writes one object to stdout; operational lines stay on stderr.",
|
|
86
|
+
"Run `cockpit login` first if this machine is not paired.",
|
|
87
|
+
],
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
"ops",
|
|
91
|
+
[
|
|
92
|
+
"Usage: cockpit ops [status [--job <id>] [--coverage] [--skips] [--memory [--memory-days N]] [--models] [--turns] [--tool-router] | recompile --person <email|name|id> [--dry-run]] [--json]",
|
|
93
|
+
"",
|
|
94
|
+
" cockpit ops status",
|
|
95
|
+
" One line per scheduled job: when it last produced something, and whether that",
|
|
96
|
+
" is late FOR THAT JOB. A once-daily job quiet for 18 hours reads ok; a",
|
|
97
|
+
" quarter-hourly one does not. Every line names the schedule it is judged against.",
|
|
98
|
+
" --turns adds 30-day JARVIS turn aggregates; --tool-router adds the shadow-router agreement board.",
|
|
99
|
+
" A job that is not ok also prints what its artifact does and does not prove —",
|
|
100
|
+
" several only write a row when there is something new, so quiet can mean a quiet",
|
|
101
|
+
" week rather than a broken cron.",
|
|
102
|
+
" --job <id> asks about one; --skips adds the open Slack and external-sync skips,",
|
|
103
|
+
" grouped by reason. Exits 1 if anything is stale, empty or unreadable.",
|
|
104
|
+
" --coverage is the collection-coverage row on its own, with the BUCKET table:",
|
|
105
|
+
" which bucket the missing sessions are in (staged-not-committed with the upload",
|
|
106
|
+
" ledger's own reason, still pending upload, deliberately withheld, or no row at",
|
|
107
|
+
" all), whose machines they are on, which of their own days, and what to run for",
|
|
108
|
+
" each. That table also prints on the ordinary board whenever anything is missing.",
|
|
109
|
+
" Every machine's line also carries its BLI Memory column — whether the MCP",
|
|
110
|
+
" servers and the recall/save hooks are registered for Claude Code and Codex,",
|
|
111
|
+
" and whether Codex has been told to TRUST its hooks. That column never colours",
|
|
112
|
+
" a machine: a laptop with no memory hooks is still collecting sessions.",
|
|
113
|
+
" --memory adds the adoption gauge: memories saved per person per day for the",
|
|
114
|
+
" last 7 days, split by source (an agent calling save_memory, the Stop hook, or",
|
|
115
|
+
" the vendor import, which is a backfill and never counted as use).",
|
|
116
|
+
" --memory-days N changes the window (1-30) and implies --memory. Searches are",
|
|
117
|
+
" NOT counted — nothing in Tower records one, and the gauge says so rather than",
|
|
118
|
+
" printing a zero you would read as nobody searching.",
|
|
119
|
+
" It also shows ordinary prompt-hook observations: outcome counts, 100 ms",
|
|
120
|
+
" duration bounds, actual vector-cache reuse and named coverage gaps. These",
|
|
121
|
+
" are hook-body timings, not full host wait or retrieval-quality scores.",
|
|
122
|
+
" cockpit ops recompile --person <email|name|id> [--dry-run]",
|
|
123
|
+
" Writes that person's page again, now. Your own page is always yours to",
|
|
124
|
+
" recompile; somebody else's is for the people who read across the team.",
|
|
125
|
+
" --dry-run resolves the person and says which page is being served, compiling",
|
|
126
|
+
" nothing. A compile only ever ADDS a version — nothing is overwritten.",
|
|
127
|
+
" It can take minutes, and if Tower's 800-second budget runs out first you are",
|
|
128
|
+
" told exactly that: the compile may still have finished. Check with",
|
|
129
|
+
" `cockpit brief --for <person>`.",
|
|
130
|
+
" There is no --cadence: the compile path behind this writes a daily, and a flag",
|
|
131
|
+
" that was accepted and dropped would be worse than no flag.",
|
|
132
|
+
],
|
|
133
|
+
],
|
|
134
|
+
];
|