@alasano/pi-linear 0.0.1

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.
@@ -0,0 +1,357 @@
1
+ import { defineTool } from '@mariozechner/pi-coding-agent';
2
+ import { Type } from '@sinclair/typebox';
3
+ import { withLinearAuth, linearGraphQL, resolveTeamId } from '../client';
4
+ import { PaginationParams, FilterParam, RawInputParam, TeamConvenienceParams } from '../params';
5
+ import { DOCUMENT_SELECTION } from '../selections';
6
+ import type { JsonObject } from '../types';
7
+ import { compactObject, asObject, asString } from '../util';
8
+
9
+ export function documentTools() {
10
+ return [
11
+ defineTool({
12
+ name: 'linear_list_documents',
13
+ label: 'Linear List Documents',
14
+ description: 'List documents. Supports full documents query args.',
15
+ parameters: Type.Object({
16
+ ...PaginationParams,
17
+ ...FilterParam,
18
+ }),
19
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
20
+ return withLinearAuth(ctx, signal, async (apiKey) => {
21
+ const variables = compactObject({
22
+ after: params.after,
23
+ before: params.before,
24
+ filter: asObject(params.filter),
25
+ first: params.first ?? 20,
26
+ includeArchived: params.includeArchived,
27
+ last: params.last,
28
+ orderBy: params.orderBy,
29
+ });
30
+
31
+ const data = await linearGraphQL<{
32
+ documents: { nodes: Array<JsonObject> };
33
+ }>(
34
+ apiKey,
35
+ `query ListDocuments(
36
+ $after: String
37
+ $before: String
38
+ $filter: DocumentFilter
39
+ $first: Int
40
+ $includeArchived: Boolean
41
+ $last: Int
42
+ $orderBy: PaginationOrderBy
43
+ ) {
44
+ documents(
45
+ after: $after
46
+ before: $before
47
+ filter: $filter
48
+ first: $first
49
+ includeArchived: $includeArchived
50
+ last: $last
51
+ orderBy: $orderBy
52
+ ) {
53
+ nodes {
54
+ ${DOCUMENT_SELECTION}
55
+ }
56
+ }
57
+ }`,
58
+ variables,
59
+ signal,
60
+ );
61
+
62
+ const documents = data.documents.nodes;
63
+ return {
64
+ content: [{ type: 'text', text: JSON.stringify({ documents }, null, 2) }],
65
+ details: { documents },
66
+ };
67
+ });
68
+ },
69
+ }),
70
+ defineTool({
71
+ name: 'linear_get_document',
72
+ label: 'Linear Get Document',
73
+ description: 'Get a specific document by id.',
74
+ parameters: Type.Object({
75
+ documentId: Type.String(),
76
+ }),
77
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
78
+ return withLinearAuth(ctx, signal, async (apiKey) => {
79
+ const data = await linearGraphQL<{ document: JsonObject | null }>(
80
+ apiKey,
81
+ `query GetDocument($id: String!) {
82
+ document(id: $id) {
83
+ ${DOCUMENT_SELECTION}
84
+ }
85
+ }`,
86
+ { id: params.documentId },
87
+ signal,
88
+ );
89
+
90
+ const document = data.document;
91
+ return {
92
+ content: [
93
+ { type: 'text', text: JSON.stringify({ document: document ?? null }, null, 2) },
94
+ ],
95
+ details: { document: document ?? null },
96
+ };
97
+ });
98
+ },
99
+ }),
100
+ defineTool({
101
+ name: 'linear_create_document',
102
+ label: 'Linear Create Document',
103
+ description:
104
+ 'Create a document. Supports top-level DocumentCreateInput fields and raw input.',
105
+ parameters: Type.Object({
106
+ color: Type.Optional(Type.String()),
107
+ content: Type.Optional(Type.String()),
108
+ cycleId: Type.Optional(Type.String()),
109
+ icon: Type.Optional(Type.String()),
110
+ id: Type.Optional(Type.String()),
111
+ initiativeId: Type.Optional(Type.String()),
112
+ issueId: Type.Optional(Type.String()),
113
+ lastAppliedTemplateId: Type.Optional(Type.String()),
114
+ projectId: Type.Optional(Type.String()),
115
+ releaseId: Type.Optional(Type.String()),
116
+ resourceFolderId: Type.Optional(Type.String()),
117
+ sortOrder: Type.Optional(Type.Number()),
118
+ subscriberIds: Type.Optional(Type.Array(Type.String())),
119
+ ...TeamConvenienceParams,
120
+ title: Type.Optional(Type.String()),
121
+ ...RawInputParam,
122
+ }),
123
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
124
+ return withLinearAuth(ctx, signal, async (apiKey) => {
125
+ const rawInput = asObject(params.input) || {};
126
+ const rawInputTeamId = asString(rawInput.teamId);
127
+ const teamId =
128
+ params.teamId || params.teamKey || rawInputTeamId
129
+ ? await resolveTeamId(
130
+ apiKey,
131
+ {
132
+ teamId: params.teamId || rawInputTeamId,
133
+ teamKey: params.teamKey,
134
+ },
135
+ signal,
136
+ )
137
+ : undefined;
138
+
139
+ const input = {
140
+ ...rawInput,
141
+ ...compactObject({
142
+ color: params.color,
143
+ content: params.content,
144
+ cycleId: params.cycleId,
145
+ icon: params.icon,
146
+ id: params.id,
147
+ initiativeId: params.initiativeId,
148
+ issueId: params.issueId,
149
+ lastAppliedTemplateId: params.lastAppliedTemplateId,
150
+ projectId: params.projectId,
151
+ releaseId: params.releaseId,
152
+ resourceFolderId: params.resourceFolderId,
153
+ sortOrder: params.sortOrder,
154
+ subscriberIds: params.subscriberIds,
155
+ teamId,
156
+ title: params.title,
157
+ }),
158
+ };
159
+
160
+ if (!asString(input.title)) {
161
+ throw new Error('Document title is required for documentCreate (title).');
162
+ }
163
+
164
+ const data = await linearGraphQL<{
165
+ documentCreate: { success: boolean; document?: JsonObject | null };
166
+ }>(
167
+ apiKey,
168
+ `mutation CreateDocument($input: DocumentCreateInput!) {
169
+ documentCreate(input: $input) {
170
+ success
171
+ document {
172
+ ${DOCUMENT_SELECTION}
173
+ }
174
+ }
175
+ }`,
176
+ { input },
177
+ signal,
178
+ );
179
+
180
+ if (!data.documentCreate.success || !data.documentCreate.document) {
181
+ throw new Error('Linear documentCreate did not succeed.');
182
+ }
183
+
184
+ const document = data.documentCreate.document;
185
+ return {
186
+ content: [{ type: 'text', text: JSON.stringify({ document }, null, 2) }],
187
+ details: { document },
188
+ };
189
+ });
190
+ },
191
+ }),
192
+ defineTool({
193
+ name: 'linear_update_document',
194
+ label: 'Linear Update Document',
195
+ description:
196
+ 'Update a document by id. Supports top-level DocumentUpdateInput fields and raw input.',
197
+ parameters: Type.Object({
198
+ documentId: Type.String(),
199
+ color: Type.Optional(Type.String()),
200
+ content: Type.Optional(Type.String()),
201
+ cycleId: Type.Optional(Type.String()),
202
+ hiddenAt: Type.Optional(Type.String()),
203
+ icon: Type.Optional(Type.String()),
204
+ initiativeId: Type.Optional(Type.String()),
205
+ issueId: Type.Optional(Type.String()),
206
+ lastAppliedTemplateId: Type.Optional(Type.String()),
207
+ projectId: Type.Optional(Type.String()),
208
+ releaseId: Type.Optional(Type.String()),
209
+ resourceFolderId: Type.Optional(Type.String()),
210
+ sortOrder: Type.Optional(Type.Number()),
211
+ subscriberIds: Type.Optional(Type.Array(Type.String())),
212
+ ...TeamConvenienceParams,
213
+ title: Type.Optional(Type.String()),
214
+ trashed: Type.Optional(Type.Boolean()),
215
+ ...RawInputParam,
216
+ }),
217
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
218
+ return withLinearAuth(ctx, signal, async (apiKey) => {
219
+ const rawInput = asObject(params.input) || {};
220
+ const rawInputTeamId = asString(rawInput.teamId);
221
+ const teamId =
222
+ params.teamId || params.teamKey || rawInputTeamId
223
+ ? await resolveTeamId(
224
+ apiKey,
225
+ {
226
+ teamId: params.teamId || rawInputTeamId,
227
+ teamKey: params.teamKey,
228
+ },
229
+ signal,
230
+ )
231
+ : undefined;
232
+
233
+ const input = {
234
+ ...rawInput,
235
+ ...compactObject({
236
+ color: params.color,
237
+ content: params.content,
238
+ cycleId: params.cycleId,
239
+ hiddenAt: params.hiddenAt,
240
+ icon: params.icon,
241
+ initiativeId: params.initiativeId,
242
+ issueId: params.issueId,
243
+ lastAppliedTemplateId: params.lastAppliedTemplateId,
244
+ projectId: params.projectId,
245
+ releaseId: params.releaseId,
246
+ resourceFolderId: params.resourceFolderId,
247
+ sortOrder: params.sortOrder,
248
+ subscriberIds: params.subscriberIds,
249
+ teamId,
250
+ title: params.title,
251
+ trashed: params.trashed,
252
+ }),
253
+ };
254
+
255
+ if (Object.keys(input).length === 0) {
256
+ throw new Error('No document update fields were provided.');
257
+ }
258
+
259
+ const data = await linearGraphQL<{
260
+ documentUpdate: { success: boolean; document?: JsonObject | null };
261
+ }>(
262
+ apiKey,
263
+ `mutation UpdateDocument($id: String!, $input: DocumentUpdateInput!) {
264
+ documentUpdate(id: $id, input: $input) {
265
+ success
266
+ document {
267
+ ${DOCUMENT_SELECTION}
268
+ }
269
+ }
270
+ }`,
271
+ {
272
+ id: params.documentId,
273
+ input,
274
+ },
275
+ signal,
276
+ );
277
+
278
+ if (!data.documentUpdate.success || !data.documentUpdate.document) {
279
+ throw new Error('Linear documentUpdate did not succeed.');
280
+ }
281
+
282
+ const document = data.documentUpdate.document;
283
+ return {
284
+ content: [{ type: 'text', text: JSON.stringify({ document }, null, 2) }],
285
+ details: { document },
286
+ };
287
+ });
288
+ },
289
+ }),
290
+ defineTool({
291
+ name: 'linear_delete_document',
292
+ label: 'Linear Delete Document',
293
+ description: 'Delete a document by id.',
294
+ parameters: Type.Object({
295
+ documentId: Type.String(),
296
+ }),
297
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
298
+ return withLinearAuth(ctx, signal, async (apiKey) => {
299
+ const data = await linearGraphQL<{
300
+ documentDelete: { success: boolean };
301
+ }>(
302
+ apiKey,
303
+ `mutation DeleteDocument($id: String!) {
304
+ documentDelete(id: $id) {
305
+ success
306
+ }
307
+ }`,
308
+ { id: params.documentId },
309
+ signal,
310
+ );
311
+
312
+ if (!data.documentDelete.success) {
313
+ throw new Error('Linear documentDelete did not succeed.');
314
+ }
315
+
316
+ return {
317
+ content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
318
+ details: { success: true },
319
+ };
320
+ });
321
+ },
322
+ }),
323
+ defineTool({
324
+ name: 'linear_unarchive_document',
325
+ label: 'Linear Unarchive Document',
326
+ description: 'Restore an archived document by id.',
327
+ parameters: Type.Object({
328
+ documentId: Type.String(),
329
+ }),
330
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
331
+ return withLinearAuth(ctx, signal, async (apiKey) => {
332
+ const data = await linearGraphQL<{
333
+ documentUnarchive: { success: boolean };
334
+ }>(
335
+ apiKey,
336
+ `mutation UnarchiveDocument($id: String!) {
337
+ documentUnarchive(id: $id) {
338
+ success
339
+ }
340
+ }`,
341
+ { id: params.documentId },
342
+ signal,
343
+ );
344
+
345
+ if (!data.documentUnarchive.success) {
346
+ throw new Error('Linear documentUnarchive did not succeed.');
347
+ }
348
+
349
+ return {
350
+ content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
351
+ details: { success: true },
352
+ };
353
+ });
354
+ },
355
+ }),
356
+ ];
357
+ }
@@ -0,0 +1,328 @@
1
+ import { defineTool } from '@mariozechner/pi-coding-agent';
2
+ import { Type } from '@sinclair/typebox';
3
+ import { withLinearAuth, linearGraphQL } from '../client';
4
+ import { PaginationParams, FilterParam, SortParam, RawInputParam } from '../params';
5
+ import { INITIATIVE_SELECTION } from '../selections';
6
+ import type { JsonObject } from '../types';
7
+ import { compactObject, asObject, asObjectArray, asString } from '../util';
8
+
9
+ export function initiativeTools() {
10
+ return [
11
+ defineTool({
12
+ name: 'linear_list_initiatives',
13
+ label: 'Linear List Initiatives',
14
+ description: 'List initiatives. Supports full initiatives query args.',
15
+ parameters: Type.Object({
16
+ ...PaginationParams,
17
+ ...FilterParam,
18
+ ...SortParam,
19
+ }),
20
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
21
+ return withLinearAuth(ctx, signal, async (apiKey) => {
22
+ const variables = compactObject({
23
+ after: params.after,
24
+ before: params.before,
25
+ filter: asObject(params.filter),
26
+ first: params.first ?? 20,
27
+ includeArchived: params.includeArchived,
28
+ last: params.last,
29
+ orderBy: params.orderBy,
30
+ sort: asObjectArray(params.sort),
31
+ });
32
+
33
+ const data = await linearGraphQL<{
34
+ initiatives: { nodes: Array<JsonObject> };
35
+ }>(
36
+ apiKey,
37
+ `query ListInitiatives(
38
+ $after: String
39
+ $before: String
40
+ $filter: InitiativeFilter
41
+ $first: Int
42
+ $includeArchived: Boolean
43
+ $last: Int
44
+ $orderBy: PaginationOrderBy
45
+ $sort: [InitiativeSortInput!]
46
+ ) {
47
+ initiatives(
48
+ after: $after
49
+ before: $before
50
+ filter: $filter
51
+ first: $first
52
+ includeArchived: $includeArchived
53
+ last: $last
54
+ orderBy: $orderBy
55
+ sort: $sort
56
+ ) {
57
+ nodes {
58
+ ${INITIATIVE_SELECTION}
59
+ }
60
+ }
61
+ }`,
62
+ variables,
63
+ signal,
64
+ );
65
+
66
+ const initiatives = data.initiatives.nodes;
67
+ return {
68
+ content: [{ type: 'text', text: JSON.stringify({ initiatives }, null, 2) }],
69
+ details: { initiatives },
70
+ };
71
+ });
72
+ },
73
+ }),
74
+ defineTool({
75
+ name: 'linear_get_initiative',
76
+ label: 'Linear Get Initiative',
77
+ description: 'Get a specific initiative by id.',
78
+ parameters: Type.Object({
79
+ initiativeId: Type.String(),
80
+ }),
81
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
82
+ return withLinearAuth(ctx, signal, async (apiKey) => {
83
+ const data = await linearGraphQL<{ initiative: JsonObject | null }>(
84
+ apiKey,
85
+ `query GetInitiative($id: String!) {
86
+ initiative(id: $id) {
87
+ ${INITIATIVE_SELECTION}
88
+ }
89
+ }`,
90
+ { id: params.initiativeId },
91
+ signal,
92
+ );
93
+
94
+ const initiative = data.initiative;
95
+ return {
96
+ content: [
97
+ { type: 'text', text: JSON.stringify({ initiative: initiative ?? null }, null, 2) },
98
+ ],
99
+ details: { initiative: initiative ?? null },
100
+ };
101
+ });
102
+ },
103
+ }),
104
+ defineTool({
105
+ name: 'linear_save_initiative',
106
+ label: 'Linear Save Initiative',
107
+ description:
108
+ 'Create or update an initiative. If initiativeId is provided, uses initiativeUpdate; otherwise uses initiativeCreate.',
109
+ parameters: Type.Object({
110
+ initiativeId: Type.Optional(Type.String()),
111
+ color: Type.Optional(Type.String()),
112
+ content: Type.Optional(Type.String()),
113
+ description: Type.Optional(Type.String()),
114
+ icon: Type.Optional(Type.String()),
115
+ id: Type.Optional(Type.String()),
116
+ name: Type.Optional(Type.String()),
117
+ ownerId: Type.Optional(Type.String()),
118
+ sortOrder: Type.Optional(Type.Number()),
119
+ status: Type.Optional(Type.String()),
120
+ targetDate: Type.Optional(Type.String()),
121
+ targetDateResolution: Type.Optional(Type.String()),
122
+ frequencyResolution: Type.Optional(Type.String()),
123
+ trashed: Type.Optional(Type.Boolean()),
124
+ updateReminderFrequency: Type.Optional(Type.Number()),
125
+ updateReminderFrequencyInWeeks: Type.Optional(Type.Number()),
126
+ updateRemindersDay: Type.Optional(Type.String()),
127
+ updateRemindersHour: Type.Optional(Type.Integer()),
128
+ ...RawInputParam,
129
+ }),
130
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
131
+ return withLinearAuth(ctx, signal, async (apiKey) => {
132
+ const rawInput = asObject(params.input) || {};
133
+ const updateId = asString(params.initiativeId);
134
+
135
+ const input = {
136
+ ...rawInput,
137
+ ...compactObject({
138
+ color: params.color,
139
+ content: params.content,
140
+ description: params.description,
141
+ frequencyResolution: params.frequencyResolution,
142
+ icon: params.icon,
143
+ id: params.id,
144
+ name: params.name,
145
+ ownerId: params.ownerId,
146
+ sortOrder: params.sortOrder,
147
+ status: params.status,
148
+ targetDate: params.targetDate,
149
+ targetDateResolution: params.targetDateResolution,
150
+ trashed: params.trashed,
151
+ updateReminderFrequency: params.updateReminderFrequency,
152
+ updateReminderFrequencyInWeeks: params.updateReminderFrequencyInWeeks,
153
+ updateRemindersDay: params.updateRemindersDay,
154
+ updateRemindersHour: params.updateRemindersHour,
155
+ }),
156
+ };
157
+
158
+ if (updateId) {
159
+ if (Object.keys(input).length === 0) {
160
+ throw new Error('No initiative update fields were provided.');
161
+ }
162
+
163
+ const data = await linearGraphQL<{
164
+ initiativeUpdate: {
165
+ success: boolean;
166
+ initiative?: JsonObject | null;
167
+ };
168
+ }>(
169
+ apiKey,
170
+ `mutation UpdateInitiative($id: String!, $input: InitiativeUpdateInput!) {
171
+ initiativeUpdate(id: $id, input: $input) {
172
+ success
173
+ initiative {
174
+ ${INITIATIVE_SELECTION}
175
+ }
176
+ }
177
+ }`,
178
+ { id: updateId, input },
179
+ signal,
180
+ );
181
+
182
+ if (!data.initiativeUpdate.success || !data.initiativeUpdate.initiative) {
183
+ throw new Error('Linear initiativeUpdate did not succeed.');
184
+ }
185
+
186
+ const initiative = data.initiativeUpdate.initiative;
187
+ return {
188
+ content: [{ type: 'text', text: JSON.stringify({ initiative }, null, 2) }],
189
+ details: { initiative },
190
+ };
191
+ }
192
+
193
+ if (!asString(input.name)) {
194
+ throw new Error('Initiative name is required for initiativeCreate (name).');
195
+ }
196
+
197
+ const data = await linearGraphQL<{
198
+ initiativeCreate: {
199
+ success: boolean;
200
+ initiative?: JsonObject | null;
201
+ };
202
+ }>(
203
+ apiKey,
204
+ `mutation CreateInitiative($input: InitiativeCreateInput!) {
205
+ initiativeCreate(input: $input) {
206
+ success
207
+ initiative {
208
+ ${INITIATIVE_SELECTION}
209
+ }
210
+ }
211
+ }`,
212
+ { input },
213
+ signal,
214
+ );
215
+
216
+ if (!data.initiativeCreate.success || !data.initiativeCreate.initiative) {
217
+ throw new Error('Linear initiativeCreate did not succeed.');
218
+ }
219
+
220
+ const initiative = data.initiativeCreate.initiative;
221
+ return {
222
+ content: [{ type: 'text', text: JSON.stringify({ initiative }, null, 2) }],
223
+ details: { initiative },
224
+ };
225
+ });
226
+ },
227
+ }),
228
+ defineTool({
229
+ name: 'linear_delete_initiative',
230
+ label: 'Linear Delete Initiative',
231
+ description: 'Delete an initiative by id.',
232
+ parameters: Type.Object({
233
+ initiativeId: Type.String(),
234
+ }),
235
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
236
+ return withLinearAuth(ctx, signal, async (apiKey) => {
237
+ const data = await linearGraphQL<{
238
+ initiativeDelete: { success: boolean };
239
+ }>(
240
+ apiKey,
241
+ `mutation DeleteInitiative($id: String!) {
242
+ initiativeDelete(id: $id) {
243
+ success
244
+ }
245
+ }`,
246
+ { id: params.initiativeId },
247
+ signal,
248
+ );
249
+
250
+ if (!data.initiativeDelete.success) {
251
+ throw new Error('Linear initiativeDelete did not succeed.');
252
+ }
253
+
254
+ return {
255
+ content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
256
+ details: { success: true },
257
+ };
258
+ });
259
+ },
260
+ }),
261
+ defineTool({
262
+ name: 'linear_archive_initiative',
263
+ label: 'Linear Archive Initiative',
264
+ description: 'Archive an initiative by id.',
265
+ parameters: Type.Object({
266
+ initiativeId: Type.String(),
267
+ }),
268
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
269
+ return withLinearAuth(ctx, signal, async (apiKey) => {
270
+ const data = await linearGraphQL<{
271
+ initiativeArchive: { success: boolean };
272
+ }>(
273
+ apiKey,
274
+ `mutation ArchiveInitiative($id: String!) {
275
+ initiativeArchive(id: $id) {
276
+ success
277
+ }
278
+ }`,
279
+ { id: params.initiativeId },
280
+ signal,
281
+ );
282
+
283
+ if (!data.initiativeArchive.success) {
284
+ throw new Error('Linear initiativeArchive did not succeed.');
285
+ }
286
+
287
+ return {
288
+ content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
289
+ details: { success: true },
290
+ };
291
+ });
292
+ },
293
+ }),
294
+ defineTool({
295
+ name: 'linear_unarchive_initiative',
296
+ label: 'Linear Unarchive Initiative',
297
+ description: 'Unarchive an initiative by id.',
298
+ parameters: Type.Object({
299
+ initiativeId: Type.String(),
300
+ }),
301
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
302
+ return withLinearAuth(ctx, signal, async (apiKey) => {
303
+ const data = await linearGraphQL<{
304
+ initiativeUnarchive: { success: boolean };
305
+ }>(
306
+ apiKey,
307
+ `mutation UnarchiveInitiative($id: String!) {
308
+ initiativeUnarchive(id: $id) {
309
+ success
310
+ }
311
+ }`,
312
+ { id: params.initiativeId },
313
+ signal,
314
+ );
315
+
316
+ if (!data.initiativeUnarchive.success) {
317
+ throw new Error('Linear initiativeUnarchive did not succeed.');
318
+ }
319
+
320
+ return {
321
+ content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
322
+ details: { success: true },
323
+ };
324
+ });
325
+ },
326
+ }),
327
+ ];
328
+ }