@alasano/pi-linear 0.1.1 → 0.3.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.
- package/README.md +16 -2
- package/assets/linear_list_issues.png +0 -0
- package/assets/screenshot.png +0 -0
- package/extensions/params.ts +40 -1
- package/extensions/renderers/comments.ts +323 -0
- package/extensions/renderers/common.ts +305 -0
- package/extensions/renderers/documents.ts +326 -0
- package/extensions/renderers/initiatives.ts +344 -0
- package/extensions/renderers/issue-labels.ts +294 -0
- package/extensions/renderers/issue-relations.ts +318 -0
- package/extensions/renderers/issue-statuses.ts +199 -0
- package/extensions/renderers/issues.ts +373 -0
- package/extensions/renderers/milestones.ts +294 -0
- package/extensions/renderers/project-labels.ts +279 -0
- package/extensions/renderers/project-relations.ts +344 -0
- package/extensions/renderers/projects.ts +437 -0
- package/extensions/renderers/state.ts +35 -0
- package/extensions/renderers/teams.ts +246 -0
- package/extensions/renderers/users.ts +242 -0
- package/extensions/renderers/workspaces.ts +44 -0
- package/extensions/selections.ts +10 -3
- package/extensions/settings.ts +40 -7
- package/extensions/tools/comments.ts +30 -11
- package/extensions/tools/documents.ts +42 -11
- package/extensions/tools/initiatives.ts +43 -11
- package/extensions/tools/issue-labels.ts +36 -11
- package/extensions/tools/issue-relations.ts +32 -13
- package/extensions/tools/issue-statuses.ts +19 -11
- package/extensions/tools/issues.ts +53 -19
- package/extensions/tools/milestones.ts +31 -11
- package/extensions/tools/project-labels.ts +30 -11
- package/extensions/tools/project-relations.ts +32 -13
- package/extensions/tools/projects.ts +48 -16
- package/extensions/tools/teams.ts +23 -11
- package/extensions/tools/users.ts +23 -11
- package/extensions/tools/workspaces.ts +6 -0
- package/extensions/types.ts +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,318 @@
|
|
|
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
|
+
asString,
|
|
9
|
+
cleanOneLine,
|
|
10
|
+
dimStyle,
|
|
11
|
+
expandedJson,
|
|
12
|
+
shouldShowJson,
|
|
13
|
+
jsonHint,
|
|
14
|
+
LinearListResultComponent,
|
|
15
|
+
renderLinearToolCall,
|
|
16
|
+
renderResponsiveTable,
|
|
17
|
+
toolOutputStyle,
|
|
18
|
+
truncate,
|
|
19
|
+
truncateLine,
|
|
20
|
+
type LinearToolRenderContext,
|
|
21
|
+
type TableColumn,
|
|
22
|
+
type ToolArgs,
|
|
23
|
+
} from './common';
|
|
24
|
+
|
|
25
|
+
type NamedIssue = {
|
|
26
|
+
id?: string;
|
|
27
|
+
identifier?: string | null;
|
|
28
|
+
title?: string | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type IssueRelationLike = {
|
|
32
|
+
id?: string;
|
|
33
|
+
createdAt?: string | null;
|
|
34
|
+
updatedAt?: string | null;
|
|
35
|
+
type?: string | null;
|
|
36
|
+
issue?: NamedIssue | null;
|
|
37
|
+
relatedIssue?: NamedIssue | null;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type IssueRelationResultDetails = {
|
|
41
|
+
issueRelation?: IssueRelationLike | null;
|
|
42
|
+
issueRelations?: IssueRelationLike[];
|
|
43
|
+
success?: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const ISSUE_RELATION_LIST_PREVIEW_LIMIT = 20;
|
|
47
|
+
const ISSUE_TITLE_LIMIT = 54;
|
|
48
|
+
const TABLE_RELATION_MIN_WIDTH = 24;
|
|
49
|
+
|
|
50
|
+
function issueRelationDetails(result: AgentToolResult<any>): IssueRelationResultDetails {
|
|
51
|
+
return (result.details ?? {}) as IssueRelationResultDetails;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function argsObject(context: { args?: unknown }): ToolArgs {
|
|
55
|
+
return context.args && typeof context.args === 'object' && !Array.isArray(context.args)
|
|
56
|
+
? (context.args as ToolArgs)
|
|
57
|
+
: {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function dateText(value: unknown): string | undefined {
|
|
61
|
+
const date = asString(value);
|
|
62
|
+
if (!date) return undefined;
|
|
63
|
+
|
|
64
|
+
const timeSeparator = date.indexOf('T');
|
|
65
|
+
return timeSeparator >= 0 ? date.slice(0, timeSeparator) : date;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function issueRef(issue: NamedIssue | null | undefined): string | undefined {
|
|
69
|
+
return asString(issue?.identifier) ?? asString(issue?.id);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function issueTitle(issue: NamedIssue | null | undefined): string | undefined {
|
|
73
|
+
const title = asString(issue?.title);
|
|
74
|
+
return title ? truncate(cleanOneLine(title), ISSUE_TITLE_LIMIT) : undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function relationType(relation: IssueRelationLike): string {
|
|
78
|
+
return asString(relation.type) ?? 'related';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function relationVerb(relation: IssueRelationLike): string {
|
|
82
|
+
const type = relationType(relation).toLowerCase();
|
|
83
|
+
if (type === 'blocks') return 'blocks';
|
|
84
|
+
if (type === 'duplicate') return 'duplicates';
|
|
85
|
+
if (type === 'related') return 'related to';
|
|
86
|
+
if (type === 'similar') return 'similar to';
|
|
87
|
+
return type.replace(/[_-]+/g, ' ');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function relationId(relation: IssueRelationLike): string | undefined {
|
|
91
|
+
return asString(relation.id);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function relationSummary(relation: IssueRelationLike): string {
|
|
95
|
+
const issue = issueRef(relation.issue);
|
|
96
|
+
const relatedIssue = issueRef(relation.relatedIssue);
|
|
97
|
+
if (issue && relatedIssue) return `${issue} ${relationVerb(relation)} ${relatedIssue}`;
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
relationId(relation) ?? [issue, relationVerb(relation), relatedIssue].filter(Boolean).join(' ')
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function formatIssueWithTitle(issue: NamedIssue | null | undefined, theme: Theme): string {
|
|
105
|
+
const ref = issueRef(issue);
|
|
106
|
+
const title = issueTitle(issue);
|
|
107
|
+
|
|
108
|
+
if (ref && title) return `${theme.fg('accent', ref)} ${theme.fg('toolOutput', title)}`;
|
|
109
|
+
if (ref) return theme.fg('accent', ref);
|
|
110
|
+
if (title) return theme.fg('toolOutput', title);
|
|
111
|
+
return theme.fg('dim', 'unknown issue');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function formatRelationInline(relation: IssueRelationLike, theme: Theme): string {
|
|
115
|
+
const issue = issueRef(relation.issue);
|
|
116
|
+
const relatedIssue = issueRef(relation.relatedIssue);
|
|
117
|
+
if (issue && relatedIssue) {
|
|
118
|
+
return `${theme.fg('accent', issue)} ${theme.fg('toolOutput', relationVerb(relation))} ${theme.fg(
|
|
119
|
+
'accent',
|
|
120
|
+
relatedIssue,
|
|
121
|
+
)}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return theme.fg('toolOutput', relationSummary(relation));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function formatRelationCardSummary(relation: IssueRelationLike, theme: Theme): string {
|
|
128
|
+
const issue = issueRef(relation.issue) ?? issueTitle(relation.issue);
|
|
129
|
+
const relatedIssue = issueRef(relation.relatedIssue) ?? issueTitle(relation.relatedIssue);
|
|
130
|
+
if (!issue && !relatedIssue) return theme.fg('accent', relationId(relation) ?? 'issue relation');
|
|
131
|
+
|
|
132
|
+
return `${formatIssueWithTitle(relation.issue, theme)} ${theme.fg(
|
|
133
|
+
'toolOutput',
|
|
134
|
+
relationVerb(relation),
|
|
135
|
+
)} ${formatIssueWithTitle(relation.relatedIssue, theme)}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function listMetadataParts(relation: IssueRelationLike): string[] {
|
|
139
|
+
const updated = dateText(relation.updatedAt);
|
|
140
|
+
const id = relationId(relation);
|
|
141
|
+
|
|
142
|
+
return [updated ? `updated: ${updated}` : undefined, id ? `id: ${id}` : undefined].filter(
|
|
143
|
+
(part): part is string => !!part,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function cardMetadataParts(relation: IssueRelationLike): string[] {
|
|
148
|
+
const updated = dateText(relation.updatedAt);
|
|
149
|
+
const created = dateText(relation.createdAt);
|
|
150
|
+
const id = relationId(relation);
|
|
151
|
+
|
|
152
|
+
return [
|
|
153
|
+
updated ? `updated: ${updated}` : undefined,
|
|
154
|
+
created ? `created: ${created}` : undefined,
|
|
155
|
+
id ? `id: ${id}` : undefined,
|
|
156
|
+
].filter((part): part is string => !!part);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function formatIssueRelationListLine(
|
|
160
|
+
relation: IssueRelationLike,
|
|
161
|
+
theme: Theme,
|
|
162
|
+
width: number,
|
|
163
|
+
): string {
|
|
164
|
+
const metadata = listMetadataParts(relation);
|
|
165
|
+
const suffix = metadata.length ? theme.fg('dim', ` · ${metadata.join(' · ')}`) : '';
|
|
166
|
+
|
|
167
|
+
return truncateLine(` ${formatRelationInline(relation, theme)}${suffix}`, width);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const ISSUE_RELATION_TABLE_COLUMNS: TableColumn<IssueRelationLike>[] = [
|
|
171
|
+
{
|
|
172
|
+
id: 'updated',
|
|
173
|
+
label: 'Updated',
|
|
174
|
+
width: 10,
|
|
175
|
+
value: (relation) => dateText(relation.updatedAt) ?? '—',
|
|
176
|
+
style: (theme) => dimStyle(theme),
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
function renderIssueRelationTable(
|
|
181
|
+
issueRelations: IssueRelationLike[],
|
|
182
|
+
theme: Theme,
|
|
183
|
+
width: number,
|
|
184
|
+
): string[] {
|
|
185
|
+
return renderResponsiveTable(issueRelations, theme, width, {
|
|
186
|
+
columns: ISSUE_RELATION_TABLE_COLUMNS,
|
|
187
|
+
primary: {
|
|
188
|
+
label: 'Relation',
|
|
189
|
+
minWidth: TABLE_RELATION_MIN_WIDTH,
|
|
190
|
+
value: relationSummary,
|
|
191
|
+
style: (theme) => toolOutputStyle(theme),
|
|
192
|
+
},
|
|
193
|
+
dropOrder: ['updated'],
|
|
194
|
+
fallback: formatIssueRelationListLine,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function renderIssueRelationCard(
|
|
199
|
+
actionLabel: string,
|
|
200
|
+
relation: IssueRelationLike | null | undefined,
|
|
201
|
+
theme: Theme,
|
|
202
|
+
): Text {
|
|
203
|
+
if (!relation) {
|
|
204
|
+
return new Text(`\n${theme.fg('dim', 'Issue relation not found')}\n\n${jsonHint()}`, 0, 0);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const metadata = cardMetadataParts(relation);
|
|
208
|
+
let text = `\n${theme.fg('success', `✓ ${actionLabel}`)} ${formatRelationCardSummary(
|
|
209
|
+
relation,
|
|
210
|
+
theme,
|
|
211
|
+
)}`;
|
|
212
|
+
if (metadata.length) text += `\n ${theme.fg('dim', metadata.join(' · '))}`;
|
|
213
|
+
text += `\n\n${jsonHint()}`;
|
|
214
|
+
|
|
215
|
+
return new Text(text, 0, 0);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function renderLinearIssueRelationListCall(args: ToolArgs | undefined, theme: Theme): Text {
|
|
219
|
+
return renderLinearToolCall('linear_list_issue_relations', args, theme, [
|
|
220
|
+
['first', 'first'],
|
|
221
|
+
['last', 'last'],
|
|
222
|
+
['orderBy', 'order'],
|
|
223
|
+
['includeArchived', 'archived'],
|
|
224
|
+
]);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function renderLinearCreateIssueRelationCall(
|
|
228
|
+
args: ToolArgs | undefined,
|
|
229
|
+
theme: Theme,
|
|
230
|
+
): Text {
|
|
231
|
+
return renderLinearToolCall('linear_create_issue_relation', args, theme, [
|
|
232
|
+
['issueId', 'issueId'],
|
|
233
|
+
['relatedIssueId', 'relatedIssueId'],
|
|
234
|
+
['type', 'type'],
|
|
235
|
+
]);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function renderLinearUpdateIssueRelationCall(
|
|
239
|
+
args: ToolArgs | undefined,
|
|
240
|
+
theme: Theme,
|
|
241
|
+
): Text {
|
|
242
|
+
return renderLinearToolCall('linear_update_issue_relation', args, theme, [
|
|
243
|
+
['id', 'id'],
|
|
244
|
+
['issueId', 'issueId'],
|
|
245
|
+
['relatedIssueId', 'relatedIssueId'],
|
|
246
|
+
['type', 'type'],
|
|
247
|
+
]);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function renderLinearDeleteIssueRelationCall(
|
|
251
|
+
args: ToolArgs | undefined,
|
|
252
|
+
theme: Theme,
|
|
253
|
+
): Text {
|
|
254
|
+
return renderLinearToolCall('linear_delete_issue_relation', args, theme, [['id', 'id']]);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function renderLinearIssueRelationListResult(
|
|
258
|
+
result: AgentToolResult<any>,
|
|
259
|
+
options: ToolRenderResultOptions,
|
|
260
|
+
theme: Theme,
|
|
261
|
+
context: LinearToolRenderContext,
|
|
262
|
+
): Text | LinearListResultComponent<IssueRelationLike> {
|
|
263
|
+
if (options.isPartial) return new Text(theme.fg('warning', 'Loading issue relations…'), 0, 0);
|
|
264
|
+
if (shouldShowJson(options, context)) return expandedJson(result, theme);
|
|
265
|
+
|
|
266
|
+
const issueRelations = Array.isArray(issueRelationDetails(result).issueRelations)
|
|
267
|
+
? (issueRelationDetails(result).issueRelations as IssueRelationLike[])
|
|
268
|
+
: [];
|
|
269
|
+
|
|
270
|
+
return new LinearListResultComponent(issueRelations, theme, {
|
|
271
|
+
noun: 'issue relation',
|
|
272
|
+
emptyLabel: 'No issue relations found',
|
|
273
|
+
previewLimit: ISSUE_RELATION_LIST_PREVIEW_LIMIT,
|
|
274
|
+
renderItems: renderIssueRelationTable,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function renderLinearIssueRelationResult(actionLabel: string) {
|
|
279
|
+
return (
|
|
280
|
+
result: AgentToolResult<any>,
|
|
281
|
+
options: ToolRenderResultOptions,
|
|
282
|
+
theme: Theme,
|
|
283
|
+
context: LinearToolRenderContext,
|
|
284
|
+
): Text => {
|
|
285
|
+
if (options.isPartial) return new Text(theme.fg('warning', `${actionLabel}…`), 0, 0);
|
|
286
|
+
if (shouldShowJson(options, context)) return expandedJson(result, theme);
|
|
287
|
+
|
|
288
|
+
return renderIssueRelationCard(actionLabel, issueRelationDetails(result).issueRelation, theme);
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function renderLinearDeleteIssueRelationResult(
|
|
293
|
+
result: AgentToolResult<any>,
|
|
294
|
+
options: ToolRenderResultOptions,
|
|
295
|
+
theme: Theme,
|
|
296
|
+
context: { args?: unknown },
|
|
297
|
+
): Text {
|
|
298
|
+
if (options.isPartial) return new Text(theme.fg('warning', 'Deleting issue relation…'), 0, 0);
|
|
299
|
+
if (shouldShowJson(options, context)) return expandedJson(result, theme);
|
|
300
|
+
|
|
301
|
+
const details = issueRelationDetails(result);
|
|
302
|
+
const args = argsObject(context);
|
|
303
|
+
const id = asString(args.id) ?? 'issue relation';
|
|
304
|
+
|
|
305
|
+
if (details.success !== true) {
|
|
306
|
+
return new Text(
|
|
307
|
+
`\n${theme.fg('warning', 'Deleted issue relation status unknown')}\n\n${jsonHint()}`,
|
|
308
|
+
0,
|
|
309
|
+
0,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return new Text(
|
|
314
|
+
`\n${theme.fg('success', '✓ Deleted issue relation')} ${theme.fg('accent', id)}\n\n${jsonHint()}`,
|
|
315
|
+
0,
|
|
316
|
+
0,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
@@ -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
|
+
}
|