linear-sdk-cli 0.1.0

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 (111) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.claude-plugin/plugin.json +9 -0
  3. package/CHANGELOG.md +807 -0
  4. package/COVERAGE.md +495 -0
  5. package/LICENSE +21 -0
  6. package/MIGRATING.md +220 -0
  7. package/README.md +524 -0
  8. package/package.json +87 -0
  9. package/skills/linear-sdk-cli/SKILL.md +534 -0
  10. package/skills/linear-sdk-cli/references/api.md +27 -0
  11. package/skills/linear-sdk-cli/references/attachment.md +82 -0
  12. package/skills/linear-sdk-cli/references/auth.md +174 -0
  13. package/skills/linear-sdk-cli/references/commands.md +37 -0
  14. package/skills/linear-sdk-cli/references/comment.md +154 -0
  15. package/skills/linear-sdk-cli/references/completion.md +17 -0
  16. package/skills/linear-sdk-cli/references/config.md +99 -0
  17. package/skills/linear-sdk-cli/references/cycle.md +131 -0
  18. package/skills/linear-sdk-cli/references/document.md +180 -0
  19. package/skills/linear-sdk-cli/references/favorite.md +78 -0
  20. package/skills/linear-sdk-cli/references/initiative-update.md +67 -0
  21. package/skills/linear-sdk-cli/references/initiative.md +256 -0
  22. package/skills/linear-sdk-cli/references/issue.md +928 -0
  23. package/skills/linear-sdk-cli/references/label.md +112 -0
  24. package/skills/linear-sdk-cli/references/milestone.md +139 -0
  25. package/skills/linear-sdk-cli/references/notification.md +123 -0
  26. package/skills/linear-sdk-cli/references/open.md +29 -0
  27. package/skills/linear-sdk-cli/references/organization.md +99 -0
  28. package/skills/linear-sdk-cli/references/project-update.md +67 -0
  29. package/skills/linear-sdk-cli/references/project.md +234 -0
  30. package/skills/linear-sdk-cli/references/roadmap.md +150 -0
  31. package/skills/linear-sdk-cli/references/schema.md +21 -0
  32. package/skills/linear-sdk-cli/references/state.md +61 -0
  33. package/skills/linear-sdk-cli/references/team.md +227 -0
  34. package/skills/linear-sdk-cli/references/user.md +103 -0
  35. package/skills/linear-sdk-cli/references/webhook.md +151 -0
  36. package/skills/linear-sdk-cli/references/whoami.md +26 -0
  37. package/skills/linear-sdk-cli/scripts/generate-docs.ts +339 -0
  38. package/src/bin/linear.ts +97 -0
  39. package/src/cli.ts +232 -0
  40. package/src/client.ts +172 -0
  41. package/src/commands/agent-session.ts +121 -0
  42. package/src/commands/api.ts +317 -0
  43. package/src/commands/attach.ts +90 -0
  44. package/src/commands/attachment.ts +74 -0
  45. package/src/commands/comment.ts +225 -0
  46. package/src/commands/completion.ts +69 -0
  47. package/src/commands/cycle.ts +125 -0
  48. package/src/commands/discover.ts +170 -0
  49. package/src/commands/document.ts +211 -0
  50. package/src/commands/favorite.ts +72 -0
  51. package/src/commands/initiative-update.ts +47 -0
  52. package/src/commands/initiative.ts +261 -0
  53. package/src/commands/issue.ts +1263 -0
  54. package/src/commands/label.ts +119 -0
  55. package/src/commands/meta.ts +534 -0
  56. package/src/commands/milestone.ts +163 -0
  57. package/src/commands/notification.ts +110 -0
  58. package/src/commands/open.ts +84 -0
  59. package/src/commands/organization.ts +79 -0
  60. package/src/commands/project-update.ts +47 -0
  61. package/src/commands/project.ts +339 -0
  62. package/src/commands/roadmap.ts +131 -0
  63. package/src/commands/state.ts +58 -0
  64. package/src/commands/team.ts +243 -0
  65. package/src/commands/user.ts +107 -0
  66. package/src/commands/webhook.ts +156 -0
  67. package/src/config.ts +1236 -0
  68. package/src/context.ts +117 -0
  69. package/src/git.ts +174 -0
  70. package/src/index.ts +10 -0
  71. package/src/lib/action.ts +26 -0
  72. package/src/lib/body.ts +172 -0
  73. package/src/lib/errors.ts +195 -0
  74. package/src/lib/introspect.ts +150 -0
  75. package/src/lib/keyring.ts +270 -0
  76. package/src/lib/mutation.ts +53 -0
  77. package/src/lib/open.ts +32 -0
  78. package/src/lib/options.ts +640 -0
  79. package/src/lib/output-shapes.ts +562 -0
  80. package/src/lib/pagination.ts +145 -0
  81. package/src/lib/prompt.ts +91 -0
  82. package/src/lib/resolve.ts +596 -0
  83. package/src/lib/shape.ts +273 -0
  84. package/src/lib/status-update.ts +91 -0
  85. package/src/lib/upload.ts +329 -0
  86. package/src/output/color.ts +52 -0
  87. package/src/output/format.ts +238 -0
  88. package/src/output/markdown.ts +133 -0
  89. package/src/output/pager.ts +131 -0
  90. package/src/output/sanitize.ts +76 -0
  91. package/src/output/table.ts +198 -0
  92. package/src/services/agent-session.ts +289 -0
  93. package/src/services/attachment.ts +198 -0
  94. package/src/services/comment.ts +262 -0
  95. package/src/services/cycle.ts +224 -0
  96. package/src/services/document.ts +357 -0
  97. package/src/services/favorite.ts +147 -0
  98. package/src/services/initiative-update.ts +59 -0
  99. package/src/services/initiative.ts +449 -0
  100. package/src/services/issue.ts +1153 -0
  101. package/src/services/label.ts +193 -0
  102. package/src/services/milestone.ts +262 -0
  103. package/src/services/notification.ts +190 -0
  104. package/src/services/organization.ts +144 -0
  105. package/src/services/project-update.ts +36 -0
  106. package/src/services/project.ts +494 -0
  107. package/src/services/roadmap.ts +177 -0
  108. package/src/services/state.ts +127 -0
  109. package/src/services/team.ts +386 -0
  110. package/src/services/user.ts +137 -0
  111. package/src/services/webhook.ts +203 -0
@@ -0,0 +1,103 @@
1
+ # linear user
2
+
3
+ > Inspect workspace users
4
+
5
+ Group alias: `u`
6
+
7
+ _Generated from `linear commands --json`. `linear user --help` (or `<subcommand> --help`) is authoritative._
8
+
9
+ Every command also accepts the global flags `-j/--json`, `--no-input`, `-y/--yes`, `-q/--quiet`, `--workspace <slug>`, `--api-key <key>`, `-t/--team <key>`, `-n/--limit <n>`, `--all`, `-f/--fields <a,b,c>`, `--no-ansi` (alias `--no-color`), and `--debug`. Only command-specific options are listed below.
10
+
11
+ ### `linear user`
12
+
13
+ Inspect workspace users
14
+
15
+ Aliases: `u`
16
+
17
+ ```
18
+ linear user [options]
19
+ ```
20
+
21
+ ### `linear user list`
22
+
23
+ List workspace users
24
+
25
+ Aliases: `ls`
26
+
27
+ ```
28
+ linear user list [options]
29
+ ```
30
+
31
+ | Option | Description |
32
+ | -------------------- | ----------------------------------------------- |
33
+ | `--include-disabled` | include deactivated users (excluded by default) |
34
+
35
+ **Output (`--json`)**: a bare array of objects
36
+
37
+ ```text
38
+ id: string
39
+ displayName: string
40
+ name: string
41
+ email: string
42
+ active: boolean
43
+ admin: boolean
44
+ guest: boolean
45
+ ```
46
+
47
+ ### `linear user me`
48
+
49
+ Show the authenticated viewer
50
+
51
+ ```
52
+ linear user me [options]
53
+ ```
54
+
55
+ **Output (`--json`)**: a bare object
56
+
57
+ ```text
58
+ id: string
59
+ displayName: string
60
+ name: string
61
+ email: string
62
+ active: boolean
63
+ admin: boolean
64
+ guest: boolean
65
+ isMe: boolean
66
+ description: string | null
67
+ statusLabel: string | null
68
+ timezone: string | null
69
+ url: string
70
+ avatarUrl: string | null
71
+ lastSeen: string | null
72
+ createdAt: string
73
+ updatedAt: string
74
+ ```
75
+
76
+ ### `linear user view`
77
+
78
+ Show a user (me, email, name, or id)
79
+
80
+ ```
81
+ linear user view [options] <who>
82
+ ```
83
+
84
+ **Output (`--json`)**: a bare object
85
+
86
+ ```text
87
+ id: string
88
+ displayName: string
89
+ name: string
90
+ email: string
91
+ active: boolean
92
+ admin: boolean
93
+ guest: boolean
94
+ isMe: boolean
95
+ description: string | null
96
+ statusLabel: string | null
97
+ timezone: string | null
98
+ url: string
99
+ avatarUrl: string | null
100
+ lastSeen: string | null
101
+ createdAt: string
102
+ updatedAt: string
103
+ ```
@@ -0,0 +1,151 @@
1
+ # linear webhook
2
+
3
+ > Manage workspace webhooks
4
+
5
+ Group alias: `wh`
6
+
7
+ _Generated from `linear commands --json`. `linear webhook --help` (or `<subcommand> --help`) is authoritative._
8
+
9
+ Every command also accepts the global flags `-j/--json`, `--no-input`, `-y/--yes`, `-q/--quiet`, `--workspace <slug>`, `--api-key <key>`, `-t/--team <key>`, `-n/--limit <n>`, `--all`, `-f/--fields <a,b,c>`, `--no-ansi` (alias `--no-color`), and `--debug`. Only command-specific options are listed below.
10
+
11
+ ### `linear webhook`
12
+
13
+ Manage workspace webhooks
14
+
15
+ Aliases: `wh`
16
+
17
+ ```
18
+ linear webhook [options]
19
+ ```
20
+
21
+ **Output (`--json`)**: a bare object — runs `webhook view` by default
22
+
23
+ ```text
24
+ id: string
25
+ url: string | null
26
+ enabled: boolean
27
+ resourceTypes: string[]
28
+ label: string | null
29
+ allPublicTeams: boolean
30
+ team: string | null
31
+ creator: string | null
32
+ createdAt: string
33
+ updatedAt: string
34
+ ```
35
+
36
+ ### `linear webhook create`
37
+
38
+ Create a webhook (scope to the global --team, or --all-public)
39
+
40
+ Aliases: `new`
41
+
42
+ ```
43
+ linear webhook create [options]
44
+ ```
45
+
46
+ | Option | Description |
47
+ | ---------------------- | ---------------------------------------------- |
48
+ | `--url <url>` | destination URL that receives event payloads |
49
+ | `--resource <type...>` | resource type to subscribe to (repeatable) |
50
+ | `--label <label>` | human-readable label for the webhook |
51
+ | `--all-public` | subscribe to all public teams in the workspace |
52
+ | `--secret <secret>` | secret used to sign webhook payloads |
53
+
54
+ **Output (`--json`)**: a receipt object
55
+
56
+ ```text
57
+ id: string
58
+ url: string | null
59
+ enabled: boolean
60
+ resourceTypes: string[]
61
+ ```
62
+
63
+ ### `linear webhook delete`
64
+
65
+ Delete a webhook
66
+
67
+ Aliases: `rm`
68
+
69
+ ```
70
+ linear webhook delete [options] <id>
71
+ ```
72
+
73
+ **Output (`--json`)**: a receipt object
74
+
75
+ ```text
76
+ id: string
77
+ deleted: boolean
78
+ ```
79
+
80
+ ### `linear webhook list`
81
+
82
+ List webhooks
83
+
84
+ Aliases: `ls`
85
+
86
+ ```
87
+ linear webhook list [options]
88
+ ```
89
+
90
+ **Output (`--json`)**: a bare array of objects
91
+
92
+ ```text
93
+ id: string
94
+ url: string | null
95
+ enabled: boolean
96
+ resourceTypes: string[]
97
+ label: string | null
98
+ ```
99
+
100
+ ### `linear webhook update`
101
+
102
+ Update a webhook
103
+
104
+ Aliases: `edit`
105
+
106
+ ```
107
+ linear webhook update [options] <id>
108
+ ```
109
+
110
+ | Option | Description |
111
+ | ---------------------- | ------------------------------- |
112
+ | `--url <url>` | new destination URL |
113
+ | `--enabled` | enable the webhook |
114
+ | `--disabled` | disable the webhook |
115
+ | `--resource <type...>` | set resource types (repeatable) |
116
+ | `--label <label>` | new label |
117
+ | `--secret <secret>` | new signing secret |
118
+
119
+ **Output (`--json`)**: a receipt object
120
+
121
+ ```text
122
+ id: string
123
+ url: string | null
124
+ enabled: boolean
125
+ resourceTypes: string[]
126
+ ```
127
+
128
+ ### `linear webhook view`
129
+
130
+ Show a webhook
131
+
132
+ Aliases: `show`
133
+
134
+ ```
135
+ linear webhook view [options] <id>
136
+ ```
137
+
138
+ **Output (`--json`)**: a bare object
139
+
140
+ ```text
141
+ id: string
142
+ url: string | null
143
+ enabled: boolean
144
+ resourceTypes: string[]
145
+ label: string | null
146
+ allPublicTeams: boolean
147
+ team: string | null
148
+ creator: string | null
149
+ createdAt: string
150
+ updatedAt: string
151
+ ```
@@ -0,0 +1,26 @@
1
+ # linear whoami
2
+
3
+ > Show the authenticated user
4
+
5
+ _Generated from `linear commands --json`. `linear whoami --help` (or `<subcommand> --help`) is authoritative._
6
+
7
+ Every command also accepts the global flags `-j/--json`, `--no-input`, `-y/--yes`, `-q/--quiet`, `--workspace <slug>`, `--api-key <key>`, `-t/--team <key>`, `-n/--limit <n>`, `--all`, `-f/--fields <a,b,c>`, `--no-ansi` (alias `--no-color`), and `--debug`. Only command-specific options are listed below.
8
+
9
+ ### `linear whoami`
10
+
11
+ Show the authenticated user
12
+
13
+ ```
14
+ linear whoami [options]
15
+ ```
16
+
17
+ **Output (`--json`)**: a bare object
18
+
19
+ ```text
20
+ id: string
21
+ name: string
22
+ displayName: string
23
+ email: string
24
+ admin: boolean
25
+ organization: {id: string, name: string, urlKey: string}
26
+ ```
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Regenerate the linear-sdk-cli skill docs from the CLI's own machine-readable
5
+ * command tree (`linear commands --json`).
6
+ *
7
+ * Produces, idempotently:
8
+ * - skills/linear-sdk-cli/references/<group>.md — one per top-level group
9
+ * - the generated command list inside SKILL.md, between the markers
10
+ * <!-- BEGIN GENERATED COMMANDS --> / <!-- END GENERATED COMMANDS -->
11
+ *
12
+ * The CLI is resolved as `src/bin/linear.ts` under the repo root (run via Bun),
13
+ * falling back to a `linear` binary on PATH. Override with LINEAR_CLI, e.g.
14
+ * LINEAR_CLI="linear" bun run skills/linear-sdk-cli/scripts/generate-docs.ts
15
+ */
16
+
17
+ import { spawnSync } from "node:child_process";
18
+ import { existsSync } from "node:fs";
19
+ import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
20
+ import { dirname, join } from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+ // The one renderer for a shape, shared with `linear commands <path>` so the
23
+ // reference and the CLI spell a row the same way.
24
+ import { renderShape, variantDelta, type OutputShape } from "../../../src/lib/shape.js";
25
+
26
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
27
+ const SKILL_DIR = join(SCRIPT_DIR, "..");
28
+ const REFERENCES_DIR = join(SKILL_DIR, "references");
29
+ const SKILL_MD = join(SKILL_DIR, "SKILL.md");
30
+ const REPO_ROOT = join(SKILL_DIR, "..", "..");
31
+
32
+ const BEGIN_MARKER = "<!-- BEGIN GENERATED COMMANDS -->";
33
+ const END_MARKER = "<!-- END GENERATED COMMANDS -->";
34
+
35
+ interface CliArgument {
36
+ name: string;
37
+ required: boolean;
38
+ variadic: boolean;
39
+ }
40
+
41
+ interface CliOption {
42
+ flags: string;
43
+ description: string;
44
+ }
45
+
46
+ interface CliCommand {
47
+ path: string;
48
+ description: string;
49
+ aliases: string[];
50
+ arguments: CliArgument[];
51
+ options: CliOption[];
52
+ /** What the command prints under `--json` (TES-610); absent on a bare group. */
53
+ output?: OutputShape;
54
+ }
55
+
56
+ /**
57
+ * Options shared by (almost) every command. We collapse them into a single note
58
+ * per reference file instead of repeating ~15 rows under each subcommand.
59
+ *
60
+ * Keyed by flags AND description, because a command may redefine a global with a
61
+ * meaning of its own — `--team` filters (repeatably) on the issue queries and
62
+ * *moves the issue* on `issue update`. Those rows must survive the collapse;
63
+ * matching on the flag string alone would hide exactly the ones worth reading.
64
+ */
65
+ const GLOBAL_OPTIONS = new Map([
66
+ ["-j, --json", "output machine-readable JSON"],
67
+ ["--no-ansi", "disable colored output"],
68
+ ["--api-key <key>", "Linear API key (overrides env/config)"],
69
+ ["--workspace <slug>", "select workspace credential profile"],
70
+ ["-t, --team <key>", "default team key (e.g. TES)"],
71
+ ["-n, --limit <n>", "max results (positive integer; 0 = all)"],
72
+ ["--all", "fetch all results (exhaust pagination)"],
73
+ [
74
+ "-f, --fields <a,b,c>",
75
+ "select fields: table columns or detail lines (human), top-level keys (--json)",
76
+ ],
77
+ ["-y, --yes", "skip confirmation prompts"],
78
+ ["-q, --quiet", "suppress status output"],
79
+ ["--no-input", "never prompt; fail instead"],
80
+ ["--debug", "verbose errors (stack traces, raw GraphQL)"],
81
+ ]);
82
+
83
+ const isGlobal = (opt: CliOption): boolean => GLOBAL_OPTIONS.get(opt.flags) === opt.description;
84
+
85
+ function resolveCli(): string[] {
86
+ const fromEnv = process.env.LINEAR_CLI?.trim();
87
+ if (fromEnv) return fromEnv.split(/\s+/);
88
+ const local = join(REPO_ROOT, "src", "bin", "linear.ts");
89
+ if (existsSync(local)) return ["bun", "run", local];
90
+ return ["linear"];
91
+ }
92
+
93
+ function loadCommands(): CliCommand[] {
94
+ const [bin, ...rest] = resolveCli();
95
+ const result = spawnSync(bin, [...rest, "commands", "--json"], {
96
+ encoding: "utf8",
97
+ maxBuffer: 32 * 1024 * 1024,
98
+ env: { ...process.env, NO_COLOR: "1" },
99
+ });
100
+ if (result.status !== 0) {
101
+ const detail = result.stderr || result.error?.message || "unknown error";
102
+ throw new Error(`\`linear commands --json\` failed: ${detail}`);
103
+ }
104
+ const parsed = JSON.parse(result.stdout) as CliCommand[];
105
+ return parsed.sort((a, b) => a.path.localeCompare(b.path));
106
+ }
107
+
108
+ /** The top-level group name for a command path ("issue create" -> "issue"). */
109
+ function groupOf(path: string): string {
110
+ return path.split(" ")[0]!;
111
+ }
112
+
113
+ function usageLine(cmd: CliCommand): string {
114
+ const parts = ["linear", cmd.path];
115
+ if (cmd.options.length > 0) parts.push("[options]");
116
+ for (const arg of cmd.arguments) {
117
+ const inner = arg.variadic ? `${arg.name}...` : arg.name;
118
+ parts.push(arg.required ? `<${inner}>` : `[${inner}]`);
119
+ }
120
+ return parts.join(" ");
121
+ }
122
+
123
+ function formatCommand(cmd: CliCommand): string {
124
+ const lines: string[] = [];
125
+ lines.push(`### \`linear ${cmd.path}\``);
126
+ lines.push("");
127
+ if (cmd.description) {
128
+ lines.push(cmd.description);
129
+ lines.push("");
130
+ }
131
+ if (cmd.aliases.length > 0) {
132
+ lines.push(`Aliases: ${cmd.aliases.map((a) => `\`${a}\``).join(", ")}`);
133
+ lines.push("");
134
+ }
135
+ lines.push("```");
136
+ lines.push(usageLine(cmd));
137
+ lines.push("```");
138
+ lines.push("");
139
+
140
+ const localOptions = cmd.options.filter((o) => !isGlobal(o));
141
+ if (localOptions.length > 0) {
142
+ lines.push("| Option | Description |");
143
+ lines.push("| --- | --- |");
144
+ for (const opt of localOptions) {
145
+ lines.push(`| \`${escapePipes(opt.flags)}\` | ${escapePipes(opt.description)} |`);
146
+ }
147
+ lines.push("");
148
+ }
149
+
150
+ if (cmd.output) lines.push(...formatOutput(cmd.output));
151
+
152
+ return lines.join("\n");
153
+ }
154
+
155
+ /** `array of objects` / `object` / `receipt` / `raw` / `none`, as prose. */
156
+ function kindLabel(out: OutputShape): string {
157
+ switch (out.kind) {
158
+ case "list":
159
+ return "a bare array of objects";
160
+ case "object":
161
+ return "a bare object";
162
+ case "receipt":
163
+ return "a receipt object";
164
+ case "raw":
165
+ return "raw JSON (keys depend on the request)";
166
+ default:
167
+ return "none — never prints JSON";
168
+ }
169
+ }
170
+
171
+ /**
172
+ * The `--json` output block of a command reference: what kind of value, then
173
+ * one `key: type` line per field in a text fence, then any variant the same
174
+ * way. Rendered with the CLI's own `renderShape`, so `linear commands <path>`
175
+ * and the reference agree to the character.
176
+ */
177
+ function formatOutput(out: OutputShape, heading = "**Output (`--json`)**"): string[] {
178
+ const lines: string[] = [];
179
+ lines.push(`${heading}: ${kindLabel(out)}${out.note ? ` — ${out.note}` : ""}`);
180
+ lines.push("");
181
+ const fields = Object.entries(out.fields ?? {});
182
+ if (fields.length > 0) {
183
+ lines.push("```text");
184
+ for (const [key, shape] of fields) lines.push(`${key}: ${renderShape(shape)}`);
185
+ lines.push("```");
186
+ lines.push("");
187
+ }
188
+ for (const [when, variant] of Object.entries(out.variants ?? {})) {
189
+ const delta = variantDelta(out, variant);
190
+ if (delta) {
191
+ // The base plus a few keys: say only what changes.
192
+ const drop = delta.dropped.length ? `, without \`${delta.dropped.join("`, `")}\`` : "";
193
+ lines.push(`With \`${when}\`: the same${drop}, plus:`);
194
+ lines.push("");
195
+ lines.push("```text");
196
+ for (const [key, shape] of Object.entries(delta.added))
197
+ lines.push(`${key}: ${renderShape(shape)}`);
198
+ lines.push("```");
199
+ lines.push("");
200
+ continue;
201
+ }
202
+ lines.push(...formatOutput(variant, `With \`${when}\``));
203
+ }
204
+ return lines;
205
+ }
206
+
207
+ function escapePipes(text: string): string {
208
+ return text.replace(/\|/g, "\\|");
209
+ }
210
+
211
+ function buildReference(group: string, commands: CliCommand[]): string {
212
+ const root = commands.find((c) => c.path === group);
213
+ const lines: string[] = [];
214
+ lines.push(`# linear ${group}`);
215
+ lines.push("");
216
+ if (root?.description) {
217
+ lines.push(`> ${root.description}`);
218
+ lines.push("");
219
+ }
220
+ if (root && root.aliases.length > 0) {
221
+ lines.push(`Group alias: ${root.aliases.map((a) => `\`${a}\``).join(", ")}`);
222
+ lines.push("");
223
+ }
224
+ lines.push(
225
+ "_Generated from `linear commands --json`. `linear " +
226
+ group +
227
+ " --help` (or `<subcommand> --help`) is authoritative._",
228
+ );
229
+ lines.push("");
230
+ lines.push(
231
+ "Every command also accepts the global flags `-j/--json`, `--no-input`, `-y/--yes`, " +
232
+ "`-q/--quiet`, `--workspace <slug>`, `--api-key <key>`, `-t/--team <key>`, " +
233
+ "`-n/--limit <n>`, `--all`, `-f/--fields <a,b,c>`, `--no-ansi` (alias `--no-color`), " +
234
+ "and `--debug`. " +
235
+ "Only command-specific options are listed below.",
236
+ );
237
+ lines.push("");
238
+
239
+ for (const cmd of commands) {
240
+ lines.push(formatCommand(cmd));
241
+ }
242
+
243
+ return (
244
+ lines
245
+ .join("\n")
246
+ .replace(/\n{3,}/g, "\n\n")
247
+ .trimEnd() + "\n"
248
+ );
249
+ }
250
+
251
+ function buildCommandList(commands: CliCommand[]): string {
252
+ const groups = new Map<string, CliCommand[]>();
253
+ for (const cmd of commands) {
254
+ const g = groupOf(cmd.path);
255
+ if (!groups.has(g)) groups.set(g, []);
256
+ groups.get(g)!.push(cmd);
257
+ }
258
+
259
+ const blocks: string[] = [];
260
+ for (const [, cmds] of [...groups.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
261
+ blocks.push(cmds.map((c) => `linear ${c.path}`).join("\n"));
262
+ }
263
+ return blocks.join("\n\n");
264
+ }
265
+
266
+ async function writeReferences(commands: CliCommand[]): Promise<string[]> {
267
+ await mkdir(REFERENCES_DIR, { recursive: true });
268
+
269
+ // Remove previously generated reference files so renamed/removed groups don't linger.
270
+ try {
271
+ for (const entry of await readdir(REFERENCES_DIR)) {
272
+ if (entry.endsWith(".md")) await rm(join(REFERENCES_DIR, entry));
273
+ }
274
+ } catch {
275
+ // directory was just created; nothing to clean
276
+ }
277
+
278
+ const groups = [...new Set(commands.map((c) => groupOf(c.path)))].sort();
279
+ for (const group of groups) {
280
+ const groupCommands = commands.filter((c) => groupOf(c.path) === group);
281
+ const filepath = join(REFERENCES_DIR, `${group}.md`);
282
+ await writeFile(filepath, buildReference(group, groupCommands), "utf8");
283
+ }
284
+ return groups;
285
+ }
286
+
287
+ async function updateSkillMd(commands: CliCommand[], groups: string[]): Promise<void> {
288
+ const current = await readFile(SKILL_MD, "utf8");
289
+
290
+ const begin = current.indexOf(BEGIN_MARKER);
291
+ const end = current.indexOf(END_MARKER);
292
+ if (begin === -1 || end === -1 || end < begin) {
293
+ throw new Error(`Could not find generated-commands markers in ${SKILL_MD}`);
294
+ }
295
+
296
+ const before = current.slice(0, begin + BEGIN_MARKER.length);
297
+ const after = current.slice(end);
298
+ // Format chosen to match Prettier: no blank line after the opening marker,
299
+ // one blank line before the closing marker, so re-running stays a no-op.
300
+ const block = ["", "```text", buildCommandList(commands), "```", "", ""].join("\n");
301
+ const next = `${before}${block}${after}`;
302
+
303
+ await writeFile(SKILL_MD, next, "utf8");
304
+
305
+ // Sanity check that every group has a reference link (warn only; non-fatal).
306
+ for (const group of groups) {
307
+ if (!next.includes(`references/${group}.md`)) {
308
+ console.warn(`Warning: SKILL.md has no link to references/${group}.md`);
309
+ }
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Run Prettier over the generated markdown so the output matches the repo's
315
+ * formatting (table alignment, list spacing) and re-running stays a clean no-op.
316
+ * Best-effort: if Prettier isn't available the docs are still valid markdown.
317
+ */
318
+ function formatGenerated(): void {
319
+ const result = spawnSync(
320
+ "bunx",
321
+ ["prettier", "--write", "--log-level", "warn", `${SKILL_DIR}/**/*.md`],
322
+ { stdio: "inherit", cwd: REPO_ROOT },
323
+ );
324
+ if (result.status !== 0) {
325
+ console.warn("Warning: prettier formatting step skipped (non-zero exit).");
326
+ }
327
+ }
328
+
329
+ async function main(): Promise<void> {
330
+ const commands = loadCommands();
331
+ const groups = await writeReferences(commands);
332
+ await updateSkillMd(commands, groups);
333
+ formatGenerated();
334
+ console.log(
335
+ `Generated ${groups.length} reference files (${commands.length} commands) and refreshed SKILL.md.`,
336
+ );
337
+ }
338
+
339
+ await main();