@enrichlayer/el-linear 1.44.2 → 1.45.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.
@@ -1,17 +1,16 @@
1
1
  import { LinearClient } from "@linear/sdk";
2
2
  import { getActiveAuth } from "../auth/token-resolver.js";
3
3
  import { resolveUserDisplayName } from "../config/resolver.js";
4
+ import { GET_CYCLE_DETAIL_QUERY, GET_CYCLES_CATALOG_QUERY, GET_LABELS_CATALOG_QUERY, GET_PROJECTS_CATALOG_QUERY, RESOLVE_PROJECT_QUERY, } from "../queries/catalog.js";
4
5
  import { toISOStringOrNow, toISOStringOrUndefined } from "./date-format.js";
5
6
  import { multipleMatchesError, notFoundError } from "./error-messages.js";
7
+ import { createClientCredentialsTokenRenewal, GraphQLService, instrumentLinearClient, } from "./graphql-service.js";
6
8
  import { parseIssueIdentifier } from "./identifier-parser.js";
9
+ import { graphQLErrorHttpStatus, toLinearGraphQLError, } from "./linear-graphql-error.js";
7
10
  import { parseProjectSlugId } from "./project-slug.js";
11
+ import { createOAuthProfileRateLimitAdmission, RateLimitAdmissionRefusal, } from "./rate-limit-admission.js";
8
12
  import { isUuid } from "./uuid.js";
9
13
  const DEFAULT_CYCLE_PAGINATION_LIMIT = 250;
10
- // The Linear SDK types don't accept string orderBy values, but the API does.
11
- // This helper avoids repeating the double-cast at every call site.
12
- function sdkOrderBy(field) {
13
- return field;
14
- }
15
14
  // Filter-building helpers for Linear SDK queries.
16
15
  function eqFilter(value) {
17
16
  return { eq: value };
@@ -22,19 +21,138 @@ function teamIdFilter(teamId) {
22
21
  function nonEmptyFilter(filter) {
23
22
  return Object.keys(filter).length > 0 ? filter : undefined;
24
23
  }
25
- function buildLinearClient(auth) {
24
+ function projectFromCatalog(project) {
25
+ return {
26
+ id: project.id,
27
+ name: project.name,
28
+ description: project.description || undefined,
29
+ state: project.state,
30
+ progress: project.progress,
31
+ teams: project.teams.nodes,
32
+ lead: project.lead ?? undefined,
33
+ targetDate: toISOStringOrUndefined(project.targetDate),
34
+ createdAt: toISOStringOrNow(project.createdAt),
35
+ updatedAt: toISOStringOrNow(project.updatedAt),
36
+ };
37
+ }
38
+ function cycleFromCatalog(cycle) {
39
+ return {
40
+ id: cycle.id,
41
+ name: cycle.name ?? undefined,
42
+ number: cycle.number,
43
+ startsAt: toISOStringOrUndefined(cycle.startsAt),
44
+ endsAt: toISOStringOrUndefined(cycle.endsAt),
45
+ isActive: cycle.isActive,
46
+ isPrevious: cycle.isPrevious,
47
+ isNext: cycle.isNext,
48
+ progress: cycle.progress,
49
+ issueCountHistory: cycle.issueCountHistory ?? undefined,
50
+ team: cycle.team ?? undefined,
51
+ };
52
+ }
53
+ function issueFromCatalog(issue) {
54
+ return {
55
+ id: issue.id,
56
+ identifier: issue.identifier,
57
+ url: issue.url,
58
+ title: issue.title,
59
+ description: issue.description || undefined,
60
+ priority: issue.priority,
61
+ estimate: issue.estimate || undefined,
62
+ state: issue.state ?? undefined,
63
+ assignee: issue.assignee ?? undefined,
64
+ team: issue.team ?? undefined,
65
+ project: issue.project ?? undefined,
66
+ labels: issue.labels.nodes,
67
+ createdAt: toISOStringOrNow(issue.createdAt),
68
+ updatedAt: toISOStringOrNow(issue.updatedAt),
69
+ };
70
+ }
71
+ function errorMessage(error) {
72
+ if (error instanceof Error)
73
+ return error.message;
74
+ if (typeof error === "object" && error !== null) {
75
+ const message = error.message;
76
+ if (typeof message === "string")
77
+ return message;
78
+ }
79
+ return String(error);
80
+ }
81
+ function findRateLimitAdmissionRefusal(error) {
82
+ if (error instanceof RateLimitAdmissionRefusal)
83
+ return error;
84
+ if (typeof error !== "object" || error === null)
85
+ return null;
86
+ const raw = error.raw;
87
+ return raw instanceof RateLimitAdmissionRefusal ? raw : null;
88
+ }
89
+ /**
90
+ * Classify every generated SDK operation after the SDK has normalized its raw
91
+ * transport rejection. Patching `client.client.request` is too early: the
92
+ * LinearClient constructor wraps that transport with `parseLinearError`, which
93
+ * would immediately strip our additive classification fields again. Keeping
94
+ * the wrapper here (rather than guessing in `outputError`) also leaves local
95
+ * validation and not-found errors unclassified.
96
+ */
97
+ export function instrumentLinearGraphQLErrorClassification(client) {
98
+ const sdk = client;
99
+ if (!sdk._request)
100
+ return client;
101
+ const originalRequest = sdk._request.bind(sdk);
102
+ sdk._request = async (document, variables) => {
103
+ try {
104
+ return await originalRequest(document, variables);
105
+ }
106
+ catch (error) {
107
+ const refusal = findRateLimitAdmissionRefusal(error);
108
+ if (refusal)
109
+ throw refusal;
110
+ throw toLinearGraphQLError(error, errorMessage(error));
111
+ }
112
+ };
113
+ return client;
114
+ }
115
+ export function instrumentClientCredentialsRenewal(client, renewAccessToken) {
116
+ if (!renewAccessToken)
117
+ return client;
118
+ const transport = client
119
+ .client;
120
+ if (!transport?.request || !transport.setHeader)
121
+ return client;
122
+ const originalRequest = transport.request.bind(transport);
123
+ transport.request = async (document, variables, requestHeaders) => {
124
+ try {
125
+ return await originalRequest(document, variables, requestHeaders);
126
+ }
127
+ catch (error) {
128
+ if (graphQLErrorHttpStatus(error) !== 401)
129
+ throw error;
130
+ const token = await renewAccessToken();
131
+ transport.setHeader("authorization", `Bearer ${token}`);
132
+ return originalRequest(document, variables, requestHeaders);
133
+ }
134
+ };
135
+ return client;
136
+ }
137
+ function buildLinearClient(auth, admission, renewAccessToken) {
26
138
  if ("oauthToken" in auth) {
27
- // Linear's SDK natively supports OAuth via the `accessToken` option,
28
- // which causes the underlying transport to send
29
- // `Authorization: Bearer <token>` instead of the personal-token shape.
30
- return new LinearClient({ accessToken: auth.oauthToken });
139
+ return instrumentLinearGraphQLErrorClassification(instrumentClientCredentialsRenewal(instrumentLinearClient(new LinearClient({ accessToken: auth.oauthToken }), auth, { admission }), renewAccessToken));
31
140
  }
32
- return new LinearClient({ apiKey: auth.apiKey });
141
+ return instrumentLinearGraphQLErrorClassification(instrumentLinearClient(new LinearClient({ apiKey: auth.apiKey }), auth, {
142
+ admission,
143
+ }));
33
144
  }
34
145
  export class LinearService {
35
146
  client;
36
- constructor(auth) {
37
- this.client = buildLinearClient(auth);
147
+ graphQLService;
148
+ constructor(auth, options = {}) {
149
+ this.client = buildLinearClient(auth, options.admission, options.renewAccessToken);
150
+ this.graphQLService =
151
+ options.graphQLService ??
152
+ new GraphQLService(auth, {
153
+ admission: options.admission,
154
+ renewAccessToken: options.renewAccessToken,
155
+ });
38
156
  }
39
157
  async resolveIssueId(issueId) {
40
158
  if (isUuid(issueId)) {
@@ -149,62 +267,28 @@ export class LinearService {
149
267
  else if (options.excludeStates && options.excludeStates.length > 0) {
150
268
  filter.state = { nin: options.excludeStates };
151
269
  }
152
- // `limit === 0` means "no limit" — paginate the full result set.
153
- // Otherwise a single page of `limit` projects. DEV-4175: `--all` /
154
- // `--limit 0` must return every project so callers never make a false
155
- // "does not exist" determination off a silently truncated page.
156
270
  const unlimited = limit === 0;
157
- // DEV-5325: ProjectFilter has no `teams` relation filter — the
158
- // `teams: { some: … }` shape this used since DEV-4165 errors against
159
- // the live GraphQL schema ('Field "teams" is not defined by type
160
- // "ProjectFilter"'). Team scoping queries from the team side instead:
161
- // `Team.projects` accepts the same name/state ProjectFilter and
162
- // paginates identically, keeping the filter server-side. (Args stay
163
- // inline in each branch so `sdkOrderBy`'s generic keeps its contextual
164
- // type from the SDK signature.)
165
- let page = options.teamId
166
- ? await (await this.client.team(options.teamId)).projects({
271
+ const projectNodes = [];
272
+ let after = null;
273
+ do {
274
+ const response = await this.graphQLService.rawRequest(GET_PROJECTS_CATALOG_QUERY, {
167
275
  filter: nonEmptyFilter(filter),
276
+ teamId: options.teamId ?? "",
277
+ teamScoped: Boolean(options.teamId),
168
278
  first: unlimited ? 250 : limit,
169
- orderBy: sdkOrderBy("updatedAt"),
170
- includeArchived: false,
171
- })
172
- : await this.client.projects({
173
- filter: nonEmptyFilter(filter),
174
- first: unlimited ? 250 : limit,
175
- orderBy: sdkOrderBy("updatedAt"),
176
- includeArchived: false,
279
+ after,
177
280
  });
178
- const projectNodes = [...page.nodes];
179
- if (unlimited) {
180
- while (page.pageInfo.hasNextPage) {
181
- page = await page.fetchNext();
182
- projectNodes.push(...page.nodes);
281
+ const page = options.teamId ? response.team?.projects : response.projects;
282
+ if (!page) {
283
+ throw new Error(options.teamId
284
+ ? `Linear project catalog omitted team ${options.teamId}`
285
+ : "Linear project catalog omitted the projects connection");
183
286
  }
184
- }
185
- const projectsWithData = await Promise.all(projectNodes.map(async (project) => {
186
- const [teams, lead] = await Promise.all([
187
- project.teams(),
188
- project.lead,
189
- ]);
190
- return { project, teams, lead };
191
- }));
192
- return projectsWithData.map(({ project, teams, lead }) => ({
193
- id: project.id,
194
- name: project.name,
195
- description: project.description || undefined,
196
- state: project.state,
197
- progress: project.progress,
198
- teams: teams.nodes.map((team) => ({
199
- id: team.id,
200
- key: team.key,
201
- name: team.name,
202
- })),
203
- lead: lead ? { id: lead.id, name: lead.name } : undefined,
204
- targetDate: toISOStringOrUndefined(project.targetDate),
205
- createdAt: toISOStringOrNow(project.createdAt),
206
- updatedAt: toISOStringOrNow(project.updatedAt),
207
- }));
287
+ projectNodes.push(...page.nodes);
288
+ after =
289
+ unlimited && page.pageInfo.hasNextPage ? page.pageInfo.endCursor : null;
290
+ } while (after);
291
+ return projectNodes.map(projectFromCatalog);
208
292
  }
209
293
  async resolveTeamId(teamKeyOrNameOrId) {
210
294
  if (isUuid(teamKeyOrNameOrId)) {
@@ -277,60 +361,26 @@ export class LinearService {
277
361
  }
278
362
  return statuses.nodes[0].id;
279
363
  }
280
- async buildLabelData(label, scope, team) {
281
- const parent = await label.parent;
282
- const labelData = {
283
- id: label.id,
284
- name: label.name,
285
- color: label.color,
286
- scope,
287
- team,
288
- };
289
- if (parent) {
290
- const parentLabel = await this.client.issueLabel(parent.id);
291
- labelData.group = { id: parent.id, name: parentLabel.name };
292
- }
293
- return labelData;
294
- }
295
364
  async getLabels(teamFilter, limit = 100, nameFilter) {
296
- const labels = [];
297
365
  const labelFilter = {};
298
366
  if (nameFilter) {
299
367
  labelFilter.name = { containsIgnoreCase: nameFilter };
300
368
  }
301
369
  if (teamFilter) {
302
370
  const teamId = await this.resolveTeamId(teamFilter);
303
- const team = await this.client.team(teamId);
304
371
  labelFilter.team = teamIdFilter(teamId);
305
- const teamLabels = await this.client.issueLabels({
306
- filter: labelFilter,
307
- first: limit,
308
- });
309
- const teamRef = { id: team.id, key: team.key, name: team.name };
310
- for (const label of teamLabels.nodes) {
311
- if (label.isGroup) {
312
- continue;
313
- }
314
- labels.push(await this.buildLabelData(label, "team", teamRef));
315
- }
316
- }
317
- else {
318
- const allLabels = await this.client.issueLabels({
319
- filter: nonEmptyFilter(labelFilter),
320
- first: limit,
321
- });
322
- for (const label of allLabels.nodes) {
323
- if (label.isGroup) {
324
- continue;
325
- }
326
- const team = await label.team;
327
- const scope = team ? "team" : "workspace";
328
- const teamRef = team
329
- ? { id: team.id, key: team.key, name: team.name }
330
- : undefined;
331
- labels.push(await this.buildLabelData(label, scope, teamRef));
332
- }
333
372
  }
373
+ const response = await this.graphQLService.rawRequest(GET_LABELS_CATALOG_QUERY, { filter: nonEmptyFilter(labelFilter), first: limit });
374
+ const labels = response.issueLabels.nodes
375
+ .filter((label) => !label.isGroup)
376
+ .map((label) => ({
377
+ id: label.id,
378
+ name: label.name,
379
+ color: label.color,
380
+ scope: label.team ? "team" : "workspace",
381
+ team: label.team ?? undefined,
382
+ group: label.parent ?? undefined,
383
+ }));
334
384
  return { labels };
335
385
  }
336
386
  async createComment(args) {
@@ -370,84 +420,20 @@ export class LinearService {
370
420
  if (activeOnly) {
371
421
  filter.isActive = eqFilter(true);
372
422
  }
373
- const cyclesConnection = await this.client.cycles({
423
+ const response = await this.graphQLService.rawRequest(GET_CYCLES_CATALOG_QUERY, {
374
424
  filter: nonEmptyFilter(filter),
375
- orderBy: sdkOrderBy("createdAt"),
376
425
  first: limit ?? DEFAULT_CYCLE_PAGINATION_LIMIT,
377
426
  });
378
- const cyclesWithData = await Promise.all(cyclesConnection.nodes.map(async (cycle) => {
379
- const team = await cycle.team;
380
- return {
381
- id: cycle.id,
382
- name: cycle.name ?? undefined,
383
- number: cycle.number,
384
- startsAt: toISOStringOrUndefined(cycle.startsAt),
385
- endsAt: toISOStringOrUndefined(cycle.endsAt),
386
- isActive: cycle.isActive,
387
- isPrevious: cycle.isPrevious,
388
- isNext: cycle.isNext,
389
- progress: cycle.progress,
390
- issueCountHistory: cycle.issueCountHistory,
391
- team: team
392
- ? { id: team.id, key: team.key, name: team.name }
393
- : undefined,
394
- };
395
- }));
396
- return cyclesWithData;
427
+ return response.cycles.nodes.map(cycleFromCatalog);
397
428
  }
398
429
  async getCycleById(cycleId, issuesLimit = 50) {
399
- const cycle = await this.client.cycle(cycleId);
400
- const [team, issuesConnection] = await Promise.all([
401
- cycle.team,
402
- cycle.issues({ first: issuesLimit }),
403
- ]);
404
- const issues = [];
405
- for (const issue of issuesConnection.nodes) {
406
- const [state, assignee, issueTeam, project, labels] = await Promise.all([
407
- issue.state,
408
- issue.assignee,
409
- issue.team,
410
- issue.project,
411
- issue.labels(),
412
- ]);
413
- issues.push({
414
- id: issue.id,
415
- identifier: issue.identifier,
416
- url: issue.url,
417
- title: issue.title,
418
- description: issue.description || undefined,
419
- // Linear SDK types `priority` as `number`; the GraphQL schema only
420
- // emits 0-4, so the cast is safe and the runtime range is
421
- // guaranteed by Linear's server-side schema.
422
- priority: issue.priority,
423
- estimate: issue.estimate || undefined,
424
- state: state ? { id: state.id, name: state.name } : undefined,
425
- assignee: assignee
426
- ? { id: assignee.id, name: assignee.name }
427
- : undefined,
428
- team: issueTeam
429
- ? { id: issueTeam.id, key: issueTeam.key, name: issueTeam.name }
430
- : undefined,
431
- project: project ? { id: project.id, name: project.name } : undefined,
432
- labels: labels.nodes.map((label) => ({
433
- id: label.id,
434
- name: label.name,
435
- })),
436
- createdAt: toISOStringOrNow(issue.createdAt),
437
- updatedAt: toISOStringOrNow(issue.updatedAt),
438
- });
439
- }
430
+ const response = await this.graphQLService.rawRequest(GET_CYCLE_DETAIL_QUERY, { id: cycleId, issuesFirst: issuesLimit });
431
+ const cycle = response.cycle;
432
+ if (!cycle)
433
+ throw notFoundError("Cycle", cycleId);
440
434
  return {
441
- id: cycle.id,
442
- name: cycle.name ?? undefined,
443
- number: cycle.number,
444
- startsAt: toISOStringOrUndefined(cycle.startsAt),
445
- endsAt: toISOStringOrUndefined(cycle.endsAt),
446
- isActive: cycle.isActive,
447
- progress: cycle.progress,
448
- issueCountHistory: cycle.issueCountHistory,
449
- team: team ? { id: team.id, key: team.key, name: team.name } : undefined,
450
- issues,
435
+ ...cycleFromCatalog(cycle),
436
+ issues: cycle.issues.nodes.map(issueFromCatalog),
451
437
  };
452
438
  }
453
439
  async resolveCycleId(cycleNameOrId, teamFilter) {
@@ -461,14 +447,10 @@ export class LinearService {
461
447
  const teamId = await this.resolveTeamId(teamFilter);
462
448
  filter.team = teamIdFilter(teamId);
463
449
  }
464
- const cyclesConnection = await this.client.cycles({
465
- filter,
466
- first: 10,
467
- });
468
- const cyclesData = cyclesConnection.nodes;
450
+ const response = await this.graphQLService.rawRequest(GET_CYCLES_CATALOG_QUERY, { filter, first: 10 });
451
+ const cyclesData = response.cycles.nodes;
469
452
  const nodes = [];
470
453
  for (const cycle of cyclesData) {
471
- const team = await cycle.team;
472
454
  nodes.push({
473
455
  id: cycle.id,
474
456
  name: cycle.name ?? undefined,
@@ -477,9 +459,7 @@ export class LinearService {
477
459
  isActive: cycle.isActive,
478
460
  isNext: cycle.isNext,
479
461
  isPrevious: cycle.isPrevious,
480
- team: team
481
- ? { id: team.id, key: team.key, name: team.name }
482
- : undefined,
462
+ team: cycle.team ?? undefined,
483
463
  });
484
464
  }
485
465
  if (nodes.length === 0) {
@@ -531,13 +511,14 @@ export class LinearService {
531
511
  // worse than resolving to an archived project, which the caller can
532
512
  // inspect via the returned UUID. Name resolution stays active-only
533
513
  // because names collide more readily than slug-ids do.
534
- const bySlug = await this.client.projects({
514
+ const bySlug = await this.graphQLService.rawRequest(RESOLVE_PROJECT_QUERY, {
535
515
  filter: { slugId: { eq: slugId } },
536
- first: 1,
516
+ teamId: "",
517
+ teamScoped: false,
537
518
  includeArchived: true,
538
519
  });
539
- if (bySlug.nodes.length > 0) {
540
- return bySlug.nodes[0].id;
520
+ if (bySlug.projects?.nodes.length) {
521
+ return bySlug.projects.nodes[0].id;
541
522
  }
542
523
  // Slug-id form was syntactically valid but didn't match a project
543
524
  // — fall through to name resolution would be misleading (the user
@@ -558,28 +539,23 @@ export class LinearService {
558
539
  // effective project-name collisions at a handful in practice; if a
559
540
  // workspace ever exceeds this we'll see it as a still-ambiguous error
560
541
  // listing the first 5 teams — better than a silent wrong pick.
561
- const projectsConnection = teamId
562
- ? await (await this.client.team(teamId)).projects({ filter, first: 5 })
563
- : await this.client.projects({ filter, first: 5 });
564
- if (projectsConnection.nodes.length === 0) {
542
+ const response = await this.graphQLService.rawRequest(RESOLVE_PROJECT_QUERY, {
543
+ filter,
544
+ teamId: teamId ?? "",
545
+ teamScoped: Boolean(teamId),
546
+ includeArchived: false,
547
+ });
548
+ const projects = teamId
549
+ ? response.team?.projects.nodes
550
+ : response.projects?.nodes;
551
+ if (!projects?.length) {
565
552
  const context = teamInput ? `on team "${teamInput}"` : undefined;
566
553
  throw notFoundError("Project", projectInput, context);
567
554
  }
568
- if (projectsConnection.nodes.length === 1) {
569
- return projectsConnection.nodes[0].id;
570
- }
571
- // Multiple candidates — collect team keys per candidate so the error
572
- // names them concretely. Bounded N ≤ 5 keeps the per-edge resolver
573
- // promise cheap (handful of round-trips on the rare ambiguous path).
574
- const candidates = await Promise.all(projectsConnection.nodes.map(async (p) => {
575
- const teams = await p.teams();
576
- return {
577
- id: p.id,
578
- name: p.name,
579
- teamKeys: teams.nodes.map((t) => t.key),
580
- };
581
- }));
582
- const descriptions = candidates.map((c) => `${c.id} (teams: ${c.teamKeys.join(", ") || "none"})`);
555
+ if (projects.length === 1) {
556
+ return projects[0].id;
557
+ }
558
+ const descriptions = projects.map((project) => `${project.id} (teams: ${project.teams.nodes.map((team) => team.key).join(", ") || "none"})`);
583
559
  throw multipleMatchesError("project", projectInput, descriptions, "scope with --team, or pass the project URL/slug-id");
584
560
  }
585
561
  /**
@@ -634,7 +610,11 @@ export class LinearService {
634
610
  export async function createLinearService(options) {
635
611
  const auth = await getActiveAuth(options);
636
612
  if (auth.kind === "oauth") {
637
- return new LinearService({ oauthToken: auth.token });
613
+ const credential = { oauthToken: auth.token };
614
+ return new LinearService(credential, {
615
+ admission: createOAuthProfileRateLimitAdmission(credential, auth.oauth.clientId, auth.oauth.viewerId),
616
+ renewAccessToken: createClientCredentialsTokenRenewal(auth.oauth),
617
+ });
638
618
  }
639
619
  return new LinearService({ apiKey: auth.token });
640
620
  }
@@ -1,4 +1,43 @@
1
1
  type OutputFormat = "json" | "summary";
2
+ export interface RateLimitInfo {
3
+ /** Maximum requests allowed in the current Linear API window. */
4
+ limit?: number;
5
+ /** Requests still available in the current Linear API window. */
6
+ remaining?: number;
7
+ /** UTC instant when the current Linear API window resets. */
8
+ resetAt?: string;
9
+ /** Number of GraphQL responses observed during this command. */
10
+ observedRequests?: number;
11
+ /** Lowest global request headroom observed during this command. */
12
+ minimumRemaining?: number;
13
+ /** Complexity consumed and remaining in Linear's complexity bucket. */
14
+ complexity?: {
15
+ /** Complexity of the most recently observed response. */
16
+ cost?: number;
17
+ /** Sum of response complexity observed during this command. */
18
+ totalCost?: number;
19
+ limit?: number;
20
+ remaining?: number;
21
+ minimumRemaining?: number;
22
+ resetAt?: string;
23
+ };
24
+ /** Per-endpoint request buckets observed during this command. */
25
+ endpoints?: Record<string, {
26
+ limit?: number;
27
+ remaining?: number;
28
+ minimumRemaining?: number;
29
+ resetAt?: string;
30
+ observedRequests?: number;
31
+ }>;
32
+ }
33
+ /**
34
+ * Record a quota observation for the command's eventual output.
35
+ *
36
+ * The latest response remains authoritative for current remaining/reset
37
+ * values. Multi-request commands additionally retain their total observed
38
+ * request/complexity cost and the lowest headroom seen in every bucket.
39
+ */
40
+ export declare function recordRateLimitInfo(info: RateLimitInfo): void;
2
41
  export declare function setRawMode(enabled: boolean): void;
3
42
  /**
4
43
  * `--quiet` (write commands only): collapse the success payload to a single