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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +96 -58
- package/COVERAGE.md +5 -5
- package/MIGRATING.md +71 -69
- package/README.md +115 -90
- package/package.json +6 -7
- package/skills/README.md +15 -0
- package/skills/linear-sdk-cli/SKILL.md +27 -3
- package/skills/linear-sdk-cli/references/api.md +9 -9
- package/skills/linear-sdk-cli/references/auth.md +18 -0
- package/skills/linear-sdk-cli/references/commands.md +2 -2
- package/skills/linear-sdk-cli/references/comment.md +9 -6
- package/skills/linear-sdk-cli/references/config.md +9 -11
- package/skills/linear-sdk-cli/references/issue.md +72 -18
- package/skills/linear-sdk-cli/references/open.md +29 -0
- package/skills/linear-sdk-cli/references/team.md +16 -0
- package/skills/linear-sdk-cli/scripts/generate-docs.ts +6 -2
- package/skills/linear-sdk-cli-maintenance/SKILL.md +97 -0
- package/src/bin/linear.ts +16 -6
- package/src/cli.ts +3 -1
- package/src/commands/agent-session.ts +13 -5
- package/src/commands/api.ts +15 -9
- package/src/commands/attach.ts +15 -4
- package/src/commands/comment.ts +50 -11
- package/src/commands/completion.ts +2 -6
- package/src/commands/cycle.ts +6 -1
- package/src/commands/discover.ts +6 -2
- package/src/commands/document.ts +4 -1
- package/src/commands/initiative.ts +5 -1
- package/src/commands/issue.ts +392 -91
- package/src/commands/meta.ts +139 -31
- package/src/commands/milestone.ts +4 -1
- package/src/commands/notification.ts +15 -6
- package/src/commands/open.ts +84 -0
- package/src/commands/project.ts +17 -4
- package/src/commands/team.ts +13 -0
- package/src/commands/user.ts +2 -2
- package/src/commands/webhook.ts +12 -2
- package/src/config.ts +501 -184
- package/src/context.ts +13 -4
- package/src/git.ts +28 -3
- package/src/lib/body.ts +26 -2
- package/src/lib/errors.ts +33 -8
- package/src/lib/introspect.ts +76 -7
- package/src/lib/keyring.ts +51 -9
- package/src/lib/mentions.ts +49 -0
- package/src/lib/open.ts +32 -0
- package/src/lib/options.ts +108 -12
- package/src/lib/output-shapes.ts +81 -9
- package/src/lib/pagination.ts +50 -17
- package/src/lib/resolve.ts +40 -7
- package/src/lib/shape.ts +16 -12
- package/src/lib/upload.ts +3 -1
- package/src/output/color.ts +52 -0
- package/src/output/format.ts +96 -18
- package/src/output/markdown.ts +133 -0
- package/src/output/pager.ts +131 -0
- package/src/output/sanitize.ts +1 -1
- package/src/output/table.ts +7 -7
- package/src/services/agent-session.ts +11 -3
- package/src/services/attachment.ts +14 -5
- package/src/services/comment.ts +37 -9
- package/src/services/cycle.ts +11 -4
- package/src/services/document.ts +29 -10
- package/src/services/favorite.ts +15 -9
- package/src/services/initiative-update.ts +16 -15
- package/src/services/initiative.ts +15 -10
- package/src/services/issue.ts +263 -19
- package/src/services/label.ts +12 -2
- package/src/services/milestone.ts +8 -5
- package/src/services/notification.ts +16 -2
- package/src/services/organization.ts +23 -18
- package/src/services/project.ts +55 -34
- package/src/services/roadmap.ts +7 -6
- package/src/services/state.ts +9 -3
- package/src/services/team.ts +67 -24
- package/src/services/user.ts +2 -2
- 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:
|
|
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
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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
|
-
{
|
|
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
|
-
[
|
|
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)`);
|
package/src/commands/api.ts
CHANGED
|
@@ -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(
|
|
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,
|
|
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)
|
|
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);
|
package/src/commands/attach.ts
CHANGED
|
@@ -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(
|
|
31
|
-
|
|
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(
|
|
54
|
-
|
|
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;
|
package/src/commands/comment.ts
CHANGED
|
@@ -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(
|
|
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
|
|
68
|
-
//
|
|
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
|
|
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)
|
|
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(
|
|
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
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/commands/cycle.ts
CHANGED
|
@@ -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(
|
|
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
|
);
|
package/src/commands/discover.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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) {
|
package/src/commands/document.ts
CHANGED
|
@@ -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(
|
|
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
|
-
{
|
|
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
|
];
|