@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,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
+ }
@@ -1,8 +1,9 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
1
+ import { promises as fs } from 'node:fs';
2
2
  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,33 +167,39 @@ 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
- function loadSettings(): ToolSettings {
176
- if (!existsSync(SETTINGS_PATH)) {
177
- return createDefaultSettings();
178
- }
177
+ async function loadSettings(): Promise<ToolSettings> {
179
178
  try {
180
- const raw = JSON.parse(readFileSync(SETTINGS_PATH, 'utf8'));
179
+ const raw = JSON.parse(await fs.readFile(SETTINGS_PATH, 'utf8'));
181
180
  if (!raw || typeof raw !== 'object' || !Array.isArray(raw.disabledTools)) {
182
- return createDefaultSettings();
181
+ const settings = createDefaultSettings();
182
+ setDefaultJsonView(settings.defaultJsonView);
183
+ return settings;
183
184
  }
184
- return {
185
+ const settings = {
185
186
  disabledTools: raw.disabledTools.filter((t: unknown) => typeof t === 'string'),
187
+ defaultJsonView: typeof raw.defaultJsonView === 'boolean' ? raw.defaultJsonView : false,
186
188
  };
189
+ setDefaultJsonView(settings.defaultJsonView);
190
+ return settings;
187
191
  } catch {
188
- return createDefaultSettings();
192
+ const settings = createDefaultSettings();
193
+ setDefaultJsonView(settings.defaultJsonView);
194
+ return settings;
189
195
  }
190
196
  }
191
197
 
192
- function saveSettings(settings: ToolSettings): boolean {
198
+ async function saveSettings(settings: ToolSettings): Promise<boolean> {
193
199
  try {
194
- mkdirSync(dirname(SETTINGS_PATH), { recursive: true });
195
- writeFileSync(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}\n`, 'utf8');
200
+ setDefaultJsonView(settings.defaultJsonView);
201
+ await fs.mkdir(dirname(SETTINGS_PATH), { recursive: true });
202
+ await fs.writeFile(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}\n`, 'utf8');
196
203
  return true;
197
204
  } catch {
198
205
  return false;
@@ -259,8 +266,21 @@ function frameBody(title: string, bodyLines: string[], inner: number): string[]
259
266
  return [top, ...framedBody, bottom];
260
267
  }
261
268
 
269
+ function defaultOutputViewValue(settings: ToolSettings): string {
270
+ return settings.defaultJsonView ? 'Full JSON' : 'Human readable';
271
+ }
272
+
262
273
  function buildItems(settings: ToolSettings): SettingItem[] {
263
- 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
+ ];
264
284
  for (const category of TOOL_CATEGORIES) {
265
285
  items.push({
266
286
  id: `category:${category.id}`,
@@ -311,7 +331,7 @@ async function showToolSettingsOverlay(
311
331
  () => {},
312
332
  );
313
333
  const probeLines = probeList.render(Math.max(8, OVERLAY_MAX_INNER - 2));
314
- const overlayBodyLines = ['Toggle Linear tools by category or individually', '', ...probeLines];
334
+ const overlayBodyLines = ['Configure Linear output and enabled tools', '', ...probeLines];
315
335
  const overlayWidth = computeOverlayInner(overlayBodyLines, OVERLAY_MAX_INNER + 2) + 2;
316
336
 
317
337
  await ctx.ui.custom(
@@ -320,7 +340,17 @@ async function showToolSettingsOverlay(
320
340
  items,
321
341
  maxVisibleItems,
322
342
  settingsTheme,
323
- (id, newValue) => {
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
+
324
354
  const nextEnabled = newValue === '[x]';
325
355
 
326
356
  if (id.startsWith('category:')) {
@@ -354,7 +384,7 @@ async function showToolSettingsOverlay(
354
384
  }
355
385
  }
356
386
 
357
- saveSettings(settings);
387
+ await saveSettings(settings);
358
388
  applySettings(pi, settings);
359
389
  },
360
390
  () => done(undefined),
@@ -367,7 +397,7 @@ async function showToolSettingsOverlay(
367
397
  const provisionalInner = Math.max(24, Math.min(safeWidth - 2, OVERLAY_MAX_INNER));
368
398
  const listLines = settingsList.render(Math.max(8, provisionalInner - 2));
369
399
  const bodyLines = [
370
- theme.fg('muted', 'Toggle Linear tools by category or individually'),
400
+ theme.fg('muted', 'Configure Linear output and enabled tools'),
371
401
  '',
372
402
  ...listLines,
373
403
  ];
@@ -392,24 +422,24 @@ async function showToolSettingsOverlay(
392
422
  );
393
423
  }
394
424
 
395
- export function registerLinearSettings(pi: ExtensionAPI): void {
396
- let settings = loadSettings();
425
+ export async function registerLinearSettings(pi: ExtensionAPI): Promise<void> {
426
+ let settings = await loadSettings();
397
427
 
398
428
  pi.registerCommand('linear-settings', {
399
429
  description: 'Open Linear tool settings',
400
430
  handler: async (_args, ctx) => {
401
- settings = loadSettings();
431
+ settings = await loadSettings();
402
432
  await showToolSettingsOverlay(pi, ctx, settings);
403
433
  },
404
434
  });
405
435
 
406
436
  pi.on('session_start', async (_event, _ctx) => {
407
- settings = loadSettings();
437
+ settings = await loadSettings();
408
438
  applySettings(pi, settings);
409
439
  });
410
440
 
411
441
  pi.on('session_before_switch', async (_event, _ctx) => {
412
- settings = loadSettings();
442
+ settings = await loadSettings();
413
443
  applySettings(pi, settings);
414
444
  });
415
445
  }
@@ -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
  }