linear-sdk-cli 0.1.0 → 0.1.2

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 (71) hide show
  1. package/CHANGELOG.md +46 -46
  2. package/COVERAGE.md +5 -5
  3. package/MIGRATING.md +69 -71
  4. package/README.md +88 -96
  5. package/package.json +8 -9
  6. package/skills/linear-sdk-cli/SKILL.md +3 -13
  7. package/skills/linear-sdk-cli/references/api.md +9 -9
  8. package/skills/linear-sdk-cli/references/auth.md +0 -18
  9. package/skills/linear-sdk-cli/references/commands.md +2 -2
  10. package/skills/linear-sdk-cli/references/config.md +11 -9
  11. package/skills/linear-sdk-cli/references/issue.md +12 -63
  12. package/skills/linear-sdk-cli/references/team.md +0 -16
  13. package/skills/linear-sdk-cli/scripts/generate-docs.ts +2 -6
  14. package/src/bin/linear.ts +6 -16
  15. package/src/cli.ts +1 -3
  16. package/src/commands/agent-session.ts +5 -13
  17. package/src/commands/api.ts +9 -15
  18. package/src/commands/attach.ts +4 -15
  19. package/src/commands/comment.ts +2 -6
  20. package/src/commands/completion.ts +6 -2
  21. package/src/commands/cycle.ts +1 -6
  22. package/src/commands/discover.ts +2 -6
  23. package/src/commands/document.ts +1 -4
  24. package/src/commands/initiative.ts +1 -5
  25. package/src/commands/issue.ts +88 -376
  26. package/src/commands/meta.ts +31 -139
  27. package/src/commands/milestone.ts +1 -4
  28. package/src/commands/notification.ts +6 -15
  29. package/src/commands/project.ts +4 -17
  30. package/src/commands/team.ts +0 -13
  31. package/src/commands/webhook.ts +2 -12
  32. package/src/config.ts +183 -500
  33. package/src/context.ts +4 -13
  34. package/src/git.ts +3 -28
  35. package/src/lib/body.ts +2 -26
  36. package/src/lib/errors.ts +7 -32
  37. package/src/lib/introspect.ts +7 -76
  38. package/src/lib/keyring.ts +9 -51
  39. package/src/lib/options.ts +12 -108
  40. package/src/lib/output-shapes.ts +9 -81
  41. package/src/lib/pagination.ts +17 -50
  42. package/src/lib/resolve.ts +7 -40
  43. package/src/lib/shape.ts +12 -16
  44. package/src/lib/upload.ts +1 -3
  45. package/src/output/format.ts +18 -96
  46. package/src/output/table.ts +7 -7
  47. package/src/services/agent-session.ts +3 -11
  48. package/src/services/attachment.ts +5 -14
  49. package/src/services/comment.ts +4 -12
  50. package/src/services/cycle.ts +4 -11
  51. package/src/services/document.ts +10 -29
  52. package/src/services/favorite.ts +9 -15
  53. package/src/services/initiative-update.ts +15 -16
  54. package/src/services/initiative.ts +10 -15
  55. package/src/services/issue.ts +18 -262
  56. package/src/services/label.ts +2 -12
  57. package/src/services/milestone.ts +5 -8
  58. package/src/services/notification.ts +2 -16
  59. package/src/services/organization.ts +18 -23
  60. package/src/services/project.ts +34 -55
  61. package/src/services/roadmap.ts +6 -7
  62. package/src/services/state.ts +3 -9
  63. package/src/services/team.ts +24 -67
  64. package/src/services/user.ts +2 -2
  65. package/src/services/webhook.ts +17 -28
  66. package/skills/linear-sdk-cli/references/open.md +0 -29
  67. package/src/commands/open.ts +0 -84
  68. package/src/lib/open.ts +0 -32
  69. package/src/output/color.ts +0 -52
  70. package/src/output/markdown.ts +0 -133
  71. package/src/output/pager.ts +0 -131
@@ -1,133 +0,0 @@
1
- /**
2
- * A small, dependency-free Markdown renderer for terminal output.
3
- *
4
- * This intentionally implements the Markdown constructs that occur in Linear
5
- * bodies rather than pretending to be a CommonMark parser. The important
6
- * properties for the CLI are deterministic readable text, optional ANSI, and
7
- * sanitizing API-controlled bytes *before* adding any of our own escapes.
8
- */
9
-
10
- import pc from "picocolors";
11
- import { sanitizeForTerminal } from "./sanitize.js";
12
-
13
- export interface MarkdownRenderOptions {
14
- color?: boolean;
15
- /** Used for horizontal rules. Wrapping itself is left to the terminal. */
16
- width?: number;
17
- }
18
-
19
- /** Render the useful subset of Markdown used by issue/document/project bodies. */
20
- export function renderMarkdown(markdown: string, options: MarkdownRenderOptions = {}): string {
21
- const colors = pc.createColors(options.color === true);
22
- const width = Math.max(10, options.width ?? 80);
23
- // CR and terminal controls disappear here. JSON paths never call this and
24
- // therefore retain the exact API bytes (escaped by JSON.stringify).
25
- const source = sanitizeForTerminal(markdown);
26
- const lines = source.split("\n");
27
- const rendered: string[] = [];
28
- let fence: "```" | "~~~" | undefined;
29
-
30
- for (const original of lines) {
31
- const fenceMatch = original.match(/^\s*(```|~~~)/);
32
- if (fenceMatch) {
33
- const marker = fenceMatch[1] as "```" | "~~~";
34
- if (!fence) fence = marker;
35
- else if (fence === marker) fence = undefined;
36
- // Fence markers and their language labels are presentation syntax.
37
- continue;
38
- }
39
-
40
- if (fence) {
41
- rendered.push(` ${colors.cyan(original)}`.trimEnd());
42
- continue;
43
- }
44
-
45
- const heading = original.match(/^\s{0,3}(#{1,6})\s+(.+?)\s*#*\s*$/);
46
- if (heading) {
47
- const text = inline(heading[2] ?? "", options.color === true);
48
- rendered.push(
49
- heading[1]?.length === 1 ? colors.underline(colors.bold(text)) : colors.bold(text),
50
- );
51
- continue;
52
- }
53
-
54
- if (/^\s{0,3}((\*\s*){3,}|(-\s*){3,}|(_\s*){3,})$/.test(original)) {
55
- rendered.push(colors.dim("─".repeat(Math.min(width, 72))));
56
- continue;
57
- }
58
-
59
- const quote = original.match(/^(\s*)>\s?(.*)$/);
60
- if (quote) {
61
- rendered.push(
62
- `${quote[1] ?? ""}${colors.dim("│")} ${colors.dim(inline(quote[2] ?? "", options.color === true))}`,
63
- );
64
- continue;
65
- }
66
-
67
- const task = original.match(/^(\s*)[-+*]\s+\[([ xX])\]\s+(.*)$/);
68
- if (task) {
69
- rendered.push(
70
- `${task[1] ?? ""}${task[2]?.toLowerCase() === "x" ? "☑" : "☐"} ${inline(task[3] ?? "", options.color === true)}`,
71
- );
72
- continue;
73
- }
74
-
75
- const bullet = original.match(/^(\s*)[-+*]\s+(.*)$/);
76
- if (bullet) {
77
- rendered.push(`${bullet[1] ?? ""}• ${inline(bullet[2] ?? "", options.color === true)}`);
78
- continue;
79
- }
80
-
81
- const ordered = original.match(/^(\s*)(\d+)[.)]\s+(.*)$/);
82
- if (ordered) {
83
- rendered.push(
84
- `${ordered[1] ?? ""}${ordered[2]}. ${inline(ordered[3] ?? "", options.color === true)}`,
85
- );
86
- continue;
87
- }
88
-
89
- rendered.push(inline(original, options.color === true));
90
- }
91
-
92
- // Opening a malformed/unclosed fence must not make content disappear; its
93
- // body has already been emitted as code. Trim only trailing blank lines so
94
- // Output can own the single final newline consistently.
95
- while (rendered.at(-1) === "") rendered.pop();
96
- return rendered.join("\n");
97
- }
98
-
99
- /** Inline Markdown, with code spans shielded from emphasis/link processing. */
100
- function inline(value: string, color: boolean): string {
101
- const colors = pc.createColors(color);
102
- const pieces = value.split(/(`+[^`]*`+)/g);
103
- return pieces
104
- .map((piece) => {
105
- if (/^`+[^`]*`+$/.test(piece)) {
106
- const ticks = piece.match(/^`+/)?.[0].length ?? 1;
107
- return colors.cyan(piece.slice(ticks, -ticks));
108
- }
109
-
110
- return piece
111
- .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_all, alt: string, url: string) => {
112
- const label = alt.trim() ? `image: ${alt.trim()}` : "image";
113
- return `${colors.dim(`[${label}]`)} (${url})`;
114
- })
115
- .replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_all, label: string, url: string) => {
116
- return `${colors.underline(label)} (${url})`;
117
- })
118
- .replace(/<((?:https?|mailto):[^>]+)>/g, "$1")
119
- .replace(/<br\s*\/?>/gi, " ")
120
- .replace(/<\/?[A-Za-z][^>]*>/g, "")
121
- .replace(/\*\*([^*]+)\*\*/g, (_all, text: string) => colors.bold(text))
122
- .replace(/__([^_]+)__/g, (_all, text: string) => colors.bold(text))
123
- .replace(/~~([^~]+)~~/g, (_all, text: string) => colors.strikethrough(text))
124
- .replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, (_all, before: string, text: string) => {
125
- return `${before}${colors.italic(text)}`;
126
- })
127
- .replace(/(^|[^_])_([^_\n]+)_(?!_)/g, (_all, before: string, text: string) => {
128
- return `${before}${colors.italic(text)}`;
129
- })
130
- .replace(/\\([\\`*_[\]{}()#+.!|>~-])/g, "$1");
131
- })
132
- .join("");
133
- }
@@ -1,131 +0,0 @@
1
- /** Conditional, portable pager support for long human output. */
2
-
3
- import { spawnSync } from "node:child_process";
4
- import { displayWidth } from "./sanitize.js";
5
-
6
- export interface PagerCommand {
7
- command: string;
8
- args: string[];
9
- }
10
-
11
- export interface PagerDecision {
12
- enabled?: boolean;
13
- json?: boolean;
14
- isTTY?: boolean;
15
- rows?: number;
16
- columns?: number;
17
- }
18
-
19
- /** Count terminal rows, including the wrapping a terminal applies to long lines. */
20
- export function terminalLineCount(content: string, columns = 80): number {
21
- const width = Math.max(1, columns);
22
- return content.split("\n").reduce((count, line) => {
23
- return count + Math.max(1, Math.ceil(displayWidth(line) / width));
24
- }, 0);
25
- }
26
-
27
- /** Page only deliberately-enabled, long, interactive human output. */
28
- export function shouldUsePager(content: string, decision: PagerDecision): boolean {
29
- if (decision.enabled === false || decision.json === true || decision.isTTY !== true) return false;
30
- const rows = decision.rows && decision.rows > 2 ? decision.rows : 52;
31
- return terminalLineCount(content, decision.columns ?? 80) > rows - 2;
32
- }
33
-
34
- /**
35
- * Resolve `$PAGER` followed by platform fallbacks. The environment value is a
36
- * command line, not a shell program: quotes/backslashes are honoured, while
37
- * expansions and redirections are deliberately not executed.
38
- */
39
- export function pagerCommands(
40
- env: NodeJS.ProcessEnv = process.env,
41
- platform: NodeJS.Platform = process.platform,
42
- ): PagerCommand[] {
43
- const commands: PagerCommand[] = [];
44
- const configured = env.PAGER?.trim();
45
- if (configured) {
46
- const argv = splitCommand(configured);
47
- if (argv.length) commands.push({ command: argv[0]!, args: argv.slice(1) });
48
- }
49
-
50
- if (platform === "win32") {
51
- commands.push({ command: "more.com", args: [] });
52
- } else {
53
- // -R passes through our SGR colour, -X leaves the terminal contents visible.
54
- commands.push({ command: "less", args: ["-R", "-X"] });
55
- commands.push({ command: "more", args: [] });
56
- }
57
-
58
- const seen = new Set<string>();
59
- return commands.filter(({ command, args }) => {
60
- const key = JSON.stringify([command, args]);
61
- if (seen.has(key)) return false;
62
- seen.add(key);
63
- return true;
64
- });
65
- }
66
-
67
- export interface PageOptions {
68
- env?: NodeJS.ProcessEnv;
69
- platform?: NodeJS.Platform;
70
- /** Test seam; production uses spawnSync without a shell. */
71
- run?: (candidate: PagerCommand, content: string, env: NodeJS.ProcessEnv) => boolean;
72
- }
73
-
74
- /** Try the configured pager and fallbacks. False means the caller should print directly. */
75
- export function pageOutput(content: string, options: PageOptions = {}): boolean {
76
- const env = options.env ?? process.env;
77
- const run = options.run ?? runPager;
78
- for (const candidate of pagerCommands(env, options.platform)) {
79
- if (run(candidate, content, env)) return true;
80
- }
81
- return false;
82
- }
83
-
84
- function runPager(candidate: PagerCommand, content: string, env: NodeJS.ProcessEnv): boolean {
85
- const result = spawnSync(candidate.command, candidate.args, {
86
- input: content,
87
- stdio: ["pipe", "inherit", "inherit"],
88
- env,
89
- });
90
- return !result.error && result.status === 0;
91
- }
92
-
93
- /** A non-evaluating, shell-like argv splitter for PAGER. */
94
- function splitCommand(line: string): string[] {
95
- const out: string[] = [];
96
- let current = "";
97
- let inWord = false;
98
- let quote: '"' | "'" | undefined;
99
-
100
- for (let i = 0; i < line.length; i++) {
101
- const char = line[i]!;
102
- if (quote === "'") {
103
- if (char === "'") quote = undefined;
104
- else current += char;
105
- } else if (quote === '"') {
106
- if (char === '"') quote = undefined;
107
- else if (char === "\\" && i + 1 < line.length && '"\\'.includes(line[i + 1]!)) {
108
- current += line[++i];
109
- } else current += char;
110
- } else if (char === "'" || char === '"') {
111
- quote = char;
112
- inWord = true;
113
- } else if (char === "\\" && i + 1 < line.length) {
114
- current += line[++i];
115
- inWord = true;
116
- } else if (/\s/.test(char)) {
117
- if (inWord) out.push(current);
118
- current = "";
119
- inWord = false;
120
- } else {
121
- current += char;
122
- inWord = true;
123
- }
124
- }
125
-
126
- // A malformed environment setting should not abort an otherwise successful
127
- // CLI command; ignore it and continue to the portable fallbacks.
128
- if (quote) return [];
129
- if (inWord) out.push(current);
130
- return out;
131
- }