@bli-cockpit/cli 0.2.137 → 0.2.140
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.js +20 -14
- package/dist/commands/local-help-commands.js +14 -2
- 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 +11 -1
- 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 } : {}),
|
|
@@ -42,17 +42,22 @@ export const TOWER_COMMAND_HELP = [
|
|
|
42
42
|
[
|
|
43
43
|
"docs",
|
|
44
44
|
[
|
|
45
|
-
"Usage: cockpit docs [list|tree|read <id|slug
|
|
45
|
+
"Usage: cockpit docs [list|tree|read <id|slug> [<id|slug>...]|read --parent <id> --bodies|create|update <id>|link <id|slug> <notion-page>|sync-status [<id|slug>]] [flags]",
|
|
46
46
|
"",
|
|
47
47
|
"The Tower document library, typed. Bare `cockpit docs` lists every document you may read.",
|
|
48
|
-
"list [--parent <id>|root] [--query <text>] [--limit <n>] — every document you may see: id, visibility, body size, slug, title. Metadata only — a list never carries a body, so it stays cheap enough for an agent to call (--json was 604 KB for 155 documents before BLI-3737, and is now a few tens of KB).",
|
|
48
|
+
"list [--parent <id>|root] [--query <text> [--word]] [--limit <n>] — every document you may see: id, visibility, body size, slug, title. Metadata only — a list never carries a body, so it stays cheap enough for an agent to call (--json was 604 KB for 155 documents before BLI-3737, and is now a few tens of KB). --json shape: { documents: [{ id, slug, title, visibility, parent_id, source, updated_at, body_chars }] }.",
|
|
49
49
|
" --parent <id> lists the documents filed directly under that document; --parent root lists the top of the tree.",
|
|
50
50
|
" --query <text> keeps the documents whose title OR body contains that text, matched case-insensitively in the database — never fuzzy, and the body still never comes back.",
|
|
51
|
+
" --word matches --query as whole words: `--query ott --word` finds \"Ott\" but not \"bottom\" or \"Otter\".",
|
|
51
52
|
" --limit <n> caps the rows (1-1000). Narrow with these before reading: `cockpit docs list --query onboarding --json` then `cockpit docs read <slug>`.",
|
|
52
53
|
"tree — the same documents nested under their parent, for a sidebar-shaped view.",
|
|
53
|
-
"read <id|slug> — one
|
|
54
|
-
"
|
|
55
|
-
"
|
|
54
|
+
"read <id|slug> [<id|slug>...] — one or more documents' title, slug, visibility, and body. This is the ONLY verb that returns a body. Several refs resolve against ONE shared list call, then one GET each — one bad ref never loses the rest.",
|
|
55
|
+
" --section \"<heading>\" — one section only, from that heading (any # level, case-insensitive) to the next heading at the same or shallower level. Mirrors `cockpit workbook --section`. A miss refuses (document_section_not_found) and names the headings that ARE there.",
|
|
56
|
+
" --offset <n> --limit <n> — a character range of the body, instead of the whole thing.",
|
|
57
|
+
" --json shape: one ref answers { document }, unchanged from before BLI-10450. Several refs answer { documents: [{ ref, ok, document } | { ref, ok: false, error, detail }] }.",
|
|
58
|
+
"read --parent <id>|root --bodies [--limit <n>] — every document filed directly under that parent, bodies included, in ONE call (GET /api/docs/documents?parent_id=...&bodies=true). --limit here caps the DOCUMENT COUNT (1-50), not characters. --json shape: { documents: [{ id, ..., body_markdown }] }.",
|
|
59
|
+
"create --title \"<title>\" [--parent <id>] [--visibility org|private] [--file <path>|--body-stdin] — the body comes from --file, or stdin (`cat body.md | cockpit docs create --title \"...\"`); neither means an empty body, matching the browser's own default. --json shape: { document }.",
|
|
60
|
+
"update <id|slug> [--title \"<t>\"] [--visibility org|private] [--parent <id>|--clear-parent] [--file <path>|--body-stdin] [--allow-empty] — only the fields you pass change; a `--parent` change IS a move, there is no separate move verb. --json shape: { document }.",
|
|
56
61
|
" --allow-empty clears the page on purpose. A body that would empty a document that holds text is refused (refused_empty_body) unless you say so, because far more often it is a surface that lost the content than a person who meant it.",
|
|
57
62
|
"link <id|slug> <notion-url|page-id> — ties the document to a Notion page so the two-way sync moves it both ways. The page is the truth from then on: the next sync pulls it over this copy and keeps the copy as a version. `link <id|slug> --clear` unties it. Refusals: invalid_notion_page, notion_page_already_linked, document_not_writable.",
|
|
58
63
|
"sync-status [<id|slug>] — every linked document's Notion sync state, or one's: synced (the body is what both sides last agreed on), tower_changed (edited here since; the next write-back pushes it), never_synced (linked, no baseline yet). Tower's half only: a Notion edit shows up at the next pull, not here. Metadata only, never a body.",
|
|
@@ -82,13 +87,14 @@ export const TOWER_COMMAND_HELP = [
|
|
|
82
87
|
[
|
|
83
88
|
"issue",
|
|
84
89
|
[
|
|
85
|
-
"Usage: cockpit issue [list|show <id
|
|
90
|
+
"Usage: cockpit issue [list|show <id> [<id>...]|create|update <id>|move <id> <state>|comment <id>|history <id>] [flags]",
|
|
86
91
|
"",
|
|
87
92
|
"Tower's issue tracker. <id> is a BLI-#### identifier (BLI-3654) or an issue's uuid — both work everywhere an issue is named.",
|
|
88
|
-
"list [--state <s>] [--assignee me|unassigned|<uuid>] [--project <name|id>] [--limit <n>] — the issues you may see, most recently updated first.",
|
|
89
|
-
"show <id> — one
|
|
90
|
-
"
|
|
91
|
-
"
|
|
93
|
+
"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). --json shape: { issues: [{ id, identifier, title, state, priority, assignee_id, assignee_name, assignee_email, project_id, parent_id, updated_at }] }.",
|
|
94
|
+
"show <id> [<id>...] — one or more issues: title, state, priority, 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.",
|
|
95
|
+
" --json shape: one id answers { issue, comments }, unchanged from before BLI-10450. Several ids answer { issues: [{ ref, ok, issue, comments } | { ref, ok: false, error, detail }] }.",
|
|
96
|
+
"create --title \"<title>\" [--project <name|id>] [--priority 0-4] [--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. --json shape: { issue }.",
|
|
97
|
+
"update <id> [--title \"<t>\"] [--priority 0-4] [--assignee me|<uuid>] [--project <name|id>] [--parent <id>] [--file <path>|--body-stdin] — only the fields you pass change. State does NOT move here; use move. --json shape: { issue }.",
|
|
92
98
|
"move <id> <state> — moves an issue and records the move. States: backlog, todo, in_progress, in_review, done, canceled.",
|
|
93
99
|
"comment <id> — posts a comment. The body is never taken on the command line: `echo \"shipped\" | cockpit issue comment BLI-3654`.",
|
|
94
100
|
"history <id> [--limit <n>] — every recorded state move and reassignment, oldest first.",
|
|
@@ -109,14 +115,14 @@ export const TOWER_COMMAND_HELP = [
|
|
|
109
115
|
" macOS: printf \"%s\" \"abcd efgh ijkl mnop\" | cockpit mail add-imap --address you@gmail.com",
|
|
110
116
|
" PowerShell: \"abcd efgh ijkl mnop\" | cockpit mail add-imap --address you@gmail.com",
|
|
111
117
|
" Make one at myaccount.google.com/apppasswords (2-Step Verification has to be on). A work @buildlaunchiterate.ca address connects in the browser instead.",
|
|
112
|
-
"inbox [--account <id>] [--unread] [--label <l>] [--limit <n>] [--before <iso>] — everything across every mailbox, newest first. * is unread, @ has an attachment.",
|
|
113
|
-
"read <thread> — one conversation with its bodies. Thread ids come from `cockpit mail inbox --json
|
|
114
|
-
"search \"<words>\" [--account <id>] [--limit <n>] — full text over subject and body. Quotes and OR work the way they do in a search box.",
|
|
118
|
+
"inbox [--account <id>] [--unread] [--label <l>] [--limit <n>] [--before <iso>] — everything across every mailbox, newest first. * is unread, @ has an attachment. Every row (text or --json) carries the thread id — `[thread <id>]` in text — so `mail read <thread>` never costs a second --json call to learn it (BLI-10450).",
|
|
119
|
+
"read <thread> — one conversation with its bodies. Thread ids come from `cockpit mail inbox` or `cockpit mail search`, in either --json or plain text.",
|
|
120
|
+
"search \"<words>\" [--account <id>] [--limit <n>] — full text over subject and body. Quotes and OR work the way they do in a search box. Every row (text or --json) carries the thread id.",
|
|
115
121
|
"send --account <id> --to <a[,b]> [--cc <c>] --subject \"<s>\" [--reply-to <message id>] [--file <path>] — the BODY comes from --file or stdin. --account is required: which address this goes out from is yours to say.",
|
|
116
122
|
"attachment <id> --out <path> — downloads one attachment to a file. Attachments are pointers until you ask; nothing is stored in Tower.",
|
|
117
123
|
"sync <account> — reads that mailbox now instead of waiting for the cron. Prints what landed, or the reason it did not.",
|
|
118
124
|
"detach <account> — removes the mailbox, its stored mail and its credential.",
|
|
119
|
-
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
|
|
125
|
+
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr. Shapes differ by verb: inbox/read answer { messages: [...] }, search answers { matches: [...] } — same MessageRow shape either name, always carrying thread_id.",
|
|
120
126
|
"A refusal keeps Tower's own reason label — needs_rls_client, account_not_found_or_unreadable, account_not_active, google_oauth_not_configured, credential_unavailable, and so on.",
|
|
121
127
|
"The command uses the existing paired device identity. Run `cockpit login` first if this machine is not paired.",
|
|
122
128
|
],
|
|
@@ -372,7 +372,7 @@ export function localSubcommandHelp(command) {
|
|
|
372
372
|
[
|
|
373
373
|
"slack",
|
|
374
374
|
[
|
|
375
|
-
"Usage: cockpit slack [coverage [--workspace bli|blue_pearl] [--stale-only] | read [filters]] [--json]",
|
|
375
|
+
"Usage: cockpit slack [coverage [--workspace bli|blue_pearl] [--stale-only] [--list] | read [filters] [--format compact|full]] [--json]",
|
|
376
376
|
"",
|
|
377
377
|
" cockpit slack coverage",
|
|
378
378
|
" What Cockpit's Slack bot can see: how many channels it is in, what each",
|
|
@@ -380,13 +380,23 @@ export function localSubcommandHelp(command) {
|
|
|
380
380
|
" are open. No message text at all, so anybody signed in can run it.",
|
|
381
381
|
" This is the command that tells an empty search apart from a coverage gap.",
|
|
382
382
|
" --stale-only drops the per-state breakdown and shows just what is behind.",
|
|
383
|
-
"
|
|
383
|
+
" --list names every channel and its readable state — counts alone could not",
|
|
384
|
+
" answer \"is there a Kaiben channel?\". --json's shape: { coverage: { workspaces: [",
|
|
385
|
+
" { workspace, channelsKnown, covered, byVisibility, staleChannels, openSkips,",
|
|
386
|
+
" channels? } ] } } — channels is present only with --list.",
|
|
387
|
+
" cockpit slack read --person <p> | --channel <c> | --query <text> [--word]",
|
|
384
388
|
" The messages themselves, through the same reader JARVIS uses. At least one",
|
|
385
389
|
" filter is required. --since/--until are YYYY-MM-DD; --limit caps the rows.",
|
|
390
|
+
" --query matches a substring; add --word to match whole words, so `excel`",
|
|
391
|
+
" stops matching \"Excellent\".",
|
|
386
392
|
" Reading Slack reads across the team, so this is for the people who do that",
|
|
387
393
|
" job; being refused it never costs you `cockpit slack coverage`.",
|
|
388
394
|
" An empty or partial answer prints Tower's own sentence about WHY, word for",
|
|
389
395
|
" word — none of those reasons means nothing was said.",
|
|
396
|
+
" --format full (default): each message as its own block (channel/author/time,",
|
|
397
|
+
" text, permalink). --format compact: one line per message. Always one rendering,",
|
|
398
|
+
" never both — a result used to print as a summary AND as blocks (BLI-10450).",
|
|
399
|
+
" --json's shape: { result: { status, reason, messages: [...], coverage, note } }.",
|
|
390
400
|
],
|
|
391
401
|
],
|
|
392
402
|
[
|
|
@@ -585,6 +595,8 @@ export function localSubcommandHelp(command) {
|
|
|
585
595
|
"The STORE, over the same /api/memory/* doors the bli-memory MCP server calls:",
|
|
586
596
|
" search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent]",
|
|
587
597
|
" repeat --container to search more than one space; omit it for this repository's own.",
|
|
598
|
+
" --limit caps the memories (default 5); up to 3 Tower pages and tickets ride beside",
|
|
599
|
+
" them whatever it says. For more Tower rows, use `cockpit search`.",
|
|
588
600
|
" save [--container <tag>] [--custom-id <id>] [--extract] [--file <path>]",
|
|
589
601
|
" the memory comes from stdin or --file, never from the command line:",
|
|
590
602
|
" `echo \"Edward decided X on 2026-09-10\" | cockpit memory save`.",
|
|
@@ -100,7 +100,7 @@ export function localCommandHelp(command) {
|
|
|
100
100
|
" cockpit models [list|show <provider:model>|compare <provider:model> <provider:model> [...]] [--highlight] [--dashboard-url <url>] [--json]",
|
|
101
101
|
" cockpit scout [board|start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
|
|
102
102
|
" cockpit ops [status [--job <id>] [--skips] [--memory] [--models] | recompile --person <email|name|id> [--dry-run]] [--dashboard-url <url>] [--json]",
|
|
103
|
-
" cockpit slack [coverage [--workspace bli|blue_pearl] [--stale-only] | read [--person <p>] [--channel <c>] [--query <text>] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>]] [--json]",
|
|
103
|
+
" cockpit slack [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]",
|
|
104
104
|
" 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]",
|
|
105
105
|
" 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]",
|
|
106
106
|
" cockpit workbook [<project> [<doc>]] [--section <id>] [--markdown] [--width <n>] [--dashboard-url <url>] [--json]",
|
|
@@ -117,7 +117,7 @@ export function localCommandHelp(command) {
|
|
|
117
117
|
" cockpit agent-rules [install|uninstall|status] [--host codex|claude|all] [--workspace <path>] [--json]",
|
|
118
118
|
" cockpit memory [search \"<question>\"|save|update <id>|forget <id>|profile|install|status|log <win|loss|noise> --store <bli|supermemory|both> \"<reason>\"] [--container <tag>] [--limit <n>] [--no-recent] [--custom-id <id>] [--extract] [--content-stdin] [--reason <label>] [--file <path>] [--dashboard-url <url>] [--dry-run] [--json]",
|
|
119
119
|
" cockpit clean [--dry-run] [--all-committed] [--reconcile] [--dashboard-url <url>] [--json]",
|
|
120
|
-
" cockpit docs [list|tree|read <id|slug>|create --title <t>|update <id>] [--parent <id>|root|--clear-parent] [--query <text>] [--limit <n>] [--visibility org|private] [--file <path>|--body-stdin] [--allow-empty] [--dashboard-url <url>] [--json]",
|
|
120
|
+
" cockpit docs [list|tree|read <id|slug>|create --title <t>|update <id>] [--parent <id>|root|--clear-parent] [--query <text> [--word]] [--limit <n>] [--visibility org|private] [--file <path>|--body-stdin] [--allow-empty] [--dashboard-url <url>] [--json]",
|
|
121
121
|
" cockpit msg [channels|create <name>|dm <email>|read <channel>|send <channel>|thread <id> --channel <channel>] [--private] [--members a@x,b@y] [--description <text>] [--thread <id>] [--limit <n>] [--dashboard-url <url>] [--json]",
|
|
122
122
|
" cockpit issue [list|show <BLI-id>|create --title <t>|update <BLI-id>|move <BLI-id> <state>|comment <BLI-id>|history <BLI-id>] [--state <s>] [--assignee me|unassigned|<uuid>] [--project <name|id>] [--limit <n>] [--priority 0-4] [--parent <BLI-id>] [--file <path>|--body-stdin] [--dashboard-url <url>] [--json]",
|
|
123
123
|
" cockpit mail [accounts|add-imap --address <a>|inbox|read <thread>|search \"<words>\"|send --account <id> --to <a> --subject <s>|attachment <id> --out <path>|sync <account>|detach <account>] [--account <id>] [--limit <n>] [--unread] [--label <l>] [--file <path>] [--dashboard-url <url>] [--json]",
|
|
@@ -136,6 +136,7 @@ export function localCommandHelp(command) {
|
|
|
136
136
|
" cockpit usage people [--by-repo | --by-topic | --by-subject | --by-task] [--min-confidence high|medium|low] [--person <email|me>] [--all] [--detail] [--since <n>d|<n>h|<iso>] [--until <iso>] [--include-automated] [--dashboard-url <url>] [--json]",
|
|
137
137
|
"",
|
|
138
138
|
`Default dashboard: ${DEFAULT_DASHBOARD_URL}. Omit --dashboard-url for normal production use; pass it only for staging/custom dashboards or to force a different pairing.`,
|
|
139
|
+
"--quiet on any command (or COCKPIT_QUIET=1) drops the `[tower client] responded {...}` trace line from stderr; --json already implies it. A failure still logs — only that success-path trace goes quiet (BLI-10450).",
|
|
139
140
|
].join("\n");
|
|
140
141
|
}
|
|
141
142
|
/**
|
package/dist/commands/local.js
CHANGED
|
@@ -57,6 +57,14 @@ export { assertCollectionRootPersisted } from "./collection-roots.js";
|
|
|
57
57
|
export { runSelfUpdate, SelfUpdateError, } from "./install-update.js";
|
|
58
58
|
import { runRoots } from "./roots.js";
|
|
59
59
|
export async function runLocalCockpitCli(argv, io = defaultIo()) {
|
|
60
|
+
// BLI-10450: `--quiet` is a global toggle, stripped here so no per-command
|
|
61
|
+
// parser's `allowedFlags` needs to learn it, and mirrored onto
|
|
62
|
+
// `COCKPIT_QUIET` so `tower-client.ts` can react without every call site
|
|
63
|
+
// threading a flag through. `--json` already implies it (tower-client.ts).
|
|
64
|
+
if (argv.includes("--quiet")) {
|
|
65
|
+
argv = argv.filter((token) => token !== "--quiet");
|
|
66
|
+
process.env.COCKPIT_QUIET = "1";
|
|
67
|
+
}
|
|
60
68
|
if (isLocalHelpRequest(argv)) {
|
|
61
69
|
writeLine(io.stdout, localCommandHelp(argv[0]));
|
|
62
70
|
return 0;
|
package/dist/commands/mail.js
CHANGED
|
@@ -177,7 +177,11 @@ function messageLine(message) {
|
|
|
177
177
|
const paperclip = message.has_attachments ? "@" : " ";
|
|
178
178
|
const from = message.from_address?.address ?? "(no sender)";
|
|
179
179
|
const day = message.internal_date.slice(0, 16).replace("T", " ");
|
|
180
|
-
|
|
180
|
+
// BLI-10450: a text row carried no thread id, so `mail read` needed a
|
|
181
|
+
// second call with `--json` just to learn what `search`/`inbox` already
|
|
182
|
+
// had in hand. `mail read <thread>` takes this verbatim.
|
|
183
|
+
const thread = message.thread_id ? ` [thread ${message.thread_id}]` : "";
|
|
184
|
+
return `${flag}${paperclip} ${day} ${from.padEnd(30).slice(0, 30)} ${(message.subject ?? "(no subject)").slice(0, 60)}${thread}`;
|
|
181
185
|
}
|
|
182
186
|
async function readThread(command, door) {
|
|
183
187
|
const answer = await askAgentDoor(door, {
|
|
@@ -175,6 +175,8 @@ export function runMemoryVerbList(io, json) {
|
|
|
175
175
|
"The experience log:",
|
|
176
176
|
" cockpit memory log <win|loss|noise> --store <bli|supermemory|both> \"<reason>\"",
|
|
177
177
|
"",
|
|
178
|
+
"Search: --limit caps the MEMORIES (default 5, at most 20). Up to 3 Tower pages and",
|
|
179
|
+
"tickets ride beside them whatever --limit says; for more, use `cockpit search`.",
|
|
178
180
|
"Save accepts repeated --alias names (up to 16 names of 64 characters).",
|
|
179
181
|
"Omit --container and this repository's own memory space is used — the same",
|
|
180
182
|
"space the bli-memory MCP server and its hooks read and write.",
|
|
@@ -222,7 +224,15 @@ async function searchMemory(command, door) {
|
|
|
222
224
|
})}`);
|
|
223
225
|
if (door.json)
|
|
224
226
|
return emitAgentDoor(door, answer.body);
|
|
225
|
-
if (results.length === 0) {
|
|
227
|
+
if (results.length === 0 && body.degraded) {
|
|
228
|
+
// BLI-10448: an empty answer from a search that lost a channel is NOT the
|
|
229
|
+
// record being silent. `semantic_timed_out` is the store answering on
|
|
230
|
+
// keywords alone after its vector search ran out of time, and saying
|
|
231
|
+
// "the record is silent" over it sends a person away from memories that
|
|
232
|
+
// exist.
|
|
233
|
+
writeLine(door.io.stdout, `No memories matched on the channels that answered, and one did not (${body.degraded}). Do not read this as the record being silent; ask again.`);
|
|
234
|
+
}
|
|
235
|
+
else if (results.length === 0) {
|
|
226
236
|
// The distinction the whole store is built on: silence is not an outage.
|
|
227
237
|
// A failure took the `!answer.ok` branch above and named itself there.
|
|
228
238
|
writeLine(door.io.stdout, "No memories matched. The record is silent on this — it does not mean the store is down.");
|
package/dist/commands/msg.js
CHANGED
|
@@ -104,15 +104,26 @@ async function readChannel(command, door) {
|
|
|
104
104
|
});
|
|
105
105
|
if (!answer.ok)
|
|
106
106
|
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
107
|
-
const
|
|
107
|
+
const body = answer.body;
|
|
108
|
+
const messages = body.messages ?? [];
|
|
109
|
+
// BLI-10449: whether this channel is Tower's own or a Slack snapshot, and
|
|
110
|
+
// how fresh — the door's own sentence, printed as sent.
|
|
111
|
+
const provenance = body.provenance ?? null;
|
|
112
|
+
const limitApplied = body.limitApplied ?? null;
|
|
108
113
|
if (door.json)
|
|
109
|
-
return emitAgentDoor(door, { ok: true, channelId: resolved.id, messages });
|
|
114
|
+
return emitAgentDoor(door, { ok: true, channelId: resolved.id, provenance, limitApplied, messages });
|
|
115
|
+
if (provenance?.sentence)
|
|
116
|
+
writeLine(door.io.stdout, provenance.sentence);
|
|
117
|
+
if (limitApplied !== null) {
|
|
118
|
+
writeLine(door.io.stdout, `Showing the newest ${limitApplied}: one read returns at most ${limitApplied} messages.`);
|
|
119
|
+
}
|
|
110
120
|
if (messages.length === 0) {
|
|
111
121
|
writeLine(door.io.stdout, "No messages.");
|
|
112
122
|
return 0;
|
|
113
123
|
}
|
|
114
124
|
for (const message of [...messages].reverse()) {
|
|
115
|
-
|
|
125
|
+
// An imported Slack message has no Tower user; its Slack name is the label.
|
|
126
|
+
const who = message.agent_label ?? message.user_id ?? message.slack_author_label ?? "someone";
|
|
116
127
|
writeLine(door.io.stdout, `${message.created_at} ${who} ${message.content ?? ""}`);
|
|
117
128
|
}
|
|
118
129
|
return 0;
|
|
@@ -4,6 +4,30 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { writeLine } from "./cli-io.js";
|
|
6
6
|
import { ask, fail, emit, sayScope, READ_DEADLINE_MS } from "./notes-door.js";
|
|
7
|
+
/**
|
|
8
|
+
* The lines `notes list` prints under the notes: every meeting record the
|
|
9
|
+
* docs door would show this caller, with the ref `docs read` takes, and which
|
|
10
|
+
* copy is canonical. Empty when there are none.
|
|
11
|
+
*/
|
|
12
|
+
export function meetingDocumentLines(meeting, readVerb) {
|
|
13
|
+
if (!meeting || meeting.count === 0)
|
|
14
|
+
return [];
|
|
15
|
+
const refOf = new Map(meeting.items.map((item) => [item.id, item.slug ?? item.id]));
|
|
16
|
+
const copies = meeting.copies > 0 ? `, ${meeting.copies} of them ${meeting.copies === 1 ? "a copy" : "copies"}` : "";
|
|
17
|
+
const lines = [
|
|
18
|
+
`Meeting records in Docs, not in Notes (${meeting.count}${copies}). Read one with \`${readVerb} <ref>\`:`,
|
|
19
|
+
];
|
|
20
|
+
for (const item of meeting.items) {
|
|
21
|
+
const size = `${Math.max(1, Math.round(item.bodyChars / 1000))} KB${item.parts.length > 0 ? ` in ${item.parts.length + 1} parts` : ""}`;
|
|
22
|
+
const mark = item.canonical
|
|
23
|
+
? item.canonicalReason && item.canonicalReason !== "only_copy" ? ` · canonical (${item.canonicalReason})` : ""
|
|
24
|
+
: ` · copy of ${refOf.get(item.duplicateOf ?? "") ?? item.duplicateOf}`;
|
|
25
|
+
lines.push(` ${item.meetingDate ?? "undated "} ${refOf.get(item.id)} ${item.title} ${item.visibility} · ${item.source} · ${size}${mark}`);
|
|
26
|
+
}
|
|
27
|
+
if (meeting.truncated)
|
|
28
|
+
lines.push(" (the read hit its cap, so there may be more)");
|
|
29
|
+
return lines;
|
|
30
|
+
}
|
|
7
31
|
function libraryQuery(command) {
|
|
8
32
|
const query = new URLSearchParams();
|
|
9
33
|
if (command.series)
|
|
@@ -33,8 +57,11 @@ export async function listNotes(command, door) {
|
|
|
33
57
|
return emit(door, body);
|
|
34
58
|
sayScope(door, body);
|
|
35
59
|
const series = body.series ?? [];
|
|
60
|
+
const inDocs = meetingDocumentLines(body.meetingDocuments, "cockpit docs read");
|
|
36
61
|
if (series.length === 0) {
|
|
37
62
|
writeLine(door.io.stdout, "No meeting notes matched.");
|
|
63
|
+
if (inDocs.length > 0)
|
|
64
|
+
writeLine(door.io.stdout, ["", ...inDocs].join("\n"));
|
|
38
65
|
return 0;
|
|
39
66
|
}
|
|
40
67
|
for (const one of series) {
|
|
@@ -47,6 +74,8 @@ export async function listNotes(command, door) {
|
|
|
47
74
|
}
|
|
48
75
|
writeLine(door.io.stdout, "");
|
|
49
76
|
writeLine(door.io.stdout, `${body.count ?? 0} note(s)${body.more ? ", and more exist beyond the limit" : ""}.`);
|
|
77
|
+
if (inDocs.length > 0)
|
|
78
|
+
writeLine(door.io.stdout, ["", ...inDocs].join("\n"));
|
|
50
79
|
return 0;
|
|
51
80
|
}
|
|
52
81
|
export async function listShelves(command, door) {
|
|
@@ -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.140");
|
|
25
25
|
return 0;
|
|
26
26
|
}
|
|
27
27
|
|