linear-sdk-cli 0.1.3 → 0.2.3

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.
Files changed (79) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +96 -58
  4. package/COVERAGE.md +5 -5
  5. package/MIGRATING.md +71 -69
  6. package/README.md +115 -90
  7. package/package.json +6 -7
  8. package/skills/README.md +15 -0
  9. package/skills/linear-sdk-cli/SKILL.md +27 -3
  10. package/skills/linear-sdk-cli/references/api.md +9 -9
  11. package/skills/linear-sdk-cli/references/auth.md +18 -0
  12. package/skills/linear-sdk-cli/references/commands.md +2 -2
  13. package/skills/linear-sdk-cli/references/comment.md +9 -6
  14. package/skills/linear-sdk-cli/references/config.md +9 -11
  15. package/skills/linear-sdk-cli/references/issue.md +72 -18
  16. package/skills/linear-sdk-cli/references/open.md +29 -0
  17. package/skills/linear-sdk-cli/references/team.md +16 -0
  18. package/skills/linear-sdk-cli/scripts/generate-docs.ts +6 -2
  19. package/skills/linear-sdk-cli-maintenance/SKILL.md +97 -0
  20. package/src/bin/linear.ts +16 -6
  21. package/src/cli.ts +3 -1
  22. package/src/commands/agent-session.ts +13 -5
  23. package/src/commands/api.ts +15 -9
  24. package/src/commands/attach.ts +15 -4
  25. package/src/commands/comment.ts +50 -11
  26. package/src/commands/completion.ts +2 -6
  27. package/src/commands/cycle.ts +6 -1
  28. package/src/commands/discover.ts +6 -2
  29. package/src/commands/document.ts +4 -1
  30. package/src/commands/initiative.ts +5 -1
  31. package/src/commands/issue.ts +392 -91
  32. package/src/commands/meta.ts +139 -31
  33. package/src/commands/milestone.ts +4 -1
  34. package/src/commands/notification.ts +15 -6
  35. package/src/commands/open.ts +84 -0
  36. package/src/commands/project.ts +17 -4
  37. package/src/commands/team.ts +13 -0
  38. package/src/commands/user.ts +2 -2
  39. package/src/commands/webhook.ts +12 -2
  40. package/src/config.ts +501 -184
  41. package/src/context.ts +13 -4
  42. package/src/git.ts +28 -3
  43. package/src/lib/body.ts +26 -2
  44. package/src/lib/errors.ts +33 -8
  45. package/src/lib/introspect.ts +76 -7
  46. package/src/lib/keyring.ts +51 -9
  47. package/src/lib/mentions.ts +49 -0
  48. package/src/lib/open.ts +32 -0
  49. package/src/lib/options.ts +108 -12
  50. package/src/lib/output-shapes.ts +81 -9
  51. package/src/lib/pagination.ts +50 -17
  52. package/src/lib/resolve.ts +40 -7
  53. package/src/lib/shape.ts +16 -12
  54. package/src/lib/upload.ts +3 -1
  55. package/src/output/color.ts +52 -0
  56. package/src/output/format.ts +96 -18
  57. package/src/output/markdown.ts +133 -0
  58. package/src/output/pager.ts +131 -0
  59. package/src/output/sanitize.ts +1 -1
  60. package/src/output/table.ts +7 -7
  61. package/src/services/agent-session.ts +11 -3
  62. package/src/services/attachment.ts +14 -5
  63. package/src/services/comment.ts +37 -9
  64. package/src/services/cycle.ts +11 -4
  65. package/src/services/document.ts +29 -10
  66. package/src/services/favorite.ts +15 -9
  67. package/src/services/initiative-update.ts +16 -15
  68. package/src/services/initiative.ts +15 -10
  69. package/src/services/issue.ts +263 -19
  70. package/src/services/label.ts +12 -2
  71. package/src/services/milestone.ts +8 -5
  72. package/src/services/notification.ts +16 -2
  73. package/src/services/organization.ts +23 -18
  74. package/src/services/project.ts +55 -34
  75. package/src/services/roadmap.ts +7 -6
  76. package/src/services/state.ts +9 -3
  77. package/src/services/team.ts +67 -24
  78. package/src/services/user.ts +2 -2
  79. package/src/services/webhook.ts +28 -17
package/src/bin/linear.ts CHANGED
@@ -10,6 +10,7 @@ import { createProgram, parsedGlobalOptions, usageHint, suppressedHelp } from ".
10
10
  import { Output } from "../output/format.js";
11
11
  import { CliError, ExitCode, normalizeError } from "../lib/errors.js";
12
12
  import { commandPath } from "../lib/options.js";
13
+ import { isDebugEnabled, shouldUseColor } from "../output/color.js";
13
14
 
14
15
  // `linear schema | head -2` is a reader that stops early, and stdout closing
15
16
  // under us is that reader's business, not an error: exit quietly, as every
@@ -53,9 +54,14 @@ program.parseAsync(process.argv).catch((err) => {
53
54
  const json = globals.json === true;
54
55
  const out = new Output({
55
56
  json,
56
- color: globals.noAnsi !== true && !json && process.stderr.isTTY === true,
57
+ color: shouldUseColor({
58
+ disabled: globals.noAnsi,
59
+ json,
60
+ isTTY: process.stderr.isTTY === true,
61
+ }),
57
62
  quiet: globals.quiet === true,
58
- debug: globals.debug === true,
63
+ debug: isDebugEnabled(globals.debug),
64
+ isTTY: process.stderr.isTTY === true,
59
65
  });
60
66
  out.error(cliError);
61
67
  process.exit(cliError.exitCode);
@@ -70,14 +76,18 @@ function fromCommander(err: CommanderError): CliError {
70
76
  if (err.code === "commander.help") {
71
77
  const help = suppressedHelp(program);
72
78
  const path = help ? commandPath(help.command) : "linear";
73
- return new CliError(`Missing subcommand. Run '${path} --help' to see the commands.`, "usage", err.code);
79
+ return new CliError(
80
+ "Missing subcommand.",
81
+ "usage",
82
+ err.code,
83
+ `Run '${path} --help' to see the commands.`,
84
+ );
74
85
  }
75
86
  return new CliError(
76
- [stripErrorPrefix(err.message).replace(/\.?$/, "."), usageHint(program)]
77
- .filter(Boolean)
78
- .join(" "),
87
+ stripErrorPrefix(err.message).replace(/\.?$/, "."),
79
88
  "usage",
80
89
  err.code,
90
+ usageHint(program),
81
91
  );
82
92
  }
83
93
 
package/src/cli.ts CHANGED
@@ -29,6 +29,7 @@ import { registerRoadmap } from "./commands/roadmap.js";
29
29
  import { registerNotification } from "./commands/notification.js";
30
30
  import { registerOrganization } from "./commands/organization.js";
31
31
  import { registerWebhook } from "./commands/webhook.js";
32
+ import { registerOpen } from "./commands/open.js";
32
33
  import { registerCommands, registerSchema } from "./commands/discover.js";
33
34
  import { Context, type GlobalOptions } from "./context.js";
34
35
  import { currentIssueId } from "./git.js";
@@ -58,6 +59,7 @@ export function createProgram(): Command {
58
59
  registerMeta(program);
59
60
  registerApi(program);
60
61
  registerCompletion(program);
62
+ registerOpen(program);
61
63
  // Phase 1: issues. Agent sessions live on issues, so their group is mounted
62
64
  // under `issue` (`linear issue agent-session …`); so is file upload
63
65
  // (`linear issue attach <issue> <file...>`, TES-602).
@@ -108,7 +110,7 @@ export function createProgram(): Command {
108
110
  return;
109
111
  }
110
112
  const detail = await getIssueDetail(ctx.client, id);
111
- await renderIssueDetail(ctx, detail, false);
113
+ await renderIssueDetail(ctx, detail, true);
112
114
  });
113
115
 
114
116
  // Make global options usable in any position (e.g. `linear whoami --json`),
@@ -18,7 +18,12 @@ const ROW_COLUMNS: Column<svc.AgentSessionRow>[] = [
18
18
  { key: "agent", header: "Agent", value: (r) => r.agent?.displayName ?? "—", max: 20 },
19
19
  { key: "issue", header: "Issue", value: (r) => r.issue?.identifier ?? "—" },
20
20
  { key: "createdAt", header: "Created", value: (r) => r.createdAt.slice(0, 10) },
21
- { key: "summary", header: "Summary", value: (r) => r.summary?.replace(/\n/g, " ") ?? "—", max: 60 },
21
+ {
22
+ key: "summary",
23
+ header: "Summary",
24
+ value: (r) => r.summary?.replace(/\n/g, " ") ?? "—",
25
+ max: 60,
26
+ },
22
27
  { key: "id", header: "ID", value: (r) => r.id },
23
28
  ];
24
29
 
@@ -88,7 +93,12 @@ export function registerAgentSession(issue: Command): void {
88
93
  ["Created", d.createdAt],
89
94
  ["Started", d.startedAt],
90
95
  ["Ended", d.endedAt],
91
- ["Dismissed", d.dismissedAt ? `${d.dismissedAt}${d.dismissedBy ? ` by ${d.dismissedBy.displayName}` : ""}` : null],
96
+ [
97
+ "Dismissed",
98
+ d.dismissedAt
99
+ ? `${d.dismissedAt}${d.dismissedBy ? ` by ${d.dismissedBy.displayName}` : ""}`
100
+ : null,
101
+ ],
92
102
  ["External link", d.externalLink],
93
103
  ["URL", d.url],
94
104
  ["Summary", d.summary ? `\n${d.summary}` : null],
@@ -103,9 +113,7 @@ function renderActivities(d: svc.AgentSessionDetail): string {
103
113
  const lines = d.activities.map((a) => {
104
114
  const when = a.createdAt.slice(11, 19);
105
115
  const text =
106
- a.action !== null
107
- ? `${a.action}${a.parameter ? `: ${a.parameter}` : ""}`
108
- : (a.body ?? "");
116
+ a.action !== null ? `${a.action}${a.parameter ? `: ${a.parameter}` : ""}` : (a.body ?? "");
109
117
  return `- ${when} ${a.type}: ${text.replace(/\s*\n\s*/g, " ")}`;
110
118
  });
111
119
  if (d.activitiesTruncated) lines.push(`… more activities not shown (use --json)`);
@@ -32,7 +32,12 @@ export function registerApi(program: Command): void {
32
32
  .command("api [query]")
33
33
  .description("Run a raw GraphQL query or mutation against the Linear API")
34
34
  .option("--query-file <path>", "read the query from a file ('-' for stdin)")
35
- .option("--var <k=v...>", "set a variable (repeatable; string value)", collectKeyVal, {})
35
+ .option(
36
+ "--var <k=v...>",
37
+ "set a variable (repeatable; JSON literals are typed, other values are strings)",
38
+ collectKeyVal,
39
+ {},
40
+ )
36
41
  .option("--vars <json>", "variables as a JSON object")
37
42
  .option("--vars-file <path>", "read variables from a JSON file ('-' for stdin)")
38
43
  .option("--operation <name>", "run this operation of a multi-operation document")
@@ -102,6 +107,11 @@ function readFileOrThrow(path: string, label: string): string {
102
107
  }
103
108
 
104
109
  function resolveQuery(arg: string | undefined, file: string | undefined): string {
110
+ if (arg !== undefined && file !== undefined) {
111
+ throw usageError(
112
+ "Pass the GraphQL query either as an argument or with --query-file, not both.",
113
+ );
114
+ }
105
115
  let query: string | undefined;
106
116
  if (arg !== undefined) query = arg;
107
117
  else if (file !== undefined) query = readFileOrThrow(file, "--query-file");
@@ -119,7 +129,7 @@ function resolveVariables(opts: Record<string, any>): Record<string, unknown> {
119
129
  vars = { ...vars, ...parseJsonObject(text, "--vars-file") };
120
130
  }
121
131
  if (opts.vars) vars = { ...vars, ...parseJsonObject(opts.vars, "--vars") };
122
- if (opts.var) vars = { ...vars, ...(opts.var as Record<string, string>) };
132
+ if (opts.var) vars = { ...vars, ...(opts.var as Record<string, unknown>) };
123
133
  return vars;
124
134
  }
125
135
 
@@ -217,10 +227,7 @@ function describeNames(operations: OperationDefinitionNode[]): string {
217
227
  return operations.map((o) => o.name?.value ?? "<unnamed>").join(", ");
218
228
  }
219
229
 
220
- function unknownOperationMessage(
221
- requested: string,
222
- operations: OperationDefinitionNode[],
223
- ): string {
230
+ function unknownOperationMessage(requested: string, operations: OperationDefinitionNode[]): string {
224
231
  const named = operations.filter((o) => o.name).map((o) => o.name!.value);
225
232
  const available = named.length
226
233
  ? `Available: ${named.join(", ")}.`
@@ -269,7 +276,6 @@ async function paginate(
269
276
  variables: Record<string, unknown>,
270
277
  ): Promise<{ nodes: unknown[]; pageCount: number; truncated: boolean }> {
271
278
  let after: string | undefined = variables.after as string | undefined;
272
- let firstData: any;
273
279
  const allNodes: unknown[] = [];
274
280
  let pages = 0;
275
281
  let truncated = false;
@@ -278,10 +284,10 @@ async function paginate(
278
284
  const result: any = await withRetry(() =>
279
285
  (ctx.client.client as any).rawRequest(query, { ...variables, after }),
280
286
  );
281
- if (!firstData) firstData = result.data;
282
287
  const conn = findConnection(result.data);
283
288
  if (!conn) {
284
- if (pages === 0) throw new CliError("No connection (nodes/pageInfo) found to paginate.", "usage");
289
+ if (pages === 0)
290
+ throw new CliError("No connection (nodes/pageInfo) found to paginate.", "usage");
285
291
  break;
286
292
  }
287
293
  allNodes.push(...conn.nodes);
@@ -27,8 +27,14 @@ export function registerAttach(issue: Command): void {
27
27
  .command("attach <issue> <file...>")
28
28
  .description("Upload files and attach them to an issue (private by default)")
29
29
  .option("--title <title>", "attachment title (single file only; default: the file name)")
30
- .option("--comment <body>", "also post a comment with this body embedding the files as markdown")
31
- .option("--public", "upload to a public, world-readable URL (raster images only; default: private)")
30
+ .option(
31
+ "--comment <body>",
32
+ "also post a comment with this body embedding the files as markdown",
33
+ )
34
+ .option(
35
+ "--public",
36
+ "upload to a public, world-readable URL (raster images only; default: private)",
37
+ )
32
38
  .addHelpText(
33
39
  "after",
34
40
  [
@@ -50,8 +56,13 @@ export function registerAttach(issue: Command): void {
50
56
  comment: opts.comment,
51
57
  public: opts.public === true,
52
58
  onAttached: (a, iss) => {
53
- ctx.output.success(`Attached ${a.filename} (${formatBytes(a.size)}) to ${iss.identifier}`);
54
- if (a.public) ctx.output.warn(`${a.filename} is on a public URL, readable by anyone: ${a.assetUrl}`);
59
+ ctx.output.success(
60
+ `Attached ${a.filename} (${formatBytes(a.size)}) to ${iss.identifier}`,
61
+ );
62
+ if (a.public)
63
+ ctx.output.warn(
64
+ `${a.filename} is on a public URL, readable by anyone: ${a.assetUrl}`,
65
+ );
55
66
  },
56
67
  });
57
68
  const identifier = result.issue.identifier;
@@ -52,26 +52,37 @@ function buildAdd(_o: MountOptions): Command {
52
52
  .argument("[body]")
53
53
  .description("Add a comment to an issue (images uploaded with --attach render inline)")
54
54
  .option("--body-file <path>", "read comment body from a file ('-' = stdin)")
55
+ .option(
56
+ "--mention <user>",
57
+ "prepend a real Linear mention (name, email, me, or id; repeatable)",
58
+ collectArray,
59
+ )
55
60
  .option(
56
61
  "--attach <file>",
57
62
  "upload a file and embed it in the comment (images inline; repeatable; private by default)",
58
63
  collectArray,
59
64
  )
60
- .option("--public", "upload the attachments to public, world-readable URLs (raster images only)")
65
+ .option(
66
+ "--public",
67
+ "upload the attachments to public, world-readable URLs (raster images only)",
68
+ )
61
69
  .action(
62
70
  action(async (ctx: Context, opts, issueArg: string, bodyArg?: string) => {
63
71
  const attachments: string[] = opts.attach ?? [];
72
+ const mentions: string[] = opts.mention ?? [];
64
73
  if (opts.public && attachments.length === 0) {
65
74
  throw usageError("--public applies to uploads; add --attach <file>, or drop --public.");
66
75
  }
67
- // With attachments the body is optional (the embeds are the comment),
68
- // so the editor is not opened for one — the reference CLI's rule too.
76
+ // With attachments or explicit mentions the body is optional, so the
77
+ // editor is not opened for one — the reference CLI's attachment rule,
78
+ // extended to mention-only comments.
69
79
  const body = resolveBody({
70
80
  arg: bodyArg,
71
81
  file: opts.bodyFile,
72
- interactive: ctx.isTTY && attachments.length === 0,
82
+ interactive: ctx.isTTY && attachments.length === 0 && mentions.length === 0,
73
83
  });
74
- if (!body && attachments.length === 0) throw usageError("No comment body provided.");
84
+ if (!body && attachments.length === 0 && mentions.length === 0)
85
+ throw usageError("No comment body provided.");
75
86
  const {
76
87
  issue,
77
88
  comment: created,
@@ -79,9 +90,11 @@ function buildAdd(_o: MountOptions): Command {
79
90
  } = await svc.addComment(ctx.client, issueArg, body ?? "", {
80
91
  attachments,
81
92
  public: opts.public === true,
93
+ mentions,
82
94
  });
83
95
  for (const u of uploads) {
84
- if (u.public) ctx.output.warn(`${u.filename} is on a public URL, readable by anyone: ${u.assetUrl}`);
96
+ if (u.public)
97
+ ctx.output.warn(`${u.filename} is on a public URL, readable by anyone: ${u.assetUrl}`);
85
98
  }
86
99
  ctx.output.emit(
87
100
  {
@@ -110,7 +123,12 @@ function buildUpdate(o: MountOptions): Command {
110
123
  .argument("<commentId>")
111
124
  .argument("[body]")
112
125
  .description("Update a comment's body")
113
- .option("--body-file <path>", "read new body from a file ('-' = stdin)");
126
+ .option("--body-file <path>", "read new body from a file ('-' = stdin)")
127
+ .option(
128
+ "--mention <user>",
129
+ "prepend a real Linear mention (name, email, me, or id; repeatable)",
130
+ collectArray,
131
+ );
114
132
  if (o.aliases !== false) cmd.alias("edit");
115
133
  return cmd.action(
116
134
  action(async (ctx: Context, opts, commentId: string, bodyArg?: string) => {
@@ -127,7 +145,13 @@ function buildUpdate(o: MountOptions): Command {
127
145
  if (body === undefined) throw usageError("No comment body provided.");
128
146
  // The editor path hands back a trimmed body, so compare against the
129
147
  // trimmed original: an untouched session must read as "unchanged".
130
- const updated = await svc.updateComment(ctx.client, commentId, body, current.body.trim());
148
+ const updated = await svc.updateComment(
149
+ ctx.client,
150
+ commentId,
151
+ body,
152
+ current.body.trim(),
153
+ opts.mention ?? [],
154
+ );
131
155
  ctx.output.emit({ id: updated.id, url: updated.url }, () =>
132
156
  ctx.output.success(`Updated comment ${updated.id}`),
133
157
  );
@@ -168,11 +192,26 @@ export function registerComment(program: Command): void {
168
192
  .command("reply <commentId> [body]")
169
193
  .description("Reply to a comment (nested under it)")
170
194
  .option("--body-file <path>", "read reply body from a file ('-' = stdin)")
195
+ .option(
196
+ "--mention <user>",
197
+ "prepend a real Linear mention (name, email, me, or id; repeatable)",
198
+ collectArray,
199
+ )
171
200
  .action(
172
201
  action(async (ctx: Context, opts, commentId: string, bodyArg?: string) => {
173
- const body = resolveBody({ arg: bodyArg, file: opts.bodyFile, interactive: ctx.isTTY });
174
- if (!body) throw usageError("No reply body provided.");
175
- const { comment: created, issue } = await svc.replyToComment(ctx.client, commentId, body);
202
+ const mentions: string[] = opts.mention ?? [];
203
+ const body = resolveBody({
204
+ arg: bodyArg,
205
+ file: opts.bodyFile,
206
+ interactive: ctx.isTTY && mentions.length === 0,
207
+ });
208
+ if (!body && mentions.length === 0) throw usageError("No reply body provided.");
209
+ const { comment: created, issue } = await svc.replyToComment(
210
+ ctx.client,
211
+ commentId,
212
+ body ?? "",
213
+ mentions,
214
+ );
176
215
  ctx.output.emit(
177
216
  { id: created.id, parent: commentId, issue: issue?.identifier ?? null, url: created.url },
178
217
  () => ctx.output.success(`Replied to comment${issue ? ` on ${issue.identifier}` : ""}`),
@@ -47,12 +47,8 @@ compdef _linear linear lin
47
47
  `;
48
48
  case "fish":
49
49
  return `# linear fish completion: linear completion fish > ~/.config/fish/completions/linear.fish
50
- ${commands
51
- .map((c) => `complete -c linear -n '__fish_use_subcommand' -a '${c}'`)
52
- .join("\n")}
53
- ${commands
54
- .map((c) => `complete -c lin -n '__fish_use_subcommand' -a '${c}'`)
55
- .join("\n")}
50
+ ${commands.map((c) => `complete -c linear -n '__fish_use_subcommand' -a '${c}'`).join("\n")}
51
+ ${commands.map((c) => `complete -c lin -n '__fish_use_subcommand' -a '${c}'`).join("\n")}
56
52
  `;
57
53
  }
58
54
  }
@@ -41,7 +41,12 @@ export function registerCycle(program: Command): void {
41
41
  .description("Show a cycle (by id, number, or 'current')")
42
42
  .action(
43
43
  action(async (ctx: Context, opts, id: string) => {
44
- const detail = await svc.getCycleDetail(ctx.client, id, opts.team ?? ctx.defaultTeam, ctx.defaultTeam);
44
+ const detail = await svc.getCycleDetail(
45
+ ctx.client,
46
+ id,
47
+ opts.team ?? ctx.defaultTeam,
48
+ ctx.defaultTeam,
49
+ );
45
50
  emitDetail(ctx, detail);
46
51
  }),
47
52
  );
@@ -26,7 +26,9 @@ import type { Context } from "../context.js";
26
26
  export function registerCommands(program: Command): void {
27
27
  program
28
28
  .command("commands [path...]")
29
- .description("List every command in a machine-readable tree, or describe one (for scripts/agents)")
29
+ .description(
30
+ "List every command in a machine-readable tree, or describe one (for scripts/agents)",
31
+ )
30
32
  .addHelpText(
31
33
  "after",
32
34
  [
@@ -89,7 +91,9 @@ function renderTree(ctx: Context, nodes: CommandNode[], baseDepth = 0): void {
89
91
 
90
92
  /** One command in full: usage, aliases, options, and the `--json` output shape. */
91
93
  function renderOne(ctx: Context, n: CommandNode): void {
92
- ctx.output.line(`Usage: linear ${[n.path, n.options.length ? "[options]" : "", usageArgs(n)].filter(Boolean).join(" ")}`);
94
+ ctx.output.line(
95
+ `Usage: linear ${[n.path, n.options.length ? "[options]" : "", usageArgs(n)].filter(Boolean).join(" ")}`,
96
+ );
93
97
  if (n.description) ctx.output.line(` ${n.description}`);
94
98
  if (n.aliases.length) ctx.output.line(`Aliases: ${n.aliases.join(", ")}`);
95
99
  if (n.options.length) {
@@ -34,7 +34,10 @@ function addTargetOptions(cmd: Command, verb: string): Command {
34
34
  .option("-p, --project <name>", `${verb} a project (name or id)`)
35
35
  .option("--issue <id>", `${verb} an issue (identifier or id)`)
36
36
  .option("--initiative <name>", `${verb} an initiative (name or id)`)
37
- .option(CYCLE_FLAG, `${verb} a cycle (number, name, id, or 'current'; team from --team or config)`)
37
+ .option(
38
+ CYCLE_FLAG,
39
+ `${verb} a cycle (number, name, id, or 'current'; team from --team or config)`,
40
+ )
38
41
  .option("--release <name>", `${verb} a release (name, version, or id)`);
39
42
  }
40
43
 
@@ -18,7 +18,11 @@ import type { Column } from "../output/table.js";
18
18
  const ROW_COLUMNS: Column<svc.InitiativeRow>[] = [
19
19
  { key: "name", header: "Name", value: (r) => r.name, max: 40 },
20
20
  { key: "status", header: "Status", value: (r) => r.status ?? "—", max: 12 },
21
- { key: "priority", header: "Pri", value: (r) => (r.priority ? svc.priorityLabel(r.priority) : "—") },
21
+ {
22
+ key: "priority",
23
+ header: "Pri",
24
+ value: (r) => (r.priority ? svc.priorityLabel(r.priority) : "—"),
25
+ },
22
26
  { key: "targetDate", header: "Target", value: (r) => r.targetDate ?? "—" },
23
27
  { key: "health", header: "Health", value: (r) => r.health ?? "—", max: 12 },
24
28
  ];