@nijaru/tk 0.0.1

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.
@@ -0,0 +1,347 @@
1
+ import { test, expect, describe, afterEach } from "bun:test";
2
+ import {
3
+ shouldUseColor,
4
+ formatTaskRow,
5
+ formatTaskList,
6
+ formatTaskDetail,
7
+ formatJson,
8
+ } from "./format";
9
+ import type { Task, TaskWithMeta, LogEntry } from "../types";
10
+
11
+ describe("shouldUseColor", () => {
12
+ const originalEnv = process.env.NO_COLOR;
13
+
14
+ afterEach(() => {
15
+ if (originalEnv === undefined) {
16
+ delete process.env.NO_COLOR;
17
+ } else {
18
+ process.env.NO_COLOR = originalEnv;
19
+ }
20
+ });
21
+
22
+ test("returns false when NO_COLOR is set (non-empty)", () => {
23
+ process.env.NO_COLOR = "1";
24
+ expect(shouldUseColor()).toBe(false);
25
+ });
26
+
27
+ test("returns false when NO_COLOR is set to any value", () => {
28
+ process.env.NO_COLOR = "true";
29
+ expect(shouldUseColor()).toBe(false);
30
+ process.env.NO_COLOR = "yes";
31
+ expect(shouldUseColor()).toBe(false);
32
+ });
33
+
34
+ test("respects NO_COLOR empty string as color enabled", () => {
35
+ process.env.NO_COLOR = "";
36
+ // When NO_COLOR is empty, color is allowed (depends on TTY)
37
+ });
38
+ });
39
+
40
+ describe("formatTaskRow", () => {
41
+ const task: Task & { id: string } = {
42
+ id: "tk-a1b2",
43
+ project: "tk",
44
+ ref: "a1b2",
45
+ title: "Test task",
46
+ description: null,
47
+ priority: 1,
48
+ status: "open",
49
+ labels: [],
50
+ assignees: [],
51
+ parent: null,
52
+ blocked_by: [],
53
+ estimate: null,
54
+ due_date: null,
55
+ logs: [],
56
+ created_at: new Date().toISOString(),
57
+ updated_at: new Date().toISOString(),
58
+ completed_at: null,
59
+ external: {},
60
+ };
61
+
62
+ test("formats task without color", () => {
63
+ const result = formatTaskRow(task, false);
64
+ expect(result).toContain("tk-a1b2");
65
+ expect(result).toContain("p1");
66
+ expect(result).toContain("open");
67
+ expect(result).toContain("Test task");
68
+ expect(result).not.toContain("\x1b[");
69
+ });
70
+
71
+ test("formats task with color", () => {
72
+ const result = formatTaskRow(task, true);
73
+ expect(result).toContain("tk-a1b2");
74
+ expect(result).toContain("\x1b[");
75
+ });
76
+
77
+ test("truncates long titles to 50 chars", () => {
78
+ const longTask: Task & { id: string } = {
79
+ ...task,
80
+ title: "A".repeat(100),
81
+ };
82
+ const result = formatTaskRow(longTask, false);
83
+ expect(result.split("A").length - 1).toBeLessThanOrEqual(50);
84
+ });
85
+
86
+ test("includes column dividers", () => {
87
+ const result = formatTaskRow(task, false);
88
+ expect(result).toContain(" | ");
89
+ const parts = result.split(" | ");
90
+ expect(parts.length).toBe(4); // ID | PRIO | STATUS | TITLE
91
+ });
92
+
93
+ test("truncates long project names to 6 chars", () => {
94
+ const longProjectTask: Task & { id: string } = {
95
+ ...task,
96
+ id: "mylongproject-a1b2",
97
+ };
98
+ const result = formatTaskRow(longProjectTask, false);
99
+ expect(result).toContain("mylong-a1b2");
100
+ expect(result).not.toContain("mylongproject");
101
+ });
102
+
103
+ test("keeps short project names intact", () => {
104
+ const result = formatTaskRow(task, false);
105
+ expect(result).toContain("tk-a1b2");
106
+ });
107
+
108
+ test("shows overdue marker for overdue tasks", () => {
109
+ const overdueTask: Task & { id: string } = {
110
+ ...task,
111
+ due_date: "2020-01-01",
112
+ };
113
+ const result = formatTaskRow(overdueTask, false);
114
+ expect(result).toContain("[OVERDUE]");
115
+ });
116
+
117
+ test("does NOT show overdue marker for tasks due today", () => {
118
+ const today = new Date().toISOString().split("T")[0] ?? null;
119
+ const todayTask: Task & { id: string } = {
120
+ ...task,
121
+ due_date: today,
122
+ };
123
+ const result = formatTaskRow(todayTask, false);
124
+ expect(result).not.toContain("[OVERDUE]");
125
+ });
126
+ });
127
+
128
+ describe("formatTaskList", () => {
129
+ test("returns message for empty list", () => {
130
+ expect(formatTaskList([])).toBe("No tasks found.");
131
+ });
132
+
133
+ test("includes header and divider", () => {
134
+ const tasks: (Task & { id: string })[] = [
135
+ {
136
+ id: "tk-a1b2",
137
+ project: "tk",
138
+ ref: "a1b2",
139
+ title: "Test",
140
+ description: null,
141
+ priority: 3,
142
+ status: "open",
143
+ labels: [],
144
+ assignees: [],
145
+ parent: null,
146
+ blocked_by: [],
147
+ estimate: null,
148
+ due_date: null,
149
+ logs: [],
150
+ created_at: new Date().toISOString(),
151
+ updated_at: new Date().toISOString(),
152
+ completed_at: null,
153
+ external: {},
154
+ },
155
+ ];
156
+ const result = formatTaskList(tasks, false);
157
+ expect(result).toContain("ID");
158
+ expect(result).toContain("PRI");
159
+ expect(result).toContain("STATUS");
160
+ expect(result).toContain("TITLE");
161
+ expect(result).toContain("----");
162
+ });
163
+
164
+ test("formats multiple tasks", () => {
165
+ const tasks: (Task & { id: string })[] = [
166
+ {
167
+ id: "tk-a1b2",
168
+ project: "tk",
169
+ ref: "a1b2",
170
+ title: "First",
171
+ description: null,
172
+ priority: 1,
173
+ status: "open",
174
+ labels: [],
175
+ assignees: [],
176
+ parent: null,
177
+ blocked_by: [],
178
+ estimate: null,
179
+ due_date: null,
180
+ logs: [],
181
+ created_at: new Date().toISOString(),
182
+ updated_at: new Date().toISOString(),
183
+ completed_at: null,
184
+ external: {},
185
+ },
186
+ {
187
+ id: "tk-c3d4",
188
+ project: "tk",
189
+ ref: "c3d4",
190
+ title: "Second",
191
+ description: null,
192
+ priority: 3,
193
+ status: "active",
194
+ labels: [],
195
+ assignees: [],
196
+ parent: null,
197
+ blocked_by: [],
198
+ estimate: null,
199
+ due_date: null,
200
+ logs: [],
201
+ created_at: new Date().toISOString(),
202
+ updated_at: new Date().toISOString(),
203
+ completed_at: null,
204
+ external: {},
205
+ },
206
+ ];
207
+ const result = formatTaskList(tasks, false);
208
+ expect(result).toContain("tk-a1b2");
209
+ expect(result).toContain("tk-c3d4");
210
+ expect(result).toContain("First");
211
+ expect(result).toContain("Second");
212
+ });
213
+ });
214
+
215
+ describe("formatTaskDetail", () => {
216
+ const task: TaskWithMeta = {
217
+ id: "tk-a1b2",
218
+ project: "tk",
219
+ ref: "a1b2",
220
+ title: "Test task",
221
+ description: "A description",
222
+ priority: 1,
223
+ status: "open",
224
+ labels: ["bug", "urgent"],
225
+ assignees: ["nick"],
226
+ parent: null,
227
+ blocked_by: ["tk-c3d4"],
228
+ estimate: 3,
229
+ due_date: "2026-01-15",
230
+ logs: [],
231
+ created_at: "2024-01-01T00:00:00.000Z",
232
+ updated_at: "2024-01-01T00:00:00.000Z",
233
+ completed_at: null,
234
+ external: {},
235
+ blocked_by_incomplete: true,
236
+ is_overdue: false,
237
+ };
238
+
239
+ test("includes all task fields", () => {
240
+ const result = formatTaskDetail(task, [], false);
241
+ expect(result).toContain("ID:");
242
+ expect(result).toContain("tk-a1b2");
243
+ expect(result).toContain("Title:");
244
+ expect(result).toContain("Test task");
245
+ expect(result).toContain("Status:");
246
+ expect(result).toContain("open");
247
+ expect(result).toContain("Priority:");
248
+ expect(result).toContain("p1");
249
+ expect(result).toContain("Description:");
250
+ expect(result).toContain("A description");
251
+ });
252
+
253
+ test("shows labels", () => {
254
+ const result = formatTaskDetail(task, [], false);
255
+ expect(result).toContain("Labels:");
256
+ expect(result).toContain("bug, urgent");
257
+ });
258
+
259
+ test("shows assignees", () => {
260
+ const result = formatTaskDetail(task, [], false);
261
+ expect(result).toContain("Assignees:");
262
+ expect(result).toContain("nick");
263
+ });
264
+
265
+ test("shows estimate", () => {
266
+ const result = formatTaskDetail(task, [], false);
267
+ expect(result).toContain("Estimate:");
268
+ expect(result).toContain("3");
269
+ });
270
+
271
+ test("shows due date", () => {
272
+ const result = formatTaskDetail(task, [], false);
273
+ expect(result).toContain("Due:");
274
+ expect(result).toContain("2026-01-15");
275
+ });
276
+
277
+ test("shows blockers with status", () => {
278
+ const result = formatTaskDetail(task, [], false);
279
+ expect(result).toContain("Blockers:");
280
+ expect(result).toContain("tk-c3d4");
281
+ expect(result).toContain("(blocked)");
282
+ });
283
+
284
+ test("shows resolved blockers", () => {
285
+ const resolvedTask: TaskWithMeta = {
286
+ ...task,
287
+ blocked_by_incomplete: false,
288
+ };
289
+ const result = formatTaskDetail(resolvedTask, [], false);
290
+ expect(result).toContain("(resolved)");
291
+ });
292
+
293
+ test("shows overdue indicator", () => {
294
+ const overdueTask: TaskWithMeta = {
295
+ ...task,
296
+ is_overdue: true,
297
+ };
298
+ const result = formatTaskDetail(overdueTask, [], false);
299
+ expect(result).toContain("[OVERDUE]");
300
+ });
301
+
302
+ test("includes log entries", () => {
303
+ const logs: LogEntry[] = [
304
+ { ts: "2024-01-01T00:00:00.000Z", msg: "Started work" },
305
+ { ts: "2024-01-02T00:00:00.000Z", msg: "Made progress" },
306
+ ];
307
+ const result = formatTaskDetail(task, logs, false);
308
+ expect(result).toContain("Log:");
309
+ expect(result).toContain("Started work");
310
+ expect(result).toContain("Made progress");
311
+ });
312
+
313
+ test("shows completed_at for done tasks", () => {
314
+ const doneTask: TaskWithMeta = {
315
+ ...task,
316
+ status: "done",
317
+ completed_at: "2024-01-02T00:00:00.000Z",
318
+ };
319
+ const result = formatTaskDetail(doneTask, [], false);
320
+ expect(result).toContain("Completed:");
321
+ });
322
+ });
323
+
324
+ describe("formatJson", () => {
325
+ test("formats objects as pretty JSON", () => {
326
+ const data = { foo: "bar", num: 42 };
327
+ const result = formatJson(data);
328
+ expect(result).toBe('{\n "foo": "bar",\n "num": 42\n}');
329
+ });
330
+
331
+ test("formats arrays", () => {
332
+ const data = [1, 2, 3];
333
+ const result = formatJson(data);
334
+ expect(result).toBe("[\n 1,\n 2,\n 3\n]");
335
+ });
336
+
337
+ test("handles null", () => {
338
+ expect(formatJson(null)).toBe("null");
339
+ });
340
+
341
+ test("handles nested objects", () => {
342
+ const data = { nested: { value: true } };
343
+ const result = formatJson(data);
344
+ expect(result).toContain('"nested"');
345
+ expect(result).toContain('"value"');
346
+ });
347
+ });
@@ -0,0 +1,162 @@
1
+ import type { Task, TaskWithMeta, LogEntry } from "../types";
2
+ import { PRIORITY_COLORS, STATUS_COLORS, OVERDUE_COLOR, RESET } from "../types";
3
+ import { formatPriority } from "./priority";
4
+
5
+ /**
6
+ * Determines if color output should be used.
7
+ * Respects NO_COLOR env var (https://no-color.org/) and TTY detection.
8
+ */
9
+ export function shouldUseColor(): boolean {
10
+ if (process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== "") {
11
+ return false;
12
+ }
13
+ if (!process.stdout.isTTY) {
14
+ return false;
15
+ }
16
+ return true;
17
+ }
18
+
19
+ function formatId(id: string): string {
20
+ // Truncate project prefix to 6 chars, keep full 4-char ref
21
+ // "myproject-a1b2" -> "myproj-a1b2"
22
+ const dash = id.lastIndexOf("-");
23
+ if (dash === -1) return id.slice(0, 11);
24
+ const project = id.slice(0, dash);
25
+ const ref = id.slice(dash + 1);
26
+ const truncatedProject = project.length > 6 ? project.slice(0, 6) : project;
27
+ return `${truncatedProject}-${ref}`;
28
+ }
29
+
30
+ export function formatTaskRow(task: Task & { id: string }, useColor?: boolean): string {
31
+ const color = useColor ?? shouldUseColor();
32
+ const id = formatId(task.id).padEnd(11);
33
+ const priority = formatPriority(task.priority).padEnd(4);
34
+ const status = task.status.padEnd(7);
35
+ const title = task.title.slice(0, 50);
36
+
37
+ // Check if overdue (due date is before today, not including today)
38
+ let isOverdue = false;
39
+ if (task.due_date && task.status !== "done") {
40
+ const today = new Date();
41
+ today.setHours(0, 0, 0, 0);
42
+ // Parse YYYY-MM-DD as local date (not UTC)
43
+ const parts = task.due_date.split("-").map(Number);
44
+ const year = parts[0];
45
+ const month = parts[1];
46
+ const day = parts[2];
47
+ if (year && month && day) {
48
+ const dueDate = new Date(year, month - 1, day);
49
+ isOverdue = dueDate < today;
50
+ }
51
+ }
52
+
53
+ if (color) {
54
+ const pc = PRIORITY_COLORS[task.priority];
55
+ const sc = isOverdue ? OVERDUE_COLOR : STATUS_COLORS[task.status];
56
+ return `${id} | ${pc}${priority}${RESET} | ${sc}${status}${RESET} | ${title}`;
57
+ }
58
+
59
+ const overdueMarker = isOverdue ? " [OVERDUE]" : "";
60
+ return `${id} | ${priority} | ${status} | ${title}${overdueMarker}`;
61
+ }
62
+
63
+ export function formatTaskList(tasks: (Task & { id: string })[], useColor?: boolean): string {
64
+ if (tasks.length === 0) return "No tasks found.";
65
+ const color = useColor ?? shouldUseColor();
66
+
67
+ const header = "ID | PRIO | STATUS | TITLE";
68
+ const divider = "-".repeat(60);
69
+ const rows = tasks.map((t) => formatTaskRow(t, color));
70
+
71
+ return [header, divider, ...rows].join("\n");
72
+ }
73
+
74
+ export function formatTaskDetail(task: TaskWithMeta, logs: LogEntry[], useColor?: boolean): string {
75
+ const color = useColor ?? shouldUseColor();
76
+ const lines: string[] = [];
77
+
78
+ const sc = color ? STATUS_COLORS[task.status] : "";
79
+ const pc = color ? PRIORITY_COLORS[task.priority] : "";
80
+ const oc = color ? OVERDUE_COLOR : "";
81
+ const r = color ? RESET : "";
82
+
83
+ lines.push(`ID: ${task.id}`);
84
+ lines.push(`Title: ${task.title}`);
85
+ lines.push(`Status: ${sc}${task.status}${r}`);
86
+ lines.push(`Priority: ${pc}${formatPriority(task.priority)}${r}`);
87
+
88
+ if (task.description) {
89
+ lines.push(`Description: ${task.description}`);
90
+ }
91
+
92
+ if (task.labels.length > 0) {
93
+ lines.push(`Labels: ${task.labels.join(", ")}`);
94
+ }
95
+
96
+ if (task.assignees.length > 0) {
97
+ lines.push(`Assignees: ${task.assignees.join(", ")}`);
98
+ }
99
+
100
+ if (task.parent) {
101
+ lines.push(`Parent: ${task.parent}`);
102
+ }
103
+
104
+ if (task.estimate !== null) {
105
+ lines.push(`Estimate: ${task.estimate}`);
106
+ }
107
+
108
+ if (task.due_date) {
109
+ const overdueStr = task.is_overdue ? ` ${oc}[OVERDUE]${r}` : "";
110
+ lines.push(`Due: ${task.due_date}${overdueStr}`);
111
+ }
112
+
113
+ lines.push(`Created: ${formatDate(task.created_at)}`);
114
+ lines.push(`Updated: ${formatDate(task.updated_at)}`);
115
+
116
+ if (task.completed_at) {
117
+ lines.push(`Completed: ${formatDate(task.completed_at)}`);
118
+ }
119
+
120
+ if (task.blocked_by.length > 0) {
121
+ const blockStatus = task.blocked_by_incomplete ? " (blocked)" : " (resolved)";
122
+ lines.push(`Blockers: ${task.blocked_by.join(", ")}${blockStatus}`);
123
+ }
124
+
125
+ if (logs.length > 0) {
126
+ lines.push("");
127
+ lines.push("Log:");
128
+ for (const entry of logs) {
129
+ lines.push(` [${formatDate(entry.ts)}] ${entry.msg}`);
130
+ }
131
+ }
132
+
133
+ return lines.join("\n");
134
+ }
135
+
136
+ function formatDate(timestamp: string): string {
137
+ return new Date(timestamp).toLocaleString();
138
+ }
139
+
140
+ export function formatJson(data: unknown): string {
141
+ return JSON.stringify(data, null, 2);
142
+ }
143
+
144
+ export function formatConfig(config: {
145
+ version: number;
146
+ project: string;
147
+ aliases: Record<string, string>;
148
+ }): string {
149
+ const lines: string[] = [];
150
+ lines.push(`Version: ${config.version}`);
151
+ lines.push(`Project: ${config.project}`);
152
+
153
+ if (Object.keys(config.aliases).length > 0) {
154
+ lines.push("");
155
+ lines.push("Aliases:");
156
+ for (const [alias, path] of Object.entries(config.aliases)) {
157
+ lines.push(` ${alias} → ${path}`);
158
+ }
159
+ }
160
+
161
+ return lines.join("\n");
162
+ }
@@ -0,0 +1,105 @@
1
+ import { test, expect, describe } from "bun:test";
2
+ import { parsePriority, formatPriority, formatPriorityName } from "./priority";
3
+
4
+ describe("parsePriority", () => {
5
+ describe("valid inputs", () => {
6
+ test("returns 3 (medium) for undefined", () => {
7
+ expect(parsePriority(undefined)).toBe(3);
8
+ });
9
+
10
+ test("parses numeric strings 0-4", () => {
11
+ expect(parsePriority("0")).toBe(0);
12
+ expect(parsePriority("1")).toBe(1);
13
+ expect(parsePriority("2")).toBe(2);
14
+ expect(parsePriority("3")).toBe(3);
15
+ expect(parsePriority("4")).toBe(4);
16
+ });
17
+
18
+ test("parses numbers 0-4", () => {
19
+ expect(parsePriority(0)).toBe(0);
20
+ expect(parsePriority(1)).toBe(1);
21
+ expect(parsePriority(2)).toBe(2);
22
+ expect(parsePriority(3)).toBe(3);
23
+ expect(parsePriority(4)).toBe(4);
24
+ });
25
+
26
+ test("parses p0-p4 format (lowercase)", () => {
27
+ expect(parsePriority("p0")).toBe(0);
28
+ expect(parsePriority("p1")).toBe(1);
29
+ expect(parsePriority("p2")).toBe(2);
30
+ expect(parsePriority("p3")).toBe(3);
31
+ expect(parsePriority("p4")).toBe(4);
32
+ });
33
+
34
+ test("parses P0-P4 format (uppercase)", () => {
35
+ expect(parsePriority("P0")).toBe(0);
36
+ expect(parsePriority("P1")).toBe(1);
37
+ expect(parsePriority("P2")).toBe(2);
38
+ expect(parsePriority("P3")).toBe(3);
39
+ expect(parsePriority("P4")).toBe(4);
40
+ });
41
+
42
+ test("parses named priorities", () => {
43
+ expect(parsePriority("none")).toBe(0);
44
+ expect(parsePriority("urgent")).toBe(1);
45
+ expect(parsePriority("high")).toBe(2);
46
+ expect(parsePriority("medium")).toBe(3);
47
+ expect(parsePriority("low")).toBe(4);
48
+ });
49
+
50
+ test("parses named priorities case-insensitively", () => {
51
+ expect(parsePriority("URGENT")).toBe(1);
52
+ expect(parsePriority("High")).toBe(2);
53
+ expect(parsePriority("MEDIUM")).toBe(3);
54
+ });
55
+ });
56
+
57
+ describe("invalid inputs", () => {
58
+ test("throws for out of range numbers", () => {
59
+ expect(() => parsePriority("5")).toThrow("Invalid priority");
60
+ expect(() => parsePriority("-1")).toThrow("Invalid priority");
61
+ expect(() => parsePriority("99")).toThrow("Invalid priority");
62
+ });
63
+
64
+ test("throws for out of range pX format", () => {
65
+ expect(() => parsePriority("p5")).toThrow("Invalid priority");
66
+ expect(() => parsePriority("p6")).toThrow("Invalid priority");
67
+ expect(() => parsePriority("p-1")).toThrow("Invalid priority");
68
+ });
69
+
70
+ test("throws for invalid named priorities", () => {
71
+ expect(() => parsePriority("critical")).toThrow("Invalid priority");
72
+ expect(() => parsePriority("abc")).toThrow("Invalid priority");
73
+ });
74
+
75
+ test("throws for empty string", () => {
76
+ expect(() => parsePriority("")).toThrow("Invalid priority");
77
+ });
78
+
79
+ test("error message includes valid formats", () => {
80
+ expect(() => parsePriority("invalid")).toThrow(
81
+ "Use 0-4, p0-p4, or none/urgent/high/medium/low",
82
+ );
83
+ });
84
+ });
85
+ });
86
+
87
+ describe("formatPriority", () => {
88
+ test("formats priorities as pX", () => {
89
+ expect(formatPriority(0)).toBe("p0");
90
+ expect(formatPriority(1)).toBe("p1");
91
+ expect(formatPriority(2)).toBe("p2");
92
+ expect(formatPriority(3)).toBe("p3");
93
+ expect(formatPriority(4)).toBe("p4");
94
+ });
95
+ });
96
+
97
+ describe("formatPriorityName", () => {
98
+ test("formats priorities as names", () => {
99
+ expect(formatPriorityName(0)).toBe("none");
100
+ expect(formatPriorityName(1)).toBe("urgent");
101
+ expect(formatPriorityName(2)).toBe("high");
102
+ expect(formatPriorityName(3)).toBe("medium");
103
+ expect(formatPriorityName(4)).toBe("low");
104
+ });
105
+ });
@@ -0,0 +1,40 @@
1
+ import type { Priority } from "../types";
2
+ import { PRIORITY_FROM_NAME, PRIORITY_LABELS } from "../types";
3
+
4
+ /**
5
+ * Parse priority from various formats:
6
+ * - Number: 0-4
7
+ * - Prefixed: p0-p4
8
+ * - Named: none, urgent, high, medium, low
9
+ */
10
+ export function parsePriority(input: string | number | undefined): Priority {
11
+ if (input === undefined) return 3; // default: medium
12
+
13
+ const str = String(input).toLowerCase();
14
+
15
+ // Handle named format
16
+ const named = PRIORITY_FROM_NAME[str];
17
+ if (named !== undefined) {
18
+ return named;
19
+ }
20
+
21
+ // Handle pX format
22
+ if (str.startsWith("p")) {
23
+ const num = parseInt(str.slice(1), 10);
24
+ if (num >= 0 && num <= 4) return num as Priority;
25
+ }
26
+
27
+ // Handle numeric format
28
+ const num = parseInt(str, 10);
29
+ if (num >= 0 && num <= 4) return num as Priority;
30
+
31
+ throw new Error(`Invalid priority: ${input}. Use 0-4, p0-p4, or none/urgent/high/medium/low.`);
32
+ }
33
+
34
+ export function formatPriority(p: Priority): string {
35
+ return `p${p}`;
36
+ }
37
+
38
+ export function formatPriorityName(p: Priority): string {
39
+ return PRIORITY_LABELS[p];
40
+ }