@alasano/pi-linear 0.1.1 → 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 (35) 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/renderers/comments.ts +323 -0
  5. package/extensions/renderers/common.ts +305 -0
  6. package/extensions/renderers/documents.ts +326 -0
  7. package/extensions/renderers/initiatives.ts +344 -0
  8. package/extensions/renderers/issue-labels.ts +294 -0
  9. package/extensions/renderers/issue-relations.ts +318 -0
  10. package/extensions/renderers/issue-statuses.ts +199 -0
  11. package/extensions/renderers/issues.ts +373 -0
  12. package/extensions/renderers/milestones.ts +294 -0
  13. package/extensions/renderers/project-labels.ts +279 -0
  14. package/extensions/renderers/project-relations.ts +344 -0
  15. package/extensions/renderers/projects.ts +430 -0
  16. package/extensions/renderers/state.ts +35 -0
  17. package/extensions/renderers/teams.ts +246 -0
  18. package/extensions/renderers/users.ts +242 -0
  19. package/extensions/renderers/workspaces.ts +44 -0
  20. package/extensions/settings.ts +40 -7
  21. package/extensions/tools/comments.ts +17 -0
  22. package/extensions/tools/documents.ts +23 -0
  23. package/extensions/tools/initiatives.ts +24 -0
  24. package/extensions/tools/issue-labels.ts +17 -0
  25. package/extensions/tools/issue-relations.ts +17 -0
  26. package/extensions/tools/issue-statuses.ts +6 -0
  27. package/extensions/tools/issues.ts +29 -0
  28. package/extensions/tools/milestones.ts +18 -0
  29. package/extensions/tools/project-labels.ts +17 -0
  30. package/extensions/tools/project-relations.ts +17 -0
  31. package/extensions/tools/projects.ts +24 -0
  32. package/extensions/tools/teams.ts +10 -0
  33. package/extensions/tools/users.ts +10 -0
  34. package/extensions/tools/workspaces.ts +6 -0
  35. package/package.json +1 -1
@@ -0,0 +1,242 @@
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 UserLike = {
27
+ id?: string | null;
28
+ name?: string | null;
29
+ displayName?: string | null;
30
+ email?: string | null;
31
+ active?: boolean | null;
32
+ admin?: boolean | null;
33
+ guest?: boolean | null;
34
+ isAssignable?: boolean | null;
35
+ createdAt?: string | null;
36
+ updatedAt?: string | null;
37
+ url?: string | null;
38
+ };
39
+
40
+ type UserResultDetails = {
41
+ user?: UserLike | null;
42
+ users?: UserLike[];
43
+ };
44
+
45
+ const USER_LIST_PREVIEW_LIMIT = 20;
46
+ const NAME_LIMIT = 90;
47
+ const TABLE_NAME_MIN_WIDTH = 24;
48
+
49
+ function userDetails(result: AgentToolResult<any>): UserResultDetails {
50
+ return (result.details ?? {}) as UserResultDetails;
51
+ }
52
+
53
+ function userName(user: UserLike): string {
54
+ return truncate(
55
+ cleanOneLine(asString(user.name) ?? asString(user.displayName) ?? '(unnamed user)'),
56
+ NAME_LIMIT,
57
+ );
58
+ }
59
+
60
+ function displayNameText(user: UserLike): string | undefined {
61
+ const displayName = asString(user.displayName);
62
+ const name = asString(user.name);
63
+ if (!displayName || !name || displayName === name) return undefined;
64
+ return displayName;
65
+ }
66
+
67
+ function activeText(user: UserLike): string {
68
+ if (user.active === true) return 'active';
69
+ if (user.active === false) return 'disabled';
70
+ return '—';
71
+ }
72
+
73
+ function roleText(user: UserLike): string {
74
+ if (user.admin === true) return 'admin';
75
+ if (user.guest === true) return 'guest';
76
+ return 'member';
77
+ }
78
+
79
+ function assignableText(user: UserLike): string {
80
+ if (user.isAssignable === true) return 'yes';
81
+ if (user.isAssignable === false) return 'no';
82
+ return '—';
83
+ }
84
+
85
+ function metadataParts(user: UserLike): string[] {
86
+ const displayName = displayNameText(user);
87
+ const email = asString(user.email);
88
+
89
+ return [
90
+ displayName ? `display: ${displayName}` : undefined,
91
+ email,
92
+ activeText(user),
93
+ roleText(user),
94
+ `assignable: ${assignableText(user)}`,
95
+ ].filter((part): part is string => !!part);
96
+ }
97
+
98
+ function formatUserListLine(user: UserLike, theme: Theme, width: number): string {
99
+ const metadata = metadataParts(user);
100
+ const suffix = metadata.length ? theme.fg('dim', ` · ${metadata.join(' · ')}`) : '';
101
+
102
+ return truncateLine(` ${theme.fg('toolOutput', userName(user))}${suffix}`, width);
103
+ }
104
+
105
+ function activeStyle(theme: Theme, value: string): (text: string) => string {
106
+ if (value === 'active') return (text) => theme.fg('success', text);
107
+ if (value === 'disabled') return dimStyle(theme);
108
+ return mutedStyle(theme);
109
+ }
110
+
111
+ function roleStyle(theme: Theme, value: string): (text: string) => string {
112
+ if (value === 'admin') return (text) => theme.fg('warning', text);
113
+ if (value === 'guest') return mutedStyle(theme);
114
+ return dimStyle(theme);
115
+ }
116
+
117
+ function assignableStyle(theme: Theme, value: string): (text: string) => string {
118
+ if (value === 'yes') return (text) => theme.fg('success', text);
119
+ if (value === 'no') return dimStyle(theme);
120
+ return mutedStyle(theme);
121
+ }
122
+
123
+ const USER_TABLE_COLUMNS: TableColumn<UserLike>[] = [
124
+ {
125
+ id: 'email',
126
+ label: 'Email',
127
+ width: 30,
128
+ value: (user) => asString(user.email) ?? '—',
129
+ style: (theme) => mutedStyle(theme),
130
+ },
131
+ {
132
+ id: 'active',
133
+ label: 'Active',
134
+ width: 8,
135
+ value: activeText,
136
+ style: activeStyle,
137
+ },
138
+ {
139
+ id: 'role',
140
+ label: 'Role',
141
+ width: 7,
142
+ value: roleText,
143
+ style: roleStyle,
144
+ },
145
+ {
146
+ id: 'assignable',
147
+ label: 'Assignable',
148
+ width: 10,
149
+ value: assignableText,
150
+ style: assignableStyle,
151
+ },
152
+ ];
153
+
154
+ function renderUserTable(users: UserLike[], theme: Theme, width: number): string[] {
155
+ return renderResponsiveTable(users, theme, width, {
156
+ columns: USER_TABLE_COLUMNS,
157
+ primary: {
158
+ label: 'Name',
159
+ minWidth: TABLE_NAME_MIN_WIDTH,
160
+ value: userName,
161
+ style: (theme) => toolOutputStyle(theme),
162
+ },
163
+ dropOrder: ['assignable', 'role', 'active', 'email'],
164
+ fallback: formatUserListLine,
165
+ });
166
+ }
167
+
168
+ function formatUserTitle(user: UserLike, theme: Theme): string {
169
+ const displayName = displayNameText(user);
170
+ const name = userName(user);
171
+ const title = displayName ? `${name} (${truncate(cleanOneLine(displayName), NAME_LIMIT)})` : name;
172
+ return theme.fg('toolOutput', title);
173
+ }
174
+
175
+ function renderUserCard(user: UserLike | null | undefined, theme: Theme): Text {
176
+ if (!user) {
177
+ return new Text(`\n${theme.fg('dim', 'User not found')}\n\n${jsonHint()}`, 0, 0);
178
+ }
179
+
180
+ const email = asString(user.email);
181
+ const url = asString(user.url);
182
+ const metadata = [
183
+ email,
184
+ activeText(user),
185
+ roleText(user),
186
+ `assignable: ${assignableText(user)}`,
187
+ ].filter((part): part is string => !!part);
188
+
189
+ let text = `\n${theme.fg('success', '✓ User')} ${formatUserTitle(user, theme)}`;
190
+ if (metadata.length) text += `\n ${theme.fg('dim', metadata.join(' · '))}`;
191
+ if (url) text += `\n ${theme.fg('dim', url)}`;
192
+ text += `\n\n${jsonHint()}`;
193
+
194
+ return new Text(text, 0, 0);
195
+ }
196
+
197
+ export function renderLinearUserListCall(args: ToolArgs | undefined, theme: Theme): Text {
198
+ return renderLinearToolCall('linear_list_users', args, theme, [
199
+ ['first', 'first'],
200
+ ['orderBy', 'order'],
201
+ ['filter', 'filter'],
202
+ ['includeArchived', 'includeArchived'],
203
+ ['includeDisabled', 'includeDisabled'],
204
+ ]);
205
+ }
206
+
207
+ export function renderLinearGetUserCall(args: ToolArgs | undefined, theme: Theme): Text {
208
+ return renderLinearToolCall('linear_get_user', args, theme, [['userId', 'userId']]);
209
+ }
210
+
211
+ export function renderLinearUserListResult(
212
+ result: AgentToolResult<any>,
213
+ options: ToolRenderResultOptions,
214
+ theme: Theme,
215
+ context: LinearToolRenderContext,
216
+ ): Text | LinearListResultComponent<UserLike> {
217
+ if (options.isPartial) return new Text(theme.fg('warning', 'Loading users…'), 0, 0);
218
+ if (shouldShowJson(options, context)) return expandedJson(result, theme);
219
+
220
+ const users = Array.isArray(userDetails(result).users)
221
+ ? (userDetails(result).users as UserLike[])
222
+ : [];
223
+
224
+ return new LinearListResultComponent(users, theme, {
225
+ noun: 'user',
226
+ emptyLabel: 'No users found',
227
+ previewLimit: USER_LIST_PREVIEW_LIMIT,
228
+ renderItems: renderUserTable,
229
+ });
230
+ }
231
+
232
+ export function renderLinearUserResult(
233
+ result: AgentToolResult<any>,
234
+ options: ToolRenderResultOptions,
235
+ theme: Theme,
236
+ context: LinearToolRenderContext,
237
+ ): Text {
238
+ if (options.isPartial) return new Text(theme.fg('warning', 'Loading user…'), 0, 0);
239
+ if (shouldShowJson(options, context)) return expandedJson(result, theme);
240
+
241
+ return renderUserCard(userDetails(result).user, theme);
242
+ }
@@ -0,0 +1,44 @@
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
+ expandedJson,
10
+ shouldShowJson,
11
+ jsonHint,
12
+ renderLinearToolCall,
13
+ type LinearToolRenderContext,
14
+ type ToolArgs,
15
+ } from './common';
16
+
17
+ type WorkspaceSwitchResultDetails = {
18
+ active?: string | null;
19
+ };
20
+
21
+ function workspaceSwitchDetails(result: AgentToolResult<any>): WorkspaceSwitchResultDetails {
22
+ return (result.details ?? {}) as WorkspaceSwitchResultDetails;
23
+ }
24
+
25
+ export function renderLinearSwitchWorkspaceCall(args: ToolArgs | undefined, theme: Theme): Text {
26
+ return renderLinearToolCall('linear_switch_workspace', args, theme, [['name', 'name']]);
27
+ }
28
+
29
+ export function renderLinearSwitchWorkspaceResult(
30
+ result: AgentToolResult<any>,
31
+ options: ToolRenderResultOptions,
32
+ theme: Theme,
33
+ context: LinearToolRenderContext,
34
+ ): Text {
35
+ if (options.isPartial) return new Text(theme.fg('warning', 'Switching workspace…'), 0, 0);
36
+ if (shouldShowJson(options, context)) return expandedJson(result, theme);
37
+
38
+ const active = asString(workspaceSwitchDetails(result).active) ?? 'unknown';
39
+ return new Text(
40
+ `\n${theme.fg('success', `✓ Active Linear workspace: ${active}`)}\n\n${jsonHint()}`,
41
+ 0,
42
+ 0,
43
+ );
44
+ }
@@ -3,6 +3,7 @@ import { dirname, join } from 'node:path';
3
3
  import type { ExtensionAPI, ExtensionContext } from '@mariozechner/pi-coding-agent';
4
4
  import { getAgentDir, getSettingsListTheme } from '@mariozechner/pi-coding-agent';
5
5
  import { type SettingItem, SettingsList } from '@mariozechner/pi-tui';
6
+ import { invalidateLinearResultRenderers, setDefaultJsonView } from './renderers/state';
6
7
 
7
8
  const SETTINGS_PATH = join(getAgentDir(), 'state', 'extensions', 'linear', 'tool-settings.json');
8
9
  const OVERLAY_MAX_INNER = 60;
@@ -166,28 +167,37 @@ const ALL_LINEAR_TOOLS = TOOL_CATEGORIES.flatMap((c) => c.tools);
166
167
 
167
168
  type ToolSettings = {
168
169
  disabledTools: string[];
170
+ defaultJsonView: boolean;
169
171
  };
170
172
 
171
173
  function createDefaultSettings(): ToolSettings {
172
- return { disabledTools: [] };
174
+ return { disabledTools: [], defaultJsonView: false };
173
175
  }
174
176
 
175
177
  async function loadSettings(): Promise<ToolSettings> {
176
178
  try {
177
179
  const raw = JSON.parse(await fs.readFile(SETTINGS_PATH, 'utf8'));
178
180
  if (!raw || typeof raw !== 'object' || !Array.isArray(raw.disabledTools)) {
179
- return createDefaultSettings();
181
+ const settings = createDefaultSettings();
182
+ setDefaultJsonView(settings.defaultJsonView);
183
+ return settings;
180
184
  }
181
- return {
185
+ const settings = {
182
186
  disabledTools: raw.disabledTools.filter((t: unknown) => typeof t === 'string'),
187
+ defaultJsonView: typeof raw.defaultJsonView === 'boolean' ? raw.defaultJsonView : false,
183
188
  };
189
+ setDefaultJsonView(settings.defaultJsonView);
190
+ return settings;
184
191
  } catch {
185
- return createDefaultSettings();
192
+ const settings = createDefaultSettings();
193
+ setDefaultJsonView(settings.defaultJsonView);
194
+ return settings;
186
195
  }
187
196
  }
188
197
 
189
198
  async function saveSettings(settings: ToolSettings): Promise<boolean> {
190
199
  try {
200
+ setDefaultJsonView(settings.defaultJsonView);
191
201
  await fs.mkdir(dirname(SETTINGS_PATH), { recursive: true });
192
202
  await fs.writeFile(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}\n`, 'utf8');
193
203
  return true;
@@ -256,8 +266,21 @@ function frameBody(title: string, bodyLines: string[], inner: number): string[]
256
266
  return [top, ...framedBody, bottom];
257
267
  }
258
268
 
269
+ function defaultOutputViewValue(settings: ToolSettings): string {
270
+ return settings.defaultJsonView ? 'Full JSON' : 'Human readable';
271
+ }
272
+
259
273
  function buildItems(settings: ToolSettings): SettingItem[] {
260
- const items: SettingItem[] = [];
274
+ const items: SettingItem[] = [
275
+ {
276
+ id: 'defaultJsonView',
277
+ label: 'Default output view',
278
+ description:
279
+ 'Controls Linear tool result display. Ctrl+O toggles the other view per tool call.',
280
+ currentValue: defaultOutputViewValue(settings),
281
+ values: ['Human readable', 'Full JSON'],
282
+ },
283
+ ];
261
284
  for (const category of TOOL_CATEGORIES) {
262
285
  items.push({
263
286
  id: `category:${category.id}`,
@@ -308,7 +331,7 @@ async function showToolSettingsOverlay(
308
331
  () => {},
309
332
  );
310
333
  const probeLines = probeList.render(Math.max(8, OVERLAY_MAX_INNER - 2));
311
- const overlayBodyLines = ['Toggle Linear tools by category or individually', '', ...probeLines];
334
+ const overlayBodyLines = ['Configure Linear output and enabled tools', '', ...probeLines];
312
335
  const overlayWidth = computeOverlayInner(overlayBodyLines, OVERLAY_MAX_INNER + 2) + 2;
313
336
 
314
337
  await ctx.ui.custom(
@@ -318,6 +341,16 @@ async function showToolSettingsOverlay(
318
341
  maxVisibleItems,
319
342
  settingsTheme,
320
343
  async (id, newValue) => {
344
+ if (id === 'defaultJsonView') {
345
+ const previousDefaultJsonView = settings.defaultJsonView;
346
+ settings.defaultJsonView = newValue === 'Full JSON';
347
+ await saveSettings(settings);
348
+ if (settings.defaultJsonView !== previousDefaultJsonView) {
349
+ invalidateLinearResultRenderers();
350
+ }
351
+ return;
352
+ }
353
+
321
354
  const nextEnabled = newValue === '[x]';
322
355
 
323
356
  if (id.startsWith('category:')) {
@@ -364,7 +397,7 @@ async function showToolSettingsOverlay(
364
397
  const provisionalInner = Math.max(24, Math.min(safeWidth - 2, OVERLAY_MAX_INNER));
365
398
  const listLines = settingsList.render(Math.max(8, provisionalInner - 2));
366
399
  const bodyLines = [
367
- theme.fg('muted', 'Toggle Linear tools by category or individually'),
400
+ theme.fg('muted', 'Configure Linear output and enabled tools'),
368
401
  '',
369
402
  ...listLines,
370
403
  ];
@@ -5,6 +5,15 @@ import { PaginationParams, FilterParam, RawInputParam } from '../params';
5
5
  import { COMMENT_SELECTION } from '../selections';
6
6
  import type { JsonObject } from '../types';
7
7
  import { compactObject, asObject, asString, GenericObjectSchema } from '../util';
8
+ import {
9
+ renderLinearCommentListCall,
10
+ renderLinearCommentListResult,
11
+ renderLinearCommentResult,
12
+ renderLinearCreateCommentCall,
13
+ renderLinearDeleteCommentCall,
14
+ renderLinearDeleteCommentResult,
15
+ renderLinearUpdateCommentCall,
16
+ } from '../renderers/comments';
8
17
 
9
18
  export function commentTools() {
10
19
  return [
@@ -16,6 +25,7 @@ export function commentTools() {
16
25
  ...PaginationParams,
17
26
  ...FilterParam,
18
27
  }),
28
+ renderCall: renderLinearCommentListCall,
19
29
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
20
30
  return withLinearAuth(ctx, signal, async (apiKey) => {
21
31
  const variables = compactObject({
@@ -66,6 +76,7 @@ export function commentTools() {
66
76
  };
67
77
  });
68
78
  },
79
+ renderResult: renderLinearCommentListResult,
69
80
  }),
70
81
  defineTool({
71
82
  name: 'linear_create_comment',
@@ -90,6 +101,7 @@ export function commentTools() {
90
101
  subscriberIds: Type.Optional(Type.Array(Type.String())),
91
102
  ...RawInputParam,
92
103
  }),
104
+ renderCall: renderLinearCreateCommentCall,
93
105
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
94
106
  return withLinearAuth(ctx, signal, async (apiKey) => {
95
107
  const rawInput = asObject(params.input) || {};
@@ -146,6 +158,7 @@ export function commentTools() {
146
158
  };
147
159
  });
148
160
  },
161
+ renderResult: renderLinearCommentResult('Created comment'),
149
162
  }),
150
163
  defineTool({
151
164
  name: 'linear_update_comment',
@@ -157,6 +170,7 @@ export function commentTools() {
157
170
  quotedText: Type.Optional(Type.String()),
158
171
  ...RawInputParam,
159
172
  }),
173
+ renderCall: renderLinearUpdateCommentCall,
160
174
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
161
175
  return withLinearAuth(ctx, signal, async (apiKey) => {
162
176
  const rawInput = asObject(params.input) || {};
@@ -199,6 +213,7 @@ export function commentTools() {
199
213
  };
200
214
  });
201
215
  },
216
+ renderResult: renderLinearCommentResult('Updated comment'),
202
217
  }),
203
218
  defineTool({
204
219
  name: 'linear_delete_comment',
@@ -207,6 +222,7 @@ export function commentTools() {
207
222
  parameters: Type.Object({
208
223
  id: Type.String(),
209
224
  }),
225
+ renderCall: renderLinearDeleteCommentCall,
210
226
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
211
227
  return withLinearAuth(ctx, signal, async (apiKey) => {
212
228
  const data = await linearGraphQL<{
@@ -232,6 +248,7 @@ export function commentTools() {
232
248
  };
233
249
  });
234
250
  },
251
+ renderResult: renderLinearDeleteCommentResult,
235
252
  }),
236
253
  ];
237
254
  }
@@ -5,6 +5,17 @@ import { PaginationParams, FilterParam, RawInputParam, TeamConvenienceParams } f
5
5
  import { DOCUMENT_SELECTION } from '../selections';
6
6
  import type { JsonObject } from '../types';
7
7
  import { compactObject, asObject, asString } from '../util';
8
+ import {
9
+ renderLinearCreateDocumentCall,
10
+ renderLinearDeleteDocumentCall,
11
+ renderLinearDocumentListCall,
12
+ renderLinearDocumentListResult,
13
+ renderLinearDocumentResult,
14
+ renderLinearDocumentSuccessResult,
15
+ renderLinearGetDocumentCall,
16
+ renderLinearUnarchiveDocumentCall,
17
+ renderLinearUpdateDocumentCall,
18
+ } from '../renderers/documents';
8
19
 
9
20
  export function documentTools() {
10
21
  return [
@@ -16,6 +27,7 @@ export function documentTools() {
16
27
  ...PaginationParams,
17
28
  ...FilterParam,
18
29
  }),
30
+ renderCall: renderLinearDocumentListCall,
19
31
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
20
32
  return withLinearAuth(ctx, signal, async (apiKey) => {
21
33
  const variables = compactObject({
@@ -66,6 +78,7 @@ export function documentTools() {
66
78
  };
67
79
  });
68
80
  },
81
+ renderResult: renderLinearDocumentListResult,
69
82
  }),
70
83
  defineTool({
71
84
  name: 'linear_get_document',
@@ -74,6 +87,7 @@ export function documentTools() {
74
87
  parameters: Type.Object({
75
88
  documentId: Type.String(),
76
89
  }),
90
+ renderCall: renderLinearGetDocumentCall,
77
91
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
78
92
  return withLinearAuth(ctx, signal, async (apiKey) => {
79
93
  const data = await linearGraphQL<{ document: JsonObject | null }>(
@@ -96,6 +110,7 @@ export function documentTools() {
96
110
  };
97
111
  });
98
112
  },
113
+ renderResult: renderLinearDocumentResult('Document'),
99
114
  }),
100
115
  defineTool({
101
116
  name: 'linear_create_document',
@@ -120,6 +135,7 @@ export function documentTools() {
120
135
  title: Type.Optional(Type.String()),
121
136
  ...RawInputParam,
122
137
  }),
138
+ renderCall: renderLinearCreateDocumentCall,
123
139
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
124
140
  return withLinearAuth(ctx, signal, async (apiKey) => {
125
141
  const rawInput = asObject(params.input) || {};
@@ -188,6 +204,7 @@ export function documentTools() {
188
204
  };
189
205
  });
190
206
  },
207
+ renderResult: renderLinearDocumentResult('Created document'),
191
208
  }),
192
209
  defineTool({
193
210
  name: 'linear_update_document',
@@ -214,6 +231,7 @@ export function documentTools() {
214
231
  trashed: Type.Optional(Type.Boolean()),
215
232
  ...RawInputParam,
216
233
  }),
234
+ renderCall: renderLinearUpdateDocumentCall,
217
235
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
218
236
  return withLinearAuth(ctx, signal, async (apiKey) => {
219
237
  const rawInput = asObject(params.input) || {};
@@ -286,6 +304,7 @@ export function documentTools() {
286
304
  };
287
305
  });
288
306
  },
307
+ renderResult: renderLinearDocumentResult('Updated document'),
289
308
  }),
290
309
  defineTool({
291
310
  name: 'linear_delete_document',
@@ -294,6 +313,7 @@ export function documentTools() {
294
313
  parameters: Type.Object({
295
314
  documentId: Type.String(),
296
315
  }),
316
+ renderCall: renderLinearDeleteDocumentCall,
297
317
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
298
318
  return withLinearAuth(ctx, signal, async (apiKey) => {
299
319
  const data = await linearGraphQL<{
@@ -319,6 +339,7 @@ export function documentTools() {
319
339
  };
320
340
  });
321
341
  },
342
+ renderResult: renderLinearDocumentSuccessResult('Deleted'),
322
343
  }),
323
344
  defineTool({
324
345
  name: 'linear_unarchive_document',
@@ -327,6 +348,7 @@ export function documentTools() {
327
348
  parameters: Type.Object({
328
349
  documentId: Type.String(),
329
350
  }),
351
+ renderCall: renderLinearUnarchiveDocumentCall,
330
352
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
331
353
  return withLinearAuth(ctx, signal, async (apiKey) => {
332
354
  const data = await linearGraphQL<{
@@ -352,6 +374,7 @@ export function documentTools() {
352
374
  };
353
375
  });
354
376
  },
377
+ renderResult: renderLinearDocumentSuccessResult('Unarchived'),
355
378
  }),
356
379
  ];
357
380
  }
@@ -5,6 +5,18 @@ import { PaginationParams, FilterParam, SortParam, RawInputParam } from '../para
5
5
  import { INITIATIVE_SELECTION } from '../selections';
6
6
  import type { JsonObject } from '../types';
7
7
  import { compactObject, asObject, asObjectArray, asString } from '../util';
8
+ import {
9
+ renderLinearArchiveInitiativeCall,
10
+ renderLinearDeleteInitiativeCall,
11
+ renderLinearGetInitiativeCall,
12
+ renderLinearInitiativeListCall,
13
+ renderLinearInitiativeListResult,
14
+ renderLinearInitiativeResult,
15
+ renderLinearInitiativeSuccessResult,
16
+ renderLinearSaveInitiativeCall,
17
+ renderLinearSaveInitiativeResult,
18
+ renderLinearUnarchiveInitiativeCall,
19
+ } from '../renderers/initiatives';
8
20
 
9
21
  export function initiativeTools() {
10
22
  return [
@@ -17,6 +29,7 @@ export function initiativeTools() {
17
29
  ...FilterParam,
18
30
  ...SortParam,
19
31
  }),
32
+ renderCall: renderLinearInitiativeListCall,
20
33
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
21
34
  return withLinearAuth(ctx, signal, async (apiKey) => {
22
35
  const variables = compactObject({
@@ -70,6 +83,7 @@ export function initiativeTools() {
70
83
  };
71
84
  });
72
85
  },
86
+ renderResult: renderLinearInitiativeListResult,
73
87
  }),
74
88
  defineTool({
75
89
  name: 'linear_get_initiative',
@@ -78,6 +92,7 @@ export function initiativeTools() {
78
92
  parameters: Type.Object({
79
93
  initiativeId: Type.String(),
80
94
  }),
95
+ renderCall: renderLinearGetInitiativeCall,
81
96
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
82
97
  return withLinearAuth(ctx, signal, async (apiKey) => {
83
98
  const data = await linearGraphQL<{ initiative: JsonObject | null }>(
@@ -100,6 +115,7 @@ export function initiativeTools() {
100
115
  };
101
116
  });
102
117
  },
118
+ renderResult: renderLinearInitiativeResult('Initiative'),
103
119
  }),
104
120
  defineTool({
105
121
  name: 'linear_save_initiative',
@@ -127,6 +143,7 @@ export function initiativeTools() {
127
143
  updateRemindersHour: Type.Optional(Type.Integer()),
128
144
  ...RawInputParam,
129
145
  }),
146
+ renderCall: renderLinearSaveInitiativeCall,
130
147
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
131
148
  return withLinearAuth(ctx, signal, async (apiKey) => {
132
149
  const rawInput = asObject(params.input) || {};
@@ -224,6 +241,7 @@ export function initiativeTools() {
224
241
  };
225
242
  });
226
243
  },
244
+ renderResult: renderLinearSaveInitiativeResult,
227
245
  }),
228
246
  defineTool({
229
247
  name: 'linear_delete_initiative',
@@ -232,6 +250,7 @@ export function initiativeTools() {
232
250
  parameters: Type.Object({
233
251
  initiativeId: Type.String(),
234
252
  }),
253
+ renderCall: renderLinearDeleteInitiativeCall,
235
254
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
236
255
  return withLinearAuth(ctx, signal, async (apiKey) => {
237
256
  const data = await linearGraphQL<{
@@ -257,6 +276,7 @@ export function initiativeTools() {
257
276
  };
258
277
  });
259
278
  },
279
+ renderResult: renderLinearInitiativeSuccessResult('Deleted'),
260
280
  }),
261
281
  defineTool({
262
282
  name: 'linear_archive_initiative',
@@ -265,6 +285,7 @@ export function initiativeTools() {
265
285
  parameters: Type.Object({
266
286
  initiativeId: Type.String(),
267
287
  }),
288
+ renderCall: renderLinearArchiveInitiativeCall,
268
289
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
269
290
  return withLinearAuth(ctx, signal, async (apiKey) => {
270
291
  const data = await linearGraphQL<{
@@ -290,6 +311,7 @@ export function initiativeTools() {
290
311
  };
291
312
  });
292
313
  },
314
+ renderResult: renderLinearInitiativeSuccessResult('Archived'),
293
315
  }),
294
316
  defineTool({
295
317
  name: 'linear_unarchive_initiative',
@@ -298,6 +320,7 @@ export function initiativeTools() {
298
320
  parameters: Type.Object({
299
321
  initiativeId: Type.String(),
300
322
  }),
323
+ renderCall: renderLinearUnarchiveInitiativeCall,
301
324
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
302
325
  return withLinearAuth(ctx, signal, async (apiKey) => {
303
326
  const data = await linearGraphQL<{
@@ -323,6 +346,7 @@ export function initiativeTools() {
323
346
  };
324
347
  });
325
348
  },
349
+ renderResult: renderLinearInitiativeSuccessResult('Unarchived'),
326
350
  }),
327
351
  ];
328
352
  }