@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.
- package/README.md +14 -2
- package/assets/linear_list_issues.png +0 -0
- package/assets/screenshot.png +0 -0
- package/extensions/index.ts +1 -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 +430 -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/settings.ts +53 -23
- package/extensions/tools/comments.ts +17 -0
- package/extensions/tools/documents.ts +23 -0
- package/extensions/tools/initiatives.ts +24 -0
- package/extensions/tools/issue-labels.ts +17 -0
- package/extensions/tools/issue-relations.ts +29 -5
- package/extensions/tools/issue-statuses.ts +6 -0
- package/extensions/tools/issues.ts +29 -0
- package/extensions/tools/milestones.ts +18 -0
- package/extensions/tools/project-labels.ts +17 -0
- package/extensions/tools/project-relations.ts +17 -0
- package/extensions/tools/projects.ts +25 -1
- package/extensions/tools/teams.ts +11 -3
- package/extensions/tools/users.ts +10 -0
- package/extensions/tools/workspaces.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,344 @@
|
|
|
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
|
+
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 ProjectRelationProject = {
|
|
27
|
+
id?: string | null;
|
|
28
|
+
name?: string | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type ProjectRelationMilestone = {
|
|
32
|
+
id?: string | null;
|
|
33
|
+
name?: string | null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type ProjectRelationLike = {
|
|
37
|
+
id?: string | null;
|
|
38
|
+
createdAt?: string | null;
|
|
39
|
+
updatedAt?: string | null;
|
|
40
|
+
type?: string | null;
|
|
41
|
+
anchorType?: string | null;
|
|
42
|
+
relatedAnchorType?: string | null;
|
|
43
|
+
project?: ProjectRelationProject | null;
|
|
44
|
+
projectMilestone?: ProjectRelationMilestone | null;
|
|
45
|
+
relatedProject?: ProjectRelationProject | null;
|
|
46
|
+
relatedProjectMilestone?: ProjectRelationMilestone | null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type ProjectRelationResultDetails = {
|
|
50
|
+
projectRelation?: ProjectRelationLike | null;
|
|
51
|
+
projectRelations?: ProjectRelationLike[];
|
|
52
|
+
success?: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const PROJECT_RELATION_LIST_PREVIEW_LIMIT = 20;
|
|
56
|
+
const NAME_LIMIT = 70;
|
|
57
|
+
const TABLE_RELATION_MIN_WIDTH = 28;
|
|
58
|
+
|
|
59
|
+
const PROJECT_RELATION_MUTATION_FIELDS: Array<[key: string, label: string]> = [
|
|
60
|
+
['id', 'id'],
|
|
61
|
+
['projectId', 'projectId'],
|
|
62
|
+
['relatedProjectId', 'relatedProjectId'],
|
|
63
|
+
['type', 'type'],
|
|
64
|
+
['anchorType', 'anchor'],
|
|
65
|
+
['relatedAnchorType', 'relatedAnchor'],
|
|
66
|
+
['projectMilestoneId', 'projectMilestone'],
|
|
67
|
+
['relatedProjectMilestoneId', 'relatedProjectMilestone'],
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
function projectRelationDetails(result: AgentToolResult<any>): ProjectRelationResultDetails {
|
|
71
|
+
return (result.details ?? {}) as ProjectRelationResultDetails;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function argsObject(context: { args?: unknown }): ToolArgs {
|
|
75
|
+
return context.args && typeof context.args === 'object' && !Array.isArray(context.args)
|
|
76
|
+
? (context.args as ToolArgs)
|
|
77
|
+
: {};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function projectName(project: ProjectRelationProject | null | undefined, fallback: string): string {
|
|
81
|
+
return truncate(
|
|
82
|
+
cleanOneLine(asString(project?.name) ?? asString(project?.id) ?? fallback),
|
|
83
|
+
NAME_LIMIT,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function hasProject(project: ProjectRelationProject | null | undefined): boolean {
|
|
88
|
+
return !!(asString(project?.name) ?? asString(project?.id));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function relationType(relation: ProjectRelationLike): string {
|
|
92
|
+
return asString(relation.type) ?? 'relation';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function milestoneName(milestone: ProjectRelationMilestone | null | undefined): string | undefined {
|
|
96
|
+
const name = asString(milestone?.name) ?? asString(milestone?.id);
|
|
97
|
+
return name ? truncate(cleanOneLine(name), NAME_LIMIT) : undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function anchorText(
|
|
101
|
+
anchorType: string | null | undefined,
|
|
102
|
+
milestone: ProjectRelationMilestone | null | undefined,
|
|
103
|
+
): string | undefined {
|
|
104
|
+
const anchor = asString(anchorType);
|
|
105
|
+
const milestoneLabel = milestoneName(milestone);
|
|
106
|
+
|
|
107
|
+
if (anchor && milestoneLabel) return `${anchor}: ${milestoneLabel}`;
|
|
108
|
+
return milestoneLabel ?? anchor;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function projectAnchorText(relation: ProjectRelationLike): string | undefined {
|
|
112
|
+
return anchorText(relation.anchorType, relation.projectMilestone);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function relatedProjectAnchorText(relation: ProjectRelationLike): string | undefined {
|
|
116
|
+
return anchorText(relation.relatedAnchorType, relation.relatedProjectMilestone);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function relationSummary(relation: ProjectRelationLike): string {
|
|
120
|
+
const id = asString(relation.id);
|
|
121
|
+
const hasSource = hasProject(relation.project);
|
|
122
|
+
const hasRelated = hasProject(relation.relatedProject);
|
|
123
|
+
|
|
124
|
+
if (!hasSource && !hasRelated && id) return truncate(id, NAME_LIMIT);
|
|
125
|
+
|
|
126
|
+
const project = projectName(relation.project, 'project');
|
|
127
|
+
const relatedProject = projectName(relation.relatedProject, 'related project');
|
|
128
|
+
return truncate(
|
|
129
|
+
cleanOneLine(`${project} ${relationType(relation)} ${relatedProject}`),
|
|
130
|
+
NAME_LIMIT * 2,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function metadataParts(
|
|
135
|
+
relation: ProjectRelationLike,
|
|
136
|
+
options: { includeId?: boolean } = {},
|
|
137
|
+
): string[] {
|
|
138
|
+
const anchor = projectAnchorText(relation);
|
|
139
|
+
const relatedAnchor = relatedProjectAnchorText(relation);
|
|
140
|
+
const id = options.includeId ? asString(relation.id) : undefined;
|
|
141
|
+
|
|
142
|
+
return [
|
|
143
|
+
anchor ? `anchor: ${anchor}` : undefined,
|
|
144
|
+
relatedAnchor ? `related anchor: ${relatedAnchor}` : undefined,
|
|
145
|
+
id ? `id: ${truncate(id, 8)}` : undefined,
|
|
146
|
+
].filter((part): part is string => !!part);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function formatProjectRelationListLine(
|
|
150
|
+
relation: ProjectRelationLike,
|
|
151
|
+
theme: Theme,
|
|
152
|
+
width: number,
|
|
153
|
+
): string {
|
|
154
|
+
const summary = relationSummary(relation);
|
|
155
|
+
const metadata = metadataParts(relation, { includeId: true });
|
|
156
|
+
const suffix = metadata.length ? theme.fg('dim', ` · ${metadata.join(' · ')}`) : '';
|
|
157
|
+
|
|
158
|
+
return truncateLine(` ${theme.fg('toolOutput', summary)}${suffix}`, width);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const PROJECT_RELATION_TABLE_COLUMNS: TableColumn<ProjectRelationLike>[] = [
|
|
162
|
+
{
|
|
163
|
+
id: 'type',
|
|
164
|
+
label: 'Type',
|
|
165
|
+
width: 12,
|
|
166
|
+
value: (relation) => relationType(relation),
|
|
167
|
+
style: (theme) => mutedStyle(theme),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'project',
|
|
171
|
+
label: 'Project',
|
|
172
|
+
width: 22,
|
|
173
|
+
value: (relation) => projectName(relation.project, '—'),
|
|
174
|
+
style: (theme) => toolOutputStyle(theme),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 'relatedProject',
|
|
178
|
+
label: 'Related project',
|
|
179
|
+
width: 22,
|
|
180
|
+
value: (relation) => projectName(relation.relatedProject, '—'),
|
|
181
|
+
style: (theme) => toolOutputStyle(theme),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: 'anchor',
|
|
185
|
+
label: 'Anchor',
|
|
186
|
+
width: 20,
|
|
187
|
+
value: (relation) => projectAnchorText(relation) ?? '—',
|
|
188
|
+
style: (theme) => dimStyle(theme),
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: 'relatedAnchor',
|
|
192
|
+
label: 'Related anchor',
|
|
193
|
+
width: 20,
|
|
194
|
+
value: (relation) => relatedProjectAnchorText(relation) ?? '—',
|
|
195
|
+
style: (theme) => dimStyle(theme),
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
function renderProjectRelationTable(
|
|
200
|
+
projectRelations: ProjectRelationLike[],
|
|
201
|
+
theme: Theme,
|
|
202
|
+
width: number,
|
|
203
|
+
): string[] {
|
|
204
|
+
return renderResponsiveTable(projectRelations, theme, width, {
|
|
205
|
+
columns: PROJECT_RELATION_TABLE_COLUMNS,
|
|
206
|
+
primary: {
|
|
207
|
+
label: 'Relation',
|
|
208
|
+
minWidth: TABLE_RELATION_MIN_WIDTH,
|
|
209
|
+
value: relationSummary,
|
|
210
|
+
style: (theme) => toolOutputStyle(theme),
|
|
211
|
+
},
|
|
212
|
+
dropOrder: ['relatedAnchor', 'anchor', 'relatedProject', 'project', 'type'],
|
|
213
|
+
fallback: formatProjectRelationListLine,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function formatProjectRelationTitle(relation: ProjectRelationLike, theme: Theme): string {
|
|
218
|
+
const id = asString(relation.id);
|
|
219
|
+
const title = theme.fg('toolOutput', relationSummary(relation));
|
|
220
|
+
return id ? `${title} ${theme.fg('dim', `(${truncate(id, 8)})`)}` : title;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function renderProjectRelationCard(
|
|
224
|
+
actionLabel: string,
|
|
225
|
+
relation: ProjectRelationLike | null | undefined,
|
|
226
|
+
theme: Theme,
|
|
227
|
+
): Text {
|
|
228
|
+
if (!relation) {
|
|
229
|
+
return new Text(`\n${theme.fg('dim', 'Project relation not found')}\n\n${jsonHint()}`, 0, 0);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const metadata = metadataParts(relation);
|
|
233
|
+
|
|
234
|
+
let text = `\n${theme.fg('success', `✓ ${actionLabel}`)} ${formatProjectRelationTitle(relation, theme)}`;
|
|
235
|
+
if (metadata.length) text += `\n ${theme.fg('dim', metadata.join(' · '))}`;
|
|
236
|
+
text += `\n\n${jsonHint()}`;
|
|
237
|
+
|
|
238
|
+
return new Text(text, 0, 0);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function renderLinearProjectRelationListCall(
|
|
242
|
+
args: ToolArgs | undefined,
|
|
243
|
+
theme: Theme,
|
|
244
|
+
): Text {
|
|
245
|
+
return renderLinearToolCall('linear_list_project_relations', args, theme, [
|
|
246
|
+
['first', 'first'],
|
|
247
|
+
['orderBy', 'order'],
|
|
248
|
+
]);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function renderLinearCreateProjectRelationCall(
|
|
252
|
+
args: ToolArgs | undefined,
|
|
253
|
+
theme: Theme,
|
|
254
|
+
): Text {
|
|
255
|
+
return renderLinearToolCall(
|
|
256
|
+
'linear_create_project_relation',
|
|
257
|
+
args,
|
|
258
|
+
theme,
|
|
259
|
+
PROJECT_RELATION_MUTATION_FIELDS,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function renderLinearUpdateProjectRelationCall(
|
|
264
|
+
args: ToolArgs | undefined,
|
|
265
|
+
theme: Theme,
|
|
266
|
+
): Text {
|
|
267
|
+
return renderLinearToolCall(
|
|
268
|
+
'linear_update_project_relation',
|
|
269
|
+
args,
|
|
270
|
+
theme,
|
|
271
|
+
PROJECT_RELATION_MUTATION_FIELDS,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function renderLinearDeleteProjectRelationCall(
|
|
276
|
+
args: ToolArgs | undefined,
|
|
277
|
+
theme: Theme,
|
|
278
|
+
): Text {
|
|
279
|
+
return renderLinearToolCall('linear_delete_project_relation', args, theme, [['id', 'id']]);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function renderLinearProjectRelationListResult(
|
|
283
|
+
result: AgentToolResult<any>,
|
|
284
|
+
options: ToolRenderResultOptions,
|
|
285
|
+
theme: Theme,
|
|
286
|
+
context: LinearToolRenderContext,
|
|
287
|
+
): Text | LinearListResultComponent<ProjectRelationLike> {
|
|
288
|
+
if (options.isPartial) return new Text(theme.fg('warning', 'Loading project relations…'), 0, 0);
|
|
289
|
+
if (shouldShowJson(options, context)) return expandedJson(result, theme);
|
|
290
|
+
|
|
291
|
+
const projectRelations = Array.isArray(projectRelationDetails(result).projectRelations)
|
|
292
|
+
? (projectRelationDetails(result).projectRelations as ProjectRelationLike[])
|
|
293
|
+
: [];
|
|
294
|
+
|
|
295
|
+
return new LinearListResultComponent(projectRelations, theme, {
|
|
296
|
+
noun: 'project relation',
|
|
297
|
+
pluralNoun: 'project relations',
|
|
298
|
+
emptyLabel: 'No project relations found',
|
|
299
|
+
previewLimit: PROJECT_RELATION_LIST_PREVIEW_LIMIT,
|
|
300
|
+
renderItems: renderProjectRelationTable,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function renderLinearProjectRelationResult(actionLabel: string) {
|
|
305
|
+
return (
|
|
306
|
+
result: AgentToolResult<any>,
|
|
307
|
+
options: ToolRenderResultOptions,
|
|
308
|
+
theme: Theme,
|
|
309
|
+
context: LinearToolRenderContext,
|
|
310
|
+
): Text => {
|
|
311
|
+
if (options.isPartial) return new Text(theme.fg('warning', `${actionLabel}…`), 0, 0);
|
|
312
|
+
if (shouldShowJson(options, context)) return expandedJson(result, theme);
|
|
313
|
+
|
|
314
|
+
return renderProjectRelationCard(
|
|
315
|
+
actionLabel,
|
|
316
|
+
projectRelationDetails(result).projectRelation,
|
|
317
|
+
theme,
|
|
318
|
+
);
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function renderLinearDeleteProjectRelationResult(
|
|
323
|
+
result: AgentToolResult<any>,
|
|
324
|
+
options: ToolRenderResultOptions,
|
|
325
|
+
theme: Theme,
|
|
326
|
+
context: { args?: unknown },
|
|
327
|
+
): Text {
|
|
328
|
+
if (options.isPartial) return new Text(theme.fg('warning', 'Deleting project relation…'), 0, 0);
|
|
329
|
+
if (shouldShowJson(options, context)) return expandedJson(result, theme);
|
|
330
|
+
|
|
331
|
+
const details = projectRelationDetails(result);
|
|
332
|
+
const args = argsObject(context);
|
|
333
|
+
const id = asString(args.id) ?? 'project relation';
|
|
334
|
+
|
|
335
|
+
if (details.success !== true) {
|
|
336
|
+
return new Text(`\n${theme.fg('warning', 'Delete status unknown')}\n\n${jsonHint()}`, 0, 0);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return new Text(
|
|
340
|
+
`\n${theme.fg('success', '✓ Deleted project relation')} ${theme.fg('accent', id)}\n\n${jsonHint()}`,
|
|
341
|
+
0,
|
|
342
|
+
0,
|
|
343
|
+
);
|
|
344
|
+
}
|