@alasano/pi-linear 0.1.0 → 0.2.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 (36) hide show
  1. package/README.md +14 -2
  2. package/assets/linear_list_issues.png +0 -0
  3. package/assets/screenshot.png +0 -0
  4. package/extensions/index.ts +1 -1
  5. package/extensions/renderers/comments.ts +323 -0
  6. package/extensions/renderers/common.ts +305 -0
  7. package/extensions/renderers/documents.ts +326 -0
  8. package/extensions/renderers/initiatives.ts +344 -0
  9. package/extensions/renderers/issue-labels.ts +294 -0
  10. package/extensions/renderers/issue-relations.ts +318 -0
  11. package/extensions/renderers/issue-statuses.ts +199 -0
  12. package/extensions/renderers/issues.ts +373 -0
  13. package/extensions/renderers/milestones.ts +294 -0
  14. package/extensions/renderers/project-labels.ts +279 -0
  15. package/extensions/renderers/project-relations.ts +344 -0
  16. package/extensions/renderers/projects.ts +430 -0
  17. package/extensions/renderers/state.ts +35 -0
  18. package/extensions/renderers/teams.ts +246 -0
  19. package/extensions/renderers/users.ts +242 -0
  20. package/extensions/renderers/workspaces.ts +44 -0
  21. package/extensions/settings.ts +53 -23
  22. package/extensions/tools/comments.ts +17 -0
  23. package/extensions/tools/documents.ts +23 -0
  24. package/extensions/tools/initiatives.ts +24 -0
  25. package/extensions/tools/issue-labels.ts +17 -0
  26. package/extensions/tools/issue-relations.ts +29 -5
  27. package/extensions/tools/issue-statuses.ts +6 -0
  28. package/extensions/tools/issues.ts +29 -0
  29. package/extensions/tools/milestones.ts +18 -0
  30. package/extensions/tools/project-labels.ts +17 -0
  31. package/extensions/tools/project-relations.ts +17 -0
  32. package/extensions/tools/projects.ts +25 -1
  33. package/extensions/tools/teams.ts +11 -3
  34. package/extensions/tools/users.ts +10 -0
  35. package/extensions/tools/workspaces.ts +6 -0
  36. package/package.json +1 -1
@@ -0,0 +1,199 @@
1
+ import {
2
+ type AgentToolResult,
3
+ type Theme,
4
+ type ToolRenderResultOptions,
5
+ } from '@mariozechner/pi-coding-agent';
6
+ import { Text } from '@mariozechner/pi-tui';
7
+ import {
8
+ accentStyle,
9
+ asString,
10
+ cleanOneLine,
11
+ dimStyle,
12
+ expandedJson,
13
+ shouldShowJson,
14
+ LinearListResultComponent,
15
+ mutedStyle,
16
+ renderLinearToolCall,
17
+ renderResponsiveTable,
18
+ toolOutputStyle,
19
+ truncate,
20
+ truncateLine,
21
+ type LinearToolRenderContext,
22
+ type TableColumn,
23
+ type ToolArgs,
24
+ } from './common';
25
+
26
+ type WorkflowStateTeam = {
27
+ id?: string | null;
28
+ key?: string | null;
29
+ name?: string | null;
30
+ };
31
+
32
+ type WorkflowStateLike = {
33
+ id?: string | null;
34
+ name?: string | null;
35
+ type?: string | null;
36
+ color?: string | null;
37
+ position?: number | null;
38
+ description?: string | null;
39
+ createdAt?: string | null;
40
+ updatedAt?: string | null;
41
+ team?: WorkflowStateTeam | null;
42
+ };
43
+
44
+ type WorkflowStateResultDetails = {
45
+ states?: WorkflowStateLike[];
46
+ };
47
+
48
+ const STATUS_LIST_PREVIEW_LIMIT = 20;
49
+ const NAME_LIMIT = 90;
50
+ const DESCRIPTION_LIMIT = 120;
51
+ const TABLE_NAME_MIN_WIDTH = 24;
52
+
53
+ function workflowStateDetails(result: AgentToolResult<any>): WorkflowStateResultDetails {
54
+ return (result.details ?? {}) as WorkflowStateResultDetails;
55
+ }
56
+
57
+ function stateName(state: WorkflowStateLike): string {
58
+ return truncate(cleanOneLine(asString(state.name) ?? '(unnamed status)'), NAME_LIMIT);
59
+ }
60
+
61
+ function stateType(state: WorkflowStateLike): string | undefined {
62
+ return asString(state.type);
63
+ }
64
+
65
+ function teamText(state: WorkflowStateLike): string | undefined {
66
+ return asString(state.team?.key) ?? asString(state.team?.name) ?? asString(state.team?.id);
67
+ }
68
+
69
+ function positionText(state: WorkflowStateLike): string | undefined {
70
+ const position = state.position;
71
+ if (typeof position !== 'number' || !Number.isFinite(position)) return undefined;
72
+ return Number.isInteger(position) ? String(position) : position.toFixed(2);
73
+ }
74
+
75
+ function descriptionSnippet(state: WorkflowStateLike): string | undefined {
76
+ const description = asString(state.description);
77
+ if (!description) return undefined;
78
+ return truncate(cleanOneLine(description), DESCRIPTION_LIMIT);
79
+ }
80
+
81
+ function metadataParts(state: WorkflowStateLike): string[] {
82
+ const team = teamText(state);
83
+ const type = stateType(state);
84
+ const position = positionText(state);
85
+ const color = asString(state.color);
86
+ const description = descriptionSnippet(state);
87
+
88
+ return [
89
+ team ? `team: ${team}` : undefined,
90
+ type ? `type: ${type}` : undefined,
91
+ position ? `pos: ${position}` : undefined,
92
+ color ? `color: ${color}` : undefined,
93
+ description,
94
+ ].filter((part): part is string => !!part);
95
+ }
96
+
97
+ function formatWorkflowStateListLine(
98
+ state: WorkflowStateLike,
99
+ theme: Theme,
100
+ width: number,
101
+ ): string {
102
+ const metadata = metadataParts(state);
103
+ const suffix = metadata.length ? theme.fg('dim', ` · ${metadata.join(' · ')}`) : '';
104
+
105
+ return truncateLine(` ${theme.fg('toolOutput', stateName(state))}${suffix}`, width);
106
+ }
107
+
108
+ function stateTypeStyle(theme: Theme, value: string): (text: string) => string {
109
+ const normalized = value.toLowerCase();
110
+ if (normalized === 'done' || normalized === 'completed') {
111
+ return (text) => theme.fg('success', text);
112
+ }
113
+ if (normalized === 'canceled' || normalized === 'cancelled') {
114
+ return (text) => theme.fg('error', text);
115
+ }
116
+ if (normalized === 'started') {
117
+ return (text) => theme.fg('warning', text);
118
+ }
119
+ if (normalized === 'unstarted') {
120
+ return mutedStyle(theme);
121
+ }
122
+ if (normalized === 'backlog' || value === '—') {
123
+ return dimStyle(theme);
124
+ }
125
+ return mutedStyle(theme);
126
+ }
127
+
128
+ const WORKFLOW_STATE_TABLE_COLUMNS: TableColumn<WorkflowStateLike>[] = [
129
+ {
130
+ id: 'team',
131
+ label: 'Team',
132
+ width: 16,
133
+ value: (state) => teamText(state) ?? '—',
134
+ style: (theme) => accentStyle(theme),
135
+ },
136
+ {
137
+ id: 'type',
138
+ label: 'Type',
139
+ width: 12,
140
+ value: (state) => stateType(state) ?? '—',
141
+ style: stateTypeStyle,
142
+ },
143
+ {
144
+ id: 'position',
145
+ label: 'Position',
146
+ width: 9,
147
+ value: (state) => positionText(state) ?? '—',
148
+ style: (theme) => dimStyle(theme),
149
+ },
150
+ ];
151
+
152
+ function renderWorkflowStateTable(
153
+ states: WorkflowStateLike[],
154
+ theme: Theme,
155
+ width: number,
156
+ ): string[] {
157
+ return renderResponsiveTable(states, theme, width, {
158
+ columns: WORKFLOW_STATE_TABLE_COLUMNS,
159
+ primary: {
160
+ label: 'Name',
161
+ minWidth: TABLE_NAME_MIN_WIDTH,
162
+ value: stateName,
163
+ style: (theme) => toolOutputStyle(theme),
164
+ },
165
+ dropOrder: ['position', 'type', 'team'],
166
+ fallback: formatWorkflowStateListLine,
167
+ });
168
+ }
169
+
170
+ export function renderLinearIssueStatusListCall(args: ToolArgs | undefined, theme: Theme): Text {
171
+ return renderLinearToolCall('linear_list_issue_statuses', args, theme, [
172
+ ['first', 'first'],
173
+ ['orderBy', 'order'],
174
+ ['filter', 'filter'],
175
+ ['includeArchived', 'archived'],
176
+ ]);
177
+ }
178
+
179
+ export function renderLinearIssueStatusListResult(
180
+ result: AgentToolResult<any>,
181
+ options: ToolRenderResultOptions,
182
+ theme: Theme,
183
+ context: LinearToolRenderContext,
184
+ ): Text | LinearListResultComponent<WorkflowStateLike> {
185
+ if (options.isPartial) return new Text(theme.fg('warning', 'Loading issue statuses…'), 0, 0);
186
+ if (shouldShowJson(options, context)) return expandedJson(result, theme);
187
+
188
+ const states = Array.isArray(workflowStateDetails(result).states)
189
+ ? (workflowStateDetails(result).states as WorkflowStateLike[])
190
+ : [];
191
+
192
+ return new LinearListResultComponent(states, theme, {
193
+ noun: 'status',
194
+ pluralNoun: 'statuses',
195
+ emptyLabel: 'No statuses found',
196
+ previewLimit: STATUS_LIST_PREVIEW_LIMIT,
197
+ renderItems: renderWorkflowStateTable,
198
+ });
199
+ }
@@ -0,0 +1,373 @@
1
+ import {
2
+ type AgentToolResult,
3
+ type Theme,
4
+ type ToolRenderResultOptions,
5
+ } from '@mariozechner/pi-coding-agent';
6
+ import { Text } from '@mariozechner/pi-tui';
7
+ import type { LinearIssue } from '../types';
8
+ import {
9
+ accentStyle,
10
+ asString,
11
+ cleanOneLine,
12
+ dimStyle,
13
+ expandedJson,
14
+ shouldShowJson,
15
+ jsonHint,
16
+ LinearListResultComponent,
17
+ mutedStyle,
18
+ renderLinearToolCall,
19
+ renderResponsiveTable,
20
+ toolOutputStyle,
21
+ truncate,
22
+ truncateLine,
23
+ type LinearToolRenderContext,
24
+ type TableColumn,
25
+ type ToolArgs,
26
+ } from './common';
27
+
28
+ type IssueLike = LinearIssue & {
29
+ archivedAt?: string | null;
30
+ completedAt?: string | null;
31
+ estimate?: number | null;
32
+ labels?: { nodes?: Array<{ id?: string; name?: string | null }> } | null;
33
+ priorityLabel?: string | null;
34
+ project?: { id?: string; name?: string | null } | null;
35
+ startedAt?: string | null;
36
+ trashed?: boolean | null;
37
+ };
38
+
39
+ type IssueResultDetails = {
40
+ issue?: LinearIssue | null;
41
+ issues?: LinearIssue[];
42
+ success?: boolean;
43
+ };
44
+
45
+ const ISSUE_LIST_PREVIEW_LIMIT = 20;
46
+ const TITLE_LIMIT = 90;
47
+ const DESCRIPTION_LIMIT = 180;
48
+ const TABLE_TITLE_MIN_WIDTH = 24;
49
+
50
+ function issueDetails(result: AgentToolResult<any>): IssueResultDetails {
51
+ return (result.details ?? {}) as IssueResultDetails;
52
+ }
53
+
54
+ function issueId(issue: IssueLike): string {
55
+ return asString(issue.identifier) ?? asString(issue.id) ?? 'issue';
56
+ }
57
+
58
+ function issueTitle(issue: IssueLike): string {
59
+ return truncate(cleanOneLine(asString(issue.title) ?? '(untitled)'), TITLE_LIMIT);
60
+ }
61
+
62
+ function priorityText(issue: IssueLike): string | undefined {
63
+ const label = asString(issue.priorityLabel);
64
+ if (label) return label;
65
+ if (typeof issue.priority !== 'number' || issue.priority <= 0) return undefined;
66
+ return `P${issue.priority}`;
67
+ }
68
+
69
+ function labelNames(issue: IssueLike): string[] {
70
+ const nodes = Array.isArray(issue.labels?.nodes) ? issue.labels.nodes : [];
71
+ return nodes.map((label) => asString(label.name)).filter((label): label is string => !!label);
72
+ }
73
+
74
+ function labelText(issue: IssueLike, limit = 3): string | undefined {
75
+ const labels = labelNames(issue);
76
+ if (labels.length === 0) return undefined;
77
+
78
+ const shown = labels.slice(0, limit).join(', ');
79
+ const hiddenCount = labels.length - limit;
80
+ return hiddenCount > 0 ? `${shown}, +${hiddenCount}` : shown;
81
+ }
82
+
83
+ function allLabelText(issue: IssueLike): string {
84
+ return labelNames(issue).join(', ') || '—';
85
+ }
86
+
87
+ function metadataParts(
88
+ issue: IssueLike,
89
+ options: { includeProject?: boolean; includeDueDate?: boolean } = {},
90
+ ) {
91
+ const state = asString(issue.state?.name);
92
+ const priority = priorityText(issue);
93
+ const assignee = asString(issue.assignee?.name);
94
+ const labels = labelText(issue);
95
+ const project = options.includeProject ? asString(issue.project?.name) : undefined;
96
+ const dueDate = options.includeDueDate ? asString(issue.dueDate) : undefined;
97
+
98
+ return [
99
+ state,
100
+ priority,
101
+ assignee ? `@${assignee}` : undefined,
102
+ labels,
103
+ project ? `project: ${project}` : undefined,
104
+ dueDate ? `due ${dueDate}` : undefined,
105
+ ].filter((part): part is string => !!part);
106
+ }
107
+
108
+ function formatIssueListLine(issue: IssueLike, theme: Theme, width: number): string {
109
+ const id = issueId(issue).padEnd(9);
110
+ const title = issueTitle(issue);
111
+ const metadata = metadataParts(issue);
112
+ const suffix = metadata.length ? theme.fg('dim', ` · ${metadata.join(' · ')}`) : '';
113
+
114
+ return truncateLine(
115
+ ` ${theme.fg('accent', id)} ${theme.fg('toolOutput', title)}${suffix}`,
116
+ width,
117
+ );
118
+ }
119
+
120
+ function formatIssueTitle(issue: IssueLike, theme: Theme): string {
121
+ return `${theme.fg('accent', issueId(issue))} ${theme.fg('toolOutput', issueTitle(issue))}`;
122
+ }
123
+
124
+ function descriptionSnippet(issue: IssueLike): string | undefined {
125
+ const description = asString(issue.description);
126
+ if (!description) return undefined;
127
+ return truncate(cleanOneLine(description), DESCRIPTION_LIMIT);
128
+ }
129
+
130
+ function priorityStyle(theme: Theme, value: string): (text: string) => string {
131
+ const normalized = value.toLowerCase();
132
+ if (normalized === 'urgent') return (text) => theme.fg('error', text);
133
+ if (normalized === 'high') return (text) => theme.fg('warning', text);
134
+ if (normalized === 'low' || normalized === 'no priority' || value === '—') {
135
+ return dimStyle(theme);
136
+ }
137
+ return mutedStyle(theme);
138
+ }
139
+
140
+ function statusStyle(theme: Theme, value: string): (text: string) => string {
141
+ const normalized = value.toLowerCase();
142
+ if (normalized === 'done' || normalized === 'completed')
143
+ return (text) => theme.fg('success', text);
144
+ if (normalized === 'backlog' || value === '—') return dimStyle(theme);
145
+ return mutedStyle(theme);
146
+ }
147
+
148
+ const ISSUE_TABLE_COLUMNS: TableColumn<IssueLike>[] = [
149
+ {
150
+ id: 'id',
151
+ label: 'ID',
152
+ width: 8,
153
+ value: issueId,
154
+ style: (theme) => accentStyle(theme),
155
+ },
156
+ {
157
+ id: 'state',
158
+ label: 'Status',
159
+ width: 12,
160
+ value: (issue) => asString(issue.state?.name) ?? '—',
161
+ style: statusStyle,
162
+ },
163
+ {
164
+ id: 'priority',
165
+ label: 'Priority',
166
+ width: 11,
167
+ value: (issue) => priorityText(issue) ?? '—',
168
+ style: priorityStyle,
169
+ },
170
+ {
171
+ id: 'assignee',
172
+ label: 'Assignee',
173
+ width: 16,
174
+ value: (issue) => asString(issue.assignee?.name) ?? '—',
175
+ style: (theme) => mutedStyle(theme),
176
+ },
177
+ {
178
+ id: 'labels',
179
+ label: 'Labels',
180
+ width: 24,
181
+ value: allLabelText,
182
+ style: (theme) => dimStyle(theme),
183
+ },
184
+ ];
185
+
186
+ function renderIssueTable(issues: IssueLike[], theme: Theme, width: number): string[] {
187
+ return renderResponsiveTable(issues, theme, width, {
188
+ columns: ISSUE_TABLE_COLUMNS,
189
+ primary: {
190
+ label: 'Title',
191
+ minWidth: TABLE_TITLE_MIN_WIDTH,
192
+ value: issueTitle,
193
+ style: (theme) => toolOutputStyle(theme),
194
+ },
195
+ dropOrder: ['labels', 'assignee', 'priority', 'state'],
196
+ fallback: formatIssueListLine,
197
+ });
198
+ }
199
+
200
+ export function renderLinearIssueListCall(args: ToolArgs | undefined, theme: Theme): Text {
201
+ return renderLinearToolCall('linear_list_issues', args, theme, [
202
+ ['query', 'query'],
203
+ ['teamKey', 'team'],
204
+ ['teamId', 'teamId'],
205
+ ['stateName', 'state'],
206
+ ['assigneeId', 'assignee'],
207
+ ['first', 'first'],
208
+ ['last', 'last'],
209
+ ['orderBy', 'order'],
210
+ ['includeArchived', 'archived'],
211
+ ['filter', 'filter'],
212
+ ['sort', 'sort'],
213
+ ]);
214
+ }
215
+
216
+ export function renderLinearIssueSearchCall(args: ToolArgs | undefined, theme: Theme): Text {
217
+ return renderLinearToolCall('linear_search_issues', args, theme, [
218
+ ['term', 'search'],
219
+ ['includeComments', 'comments'],
220
+ ['teamId', 'teamId'],
221
+ ['first', 'first'],
222
+ ['last', 'last'],
223
+ ['orderBy', 'order'],
224
+ ['includeArchived', 'archived'],
225
+ ['filter', 'filter'],
226
+ ]);
227
+ }
228
+
229
+ export function renderLinearGetIssueCall(args: ToolArgs | undefined, theme: Theme): Text {
230
+ return renderLinearToolCall('linear_get_issue', args, theme, [['issue', 'issue']]);
231
+ }
232
+
233
+ export function renderLinearCreateIssueCall(args: ToolArgs | undefined, theme: Theme): Text {
234
+ return renderLinearToolCall('linear_create_issue', args, theme, [
235
+ ['title', 'title'],
236
+ ['teamKey', 'team'],
237
+ ['teamId', 'teamId'],
238
+ ['stateId', 'state'],
239
+ ['assigneeId', 'assignee'],
240
+ ['priority', 'priority'],
241
+ ['labelIds', 'labels'],
242
+ ['projectId', 'projectId'],
243
+ ['parentId', 'parentId'],
244
+ ['dueDate', 'due'],
245
+ ['input', 'input'],
246
+ ]);
247
+ }
248
+
249
+ export function renderLinearUpdateIssueCall(args: ToolArgs | undefined, theme: Theme): Text {
250
+ return renderLinearToolCall('linear_update_issue', args, theme, [
251
+ ['issue', 'issue'],
252
+ ['title', 'title'],
253
+ ['stateId', 'state'],
254
+ ['assigneeId', 'assignee'],
255
+ ['priority', 'priority'],
256
+ ['dueDate', 'due'],
257
+ ['clearDueDate', 'clearDue'],
258
+ ['labelIds', 'labels'],
259
+ ['addedLabelIds', 'addLabels'],
260
+ ['removedLabelIds', 'removeLabels'],
261
+ ['projectId', 'projectId'],
262
+ ['parentId', 'parentId'],
263
+ ['input', 'input'],
264
+ ]);
265
+ }
266
+
267
+ export function renderLinearDeleteIssueCall(args: ToolArgs | undefined, theme: Theme): Text {
268
+ return renderLinearToolCall('linear_delete_issue', args, theme, [
269
+ ['issue', 'issue'],
270
+ ['permanentlyDelete', 'permanent'],
271
+ ]);
272
+ }
273
+
274
+ export function renderLinearArchiveIssueCall(args: ToolArgs | undefined, theme: Theme): Text {
275
+ return renderLinearToolCall('linear_archive_issue', args, theme, [
276
+ ['issue', 'issue'],
277
+ ['trash', 'trash'],
278
+ ]);
279
+ }
280
+
281
+ export function renderLinearUnarchiveIssueCall(args: ToolArgs | undefined, theme: Theme): Text {
282
+ return renderLinearToolCall('linear_unarchive_issue', args, theme, [['issue', 'issue']]);
283
+ }
284
+
285
+ export function renderLinearIssueListResult(
286
+ result: AgentToolResult<any>,
287
+ options: ToolRenderResultOptions,
288
+ theme: Theme,
289
+ context: LinearToolRenderContext,
290
+ ): Text | LinearListResultComponent<IssueLike> {
291
+ if (options.isPartial) return new Text(theme.fg('warning', 'Loading issues…'), 0, 0);
292
+ if (shouldShowJson(options, context)) return expandedJson(result, theme);
293
+
294
+ const issues = Array.isArray(issueDetails(result).issues)
295
+ ? (issueDetails(result).issues as IssueLike[])
296
+ : [];
297
+
298
+ return new LinearListResultComponent(issues, theme, {
299
+ noun: 'issue',
300
+ emptyLabel: 'No issues found',
301
+ previewLimit: ISSUE_LIST_PREVIEW_LIMIT,
302
+ renderItems: renderIssueTable,
303
+ });
304
+ }
305
+
306
+ export function renderLinearIssueResult(actionLabel: string) {
307
+ return (
308
+ result: AgentToolResult<any>,
309
+ options: ToolRenderResultOptions,
310
+ theme: Theme,
311
+ context: LinearToolRenderContext,
312
+ ): Text => {
313
+ if (options.isPartial) return new Text(theme.fg('warning', `${actionLabel}…`), 0, 0);
314
+ if (shouldShowJson(options, context)) return expandedJson(result, theme);
315
+
316
+ const issue = issueDetails(result).issue as IssueLike | null | undefined;
317
+ if (!issue) {
318
+ return new Text(`\n${theme.fg('dim', 'Issue not found')}\n\n${jsonHint()}`, 0, 0);
319
+ }
320
+
321
+ const metadata = metadataParts(issue, { includeProject: true, includeDueDate: true });
322
+ const description = descriptionSnippet(issue);
323
+
324
+ let text = `\n${theme.fg('success', `✓ ${actionLabel}`)} ${formatIssueTitle(issue, theme)}`;
325
+ if (metadata.length) text += `\n ${theme.fg('dim', metadata.join(' · '))}`;
326
+ if (description) text += `\n ${theme.fg('muted', description)}`;
327
+ if (issue.url) text += `\n ${theme.fg('dim', issue.url)}`;
328
+ text += `\n\n${jsonHint()}`;
329
+
330
+ return new Text(text, 0, 0);
331
+ };
332
+ }
333
+
334
+ export function renderLinearIssueSuccessResult(defaultActionLabel: string) {
335
+ return (
336
+ result: AgentToolResult<any>,
337
+ options: ToolRenderResultOptions,
338
+ theme: Theme,
339
+ context: { args?: unknown },
340
+ ): Text => {
341
+ if (options.isPartial)
342
+ return new Text(theme.fg('warning', `${defaultActionLabel} issue…`), 0, 0);
343
+ if (shouldShowJson(options, context)) return expandedJson(result, theme);
344
+
345
+ const details = issueDetails(result);
346
+ const args = (context.args ?? {}) as {
347
+ issue?: unknown;
348
+ permanentlyDelete?: unknown;
349
+ trash?: unknown;
350
+ };
351
+ const issue = asString(args.issue) ?? 'issue';
352
+ const actionLabel =
353
+ defaultActionLabel === 'Deleted' && args.permanentlyDelete === true
354
+ ? 'Permanently deleted'
355
+ : defaultActionLabel === 'Archived' && args.trash === true
356
+ ? 'Trashed'
357
+ : defaultActionLabel;
358
+
359
+ if (details.success !== true) {
360
+ return new Text(
361
+ `\n${theme.fg('warning', `${actionLabel} status unknown`)}\n\n${jsonHint()}`,
362
+ 0,
363
+ 0,
364
+ );
365
+ }
366
+
367
+ return new Text(
368
+ `\n${theme.fg('success', `✓ ${actionLabel}`)} ${theme.fg('accent', issue)}\n\n${jsonHint()}`,
369
+ 0,
370
+ 0,
371
+ );
372
+ };
373
+ }