@bli-cockpit/cli 0.2.244 → 0.2.245
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-contracts.js +65 -4
- package/dist/commands/issue-list-page.js +34 -6
- package/dist/commands/issue-page-args.js +64 -0
- package/dist/commands/issue-relations.js +3 -2
- package/dist/commands/issue-show.js +13 -2
- package/dist/commands/issue-versions.js +5 -8
- package/dist/commands/issue-write.js +71 -16
- package/dist/commands/issue.js +22 -3
- package/dist/commands/local-args-tower-milestone.js +120 -0
- package/dist/commands/local-args-tower-work.js +44 -51
- package/dist/commands/local-args-tower.js +1 -0
- package/dist/commands/local-args.js +2 -0
- package/dist/commands/local-command-shapes-milestone.js +1 -0
- package/dist/commands/local-help-commands-tower.js +6 -3
- package/dist/commands/local-help-milestone.js +23 -0
- package/dist/commands/local-help-project.js +1 -0
- package/dist/commands/local-help.js +2 -0
- package/dist/commands/local.js +2 -0
- package/dist/commands/milestone.js +157 -0
- package/dist/commands/project-args.js +78 -2
- package/dist/commands/project.js +52 -0
- package/dist/commands/public-root.js +1 -1
- package/package.json +2 -2
|
@@ -4,20 +4,94 @@
|
|
|
4
4
|
* A description never travels on argv by default: `--file <path>` reads it from a local file (safest on Windows);
|
|
5
5
|
* `--description <text>` is the short form and `--description none` clears it. Write-safety flags
|
|
6
6
|
* (`--request-id`, `--expected-updated-at`) belong to the four write verbs.
|
|
7
|
+
* BLI-22360: create/update also take the planning flags (`--lead`, `--member`, `--add-member`, `--remove-member`,
|
|
8
|
+
* `--status`, `--priority`, `--start`, `--target`, their `--*-resolution`, `--content-file` / `--content`); `none` clears.
|
|
7
9
|
*/
|
|
8
10
|
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
9
11
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10
12
|
const ACTIONS = ["list", "show", "create", "update", "archive", "restore"];
|
|
11
13
|
const WRITES = ["create", "update", "archive", "restore"];
|
|
14
|
+
/** BLI-22360: the planning fields of `project create|update`. `none` clears a value (update). */
|
|
15
|
+
const PLANNING_FLAGS = [
|
|
16
|
+
"--lead", "--member", "--add-member", "--remove-member", "--status", "--priority",
|
|
17
|
+
"--start", "--start-resolution", "--target", "--target-resolution", "--content-file", "--content",
|
|
18
|
+
];
|
|
19
|
+
const DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
20
|
+
const PRIORITIES = ["none", "urgent", "high", "medium", "low", "0", "1", "2", "3", "4"];
|
|
21
|
+
const STATUSES = ["backlog", "planned", "started", "in-progress", "in_progress", "paused", "completed", "done", "canceled", "none"];
|
|
22
|
+
const RESOLUTIONS = ["day", "month", "quarter", "halfYear", "year", "none"];
|
|
23
|
+
/** `--member a@x --member b@x` or `--member a@x,b@x`; `none` alone clears (sent as an empty set). */
|
|
24
|
+
function people(values, flag) {
|
|
25
|
+
const raw = values.flagValues.get(flag) ?? (values.flags.has(flag) ? [values.flags.get(flag)] : undefined);
|
|
26
|
+
if (raw === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
const list = raw.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
|
|
29
|
+
if (list.length === 0)
|
|
30
|
+
throw new Error(`${flag} needs an email, a user id or me.`);
|
|
31
|
+
return list;
|
|
32
|
+
}
|
|
33
|
+
function day(flag, value) {
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (value.trim().toLowerCase() === "none")
|
|
37
|
+
return null;
|
|
38
|
+
if (!DAY.test(value) || new Date(`${value}T12:00:00Z`).toISOString().slice(0, 10) !== value) {
|
|
39
|
+
throw new Error(`${flag} must be a calendar date as YYYY-MM-DD, or none.`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function oneOf(flag, value, allowed) {
|
|
44
|
+
if (value === undefined)
|
|
45
|
+
return undefined;
|
|
46
|
+
if (!allowed.includes(value.trim()))
|
|
47
|
+
throw new Error(`${flag} must be one of: ${allowed.join(", ")}.`);
|
|
48
|
+
return value.trim();
|
|
49
|
+
}
|
|
50
|
+
function readPlanning(values, action) {
|
|
51
|
+
const given = PLANNING_FLAGS.filter((flag) => values.flags.has(flag));
|
|
52
|
+
if (given.length === 0)
|
|
53
|
+
return {};
|
|
54
|
+
if (action !== "create" && action !== "update")
|
|
55
|
+
throw new Error(`${given[0]} belongs to project create and update.`);
|
|
56
|
+
if (values.flags.has("--content") && values.flags.has("--content-file"))
|
|
57
|
+
throw new Error("Give --content or --content-file, not both.");
|
|
58
|
+
const members = people(values, "--member");
|
|
59
|
+
const addMembers = people(values, "--add-member");
|
|
60
|
+
const removeMembers = people(values, "--remove-member");
|
|
61
|
+
if (members && (addMembers || removeMembers))
|
|
62
|
+
throw new Error("Give --member (the whole set) or --add-member / --remove-member, not both.");
|
|
63
|
+
if (action === "create" && (addMembers || removeMembers))
|
|
64
|
+
throw new Error("project create takes --member; --add-member and --remove-member belong to update.");
|
|
65
|
+
const lead = values.flags.get("--lead");
|
|
66
|
+
const content = values.flags.get("--content");
|
|
67
|
+
const status = oneOf("--status", values.flags.get("--status"), STATUSES);
|
|
68
|
+
const priority = oneOf("--priority", values.flags.get("--priority"), PRIORITIES);
|
|
69
|
+
return {
|
|
70
|
+
...(lead === undefined ? {} : { lead: lead.trim().toLowerCase() === "none" ? null : lead.trim() }),
|
|
71
|
+
...(members === undefined ? {} : { members }),
|
|
72
|
+
...(addMembers === undefined ? {} : { addMembers }),
|
|
73
|
+
...(removeMembers === undefined ? {} : { removeMembers }),
|
|
74
|
+
...(status === undefined ? {} : { status: status === "none" ? null : status }),
|
|
75
|
+
...(priority === undefined ? {} : { priority }),
|
|
76
|
+
...(values.flags.has("--start") ? { startDate: day("--start", values.flags.get("--start")) } : {}),
|
|
77
|
+
...(values.flags.has("--target") ? { targetDate: day("--target", values.flags.get("--target")) } : {}),
|
|
78
|
+
...(values.flags.has("--start-resolution") ? { startDateResolution: oneOf("--start-resolution", values.flags.get("--start-resolution"), RESOLUTIONS) } : {}),
|
|
79
|
+
...(values.flags.has("--target-resolution") ? { targetDateResolution: oneOf("--target-resolution", values.flags.get("--target-resolution"), RESOLUTIONS) } : {}),
|
|
80
|
+
...(content === undefined ? {} : { content: content.trim().toLowerCase() === "none" ? null : content }),
|
|
81
|
+
...(values.flags.has("--content-file") ? { contentFilePath: optionalNonEmpty(values.flags.get("--content-file")) } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
12
84
|
export function parseProjectArgs(args) {
|
|
13
85
|
const values = parseNamedArgs(args, {
|
|
14
86
|
allowedFlags: [
|
|
15
87
|
"--home", "--dashboard-url", "--archived", "--cursor", "--limit", "--json",
|
|
16
88
|
"--name", "--description", "--file", "--color", "--request-id", "--expected-updated-at",
|
|
89
|
+
...PLANNING_FLAGS,
|
|
17
90
|
],
|
|
18
91
|
valueFlags: [
|
|
19
92
|
"--home", "--dashboard-url", "--cursor", "--limit",
|
|
20
93
|
"--name", "--description", "--file", "--color", "--request-id", "--expected-updated-at",
|
|
94
|
+
...PLANNING_FLAGS,
|
|
21
95
|
],
|
|
22
96
|
});
|
|
23
97
|
const first = values.positionals[0];
|
|
@@ -59,8 +133,9 @@ export function parseProjectArgs(args) {
|
|
|
59
133
|
const rawDescription = values.flags.get("--description");
|
|
60
134
|
const description = rawDescription === undefined ? undefined : rawDescription.trim().toLowerCase() === "none" ? null : rawDescription;
|
|
61
135
|
const filePath = optionalNonEmpty(values.flags.get("--file"));
|
|
62
|
-
|
|
63
|
-
|
|
136
|
+
const planning = readPlanning(values, action);
|
|
137
|
+
if (action === "update" && name === undefined && description === undefined && filePath === undefined && Object.keys(planning).length === 0) {
|
|
138
|
+
throw new Error("project update needs --name, --description (or none), --file, or a planning flag (--lead, --member, --status, --priority, --start, --target, --content-file).");
|
|
64
139
|
}
|
|
65
140
|
return {
|
|
66
141
|
kind: "project",
|
|
@@ -77,6 +152,7 @@ export function parseProjectArgs(args) {
|
|
|
77
152
|
...(optionalNonEmpty(values.flags.get("--color")) ? { color: optionalNonEmpty(values.flags.get("--color")) } : {}),
|
|
78
153
|
...(requestId ? { requestId } : {}),
|
|
79
154
|
...(expectedUpdatedAt ? { expectedUpdatedAt } : {}),
|
|
155
|
+
...(Object.keys(planning).length > 0 ? { planning } : {}),
|
|
80
156
|
json: values.booleans.has("--json"),
|
|
81
157
|
};
|
|
82
158
|
}
|
package/dist/commands/project.js
CHANGED
|
@@ -12,6 +12,22 @@ import { pageFieldsOf, pageFooter } from "./issue-list-page.js";
|
|
|
12
12
|
import { readBody } from "./issue-contracts.js";
|
|
13
13
|
const TAG = "[project cli]";
|
|
14
14
|
const READ_DEADLINE_MS = 30_000;
|
|
15
|
+
const PRIORITY_WORDS = ["none", "urgent", "high", "medium", "low"];
|
|
16
|
+
const person = (p) => p.name && p.email ? `${p.name} <${p.email}>` : (p.email ?? p.name ?? p.user_id);
|
|
17
|
+
/** "2026-06-01 (month)" for a period, the bare day for an exact date. */
|
|
18
|
+
const dated = (day, resolution) => day ? `${day}${resolution ? ` (${resolution})` : ""}` : "none";
|
|
19
|
+
/** The planning block `project show` prints (BLI-22360). */
|
|
20
|
+
export function planningLines(body) {
|
|
21
|
+
const project = body.project;
|
|
22
|
+
if (!project)
|
|
23
|
+
return [];
|
|
24
|
+
return [
|
|
25
|
+
`status ${project.status ?? "none"} priority ${PRIORITY_WORDS[project.priority ?? 0] ?? project.priority} start ${dated(project.start_date, project.start_date_resolution)} target ${dated(project.target_date, project.target_date_resolution)}`,
|
|
26
|
+
`lead ${body.lead ? person(body.lead) : "none"}`,
|
|
27
|
+
`members ${(body.members ?? []).length === 0 ? "none" : (body.members ?? []).map(person).join(", ")}`,
|
|
28
|
+
...(body.milestones ?? []).map((m) => `milestone ${m.name}${m.archived_at ? " (archived)" : ""} target ${m.target_date ?? "none"} ${m.progress?.summary ?? ""} ${m.id}`),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
15
31
|
export async function runProject(command, io) {
|
|
16
32
|
const door = await openAgentDoor("project", command, io);
|
|
17
33
|
if (command.action !== "list")
|
|
@@ -65,6 +81,10 @@ async function runProjectVerb(command, door) {
|
|
|
65
81
|
writeLine(out, `id ${project?.id ?? ""} updated ${project?.updated_at ?? ""}`);
|
|
66
82
|
if (project?.description)
|
|
67
83
|
writeLine(out, project.description);
|
|
84
|
+
for (const line of planningLines(body))
|
|
85
|
+
writeLine(out, line);
|
|
86
|
+
if (project?.content)
|
|
87
|
+
writeLine(out, `\n${project.content}\n`);
|
|
68
88
|
for (const issue of body.issues ?? [])
|
|
69
89
|
writeLine(out, `${issue.identifier ?? ""} ${issue.state ?? ""} ${issue.title ?? ""}`);
|
|
70
90
|
const footer = pageFooter(body, "tickets");
|
|
@@ -92,6 +112,38 @@ async function runProjectVerb(command, door) {
|
|
|
92
112
|
else if (command.description !== undefined) {
|
|
93
113
|
payload["description"] = command.description;
|
|
94
114
|
}
|
|
115
|
+
const planning = command.planning;
|
|
116
|
+
if (planning) {
|
|
117
|
+
if (planning.lead !== undefined)
|
|
118
|
+
payload["lead"] = planning.lead;
|
|
119
|
+
if (planning.members !== undefined)
|
|
120
|
+
payload["members"] = planning.members;
|
|
121
|
+
if (planning.addMembers !== undefined)
|
|
122
|
+
payload["add_members"] = planning.addMembers;
|
|
123
|
+
if (planning.removeMembers !== undefined)
|
|
124
|
+
payload["remove_members"] = planning.removeMembers;
|
|
125
|
+
if (planning.status !== undefined)
|
|
126
|
+
payload["status"] = planning.status;
|
|
127
|
+
if (planning.priority !== undefined)
|
|
128
|
+
payload["priority"] = planning.priority;
|
|
129
|
+
if (planning.startDate !== undefined)
|
|
130
|
+
payload["start_date"] = planning.startDate;
|
|
131
|
+
if (planning.targetDate !== undefined)
|
|
132
|
+
payload["target_date"] = planning.targetDate;
|
|
133
|
+
if (planning.startDateResolution !== undefined)
|
|
134
|
+
payload["start_date_resolution"] = planning.startDateResolution;
|
|
135
|
+
if (planning.targetDateResolution !== undefined)
|
|
136
|
+
payload["target_date_resolution"] = planning.targetDateResolution;
|
|
137
|
+
if (planning.contentFilePath) {
|
|
138
|
+
const read = await readBody({ filePath: planning.contentFilePath }, door.io);
|
|
139
|
+
if (!read.ok)
|
|
140
|
+
return failAgentDoor(door, TAG, read.reason, read.detail);
|
|
141
|
+
payload["content"] = read.text;
|
|
142
|
+
}
|
|
143
|
+
else if (planning.content !== undefined) {
|
|
144
|
+
payload["content"] = planning.content;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
95
147
|
}
|
|
96
148
|
const path = command.action === "create" ? base : command.action === "update" ? ref : `${ref}/${command.action}`;
|
|
97
149
|
const answer = await askAgentDoor(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.245");
|
|
25
25
|
return 0;
|
|
26
26
|
}
|
|
27
27
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.245",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@bli-cockpit/memory-mcp": "0.1.72",
|
|
31
|
-
"@bli-cockpit/mcp": "0.1.
|
|
31
|
+
"@bli-cockpit/mcp": "0.1.150",
|
|
32
32
|
"@bli-cockpit/telemetry-core": "0.1.79"
|
|
33
33
|
},
|
|
34
34
|
"repository": {
|