@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,250 @@
1
+ import { defineTool } from '@mariozechner/pi-coding-agent';
2
+ import { Type } from '@sinclair/typebox';
3
+ import { withLinearAuth, linearGraphQL } from '../client';
4
+ import { PaginationParams, FilterParam, RawInputParam } from '../params';
5
+ import { MILESTONE_SELECTION } from '../selections';
6
+ import type { JsonObject } from '../types';
7
+ import { compactObject, asObject, asString, GenericObjectSchema } from '../util';
8
+
9
+ export function milestoneTools() {
10
+ return [
11
+ defineTool({
12
+ name: 'linear_list_milestones',
13
+ label: 'Linear List Milestones',
14
+ description: 'List project milestones. Supports full projectMilestones 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
+ projectMilestones: { nodes: Array<JsonObject> };
33
+ }>(
34
+ apiKey,
35
+ `query ListMilestones(
36
+ $after: String
37
+ $before: String
38
+ $filter: ProjectMilestoneFilter
39
+ $first: Int
40
+ $includeArchived: Boolean
41
+ $last: Int
42
+ $orderBy: PaginationOrderBy
43
+ ) {
44
+ projectMilestones(
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
+ ${MILESTONE_SELECTION}
55
+ }
56
+ }
57
+ }`,
58
+ variables,
59
+ signal,
60
+ );
61
+
62
+ const milestones = data.projectMilestones.nodes;
63
+ return {
64
+ content: [{ type: 'text', text: JSON.stringify({ milestones }, null, 2) }],
65
+ details: { milestones },
66
+ };
67
+ });
68
+ },
69
+ }),
70
+ defineTool({
71
+ name: 'linear_get_milestone',
72
+ label: 'Linear Get Milestone',
73
+ description: 'Get a specific project milestone by id.',
74
+ parameters: Type.Object({
75
+ milestoneId: Type.String(),
76
+ }),
77
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
78
+ return withLinearAuth(ctx, signal, async (apiKey) => {
79
+ const data = await linearGraphQL<{
80
+ projectMilestone: JsonObject | null;
81
+ }>(
82
+ apiKey,
83
+ `query GetMilestone($id: String!) {
84
+ projectMilestone(id: $id) {
85
+ ${MILESTONE_SELECTION}
86
+ }
87
+ }`,
88
+ { id: params.milestoneId },
89
+ signal,
90
+ );
91
+
92
+ const milestone = data.projectMilestone;
93
+ return {
94
+ content: [
95
+ { type: 'text', text: JSON.stringify({ milestone: milestone ?? null }, null, 2) },
96
+ ],
97
+ details: { milestone: milestone ?? null },
98
+ };
99
+ });
100
+ },
101
+ }),
102
+ defineTool({
103
+ name: 'linear_save_milestone',
104
+ label: 'Linear Save Milestone',
105
+ description:
106
+ 'Create or update a project milestone. If milestoneId is provided, uses projectMilestoneUpdate; otherwise uses projectMilestoneCreate.',
107
+ parameters: Type.Object({
108
+ milestoneId: Type.Optional(Type.String()),
109
+ description: Type.Optional(Type.String()),
110
+ descriptionData: Type.Optional(GenericObjectSchema),
111
+ id: Type.Optional(Type.String()),
112
+ name: Type.Optional(Type.String()),
113
+ projectId: Type.Optional(Type.String()),
114
+ sortOrder: Type.Optional(Type.Number()),
115
+ targetDate: Type.Optional(Type.String()),
116
+ ...RawInputParam,
117
+ }),
118
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
119
+ return withLinearAuth(ctx, signal, async (apiKey) => {
120
+ const rawInput = asObject(params.input) || {};
121
+ const updateId = asString(params.milestoneId);
122
+
123
+ const input = {
124
+ ...rawInput,
125
+ ...compactObject({
126
+ description: params.description,
127
+ descriptionData: asObject(params.descriptionData),
128
+ id: params.id,
129
+ name: params.name,
130
+ projectId: params.projectId,
131
+ sortOrder: params.sortOrder,
132
+ targetDate: params.targetDate,
133
+ }),
134
+ };
135
+
136
+ if (updateId) {
137
+ if (Object.keys(input).length === 0) {
138
+ throw new Error('No milestone update fields were provided.');
139
+ }
140
+
141
+ const data = await linearGraphQL<{
142
+ projectMilestoneUpdate: {
143
+ success: boolean;
144
+ projectMilestone?: JsonObject | null;
145
+ };
146
+ }>(
147
+ apiKey,
148
+ `mutation UpdateMilestone($id: String!, $input: ProjectMilestoneUpdateInput!) {
149
+ projectMilestoneUpdate(id: $id, input: $input) {
150
+ success
151
+ projectMilestone {
152
+ ${MILESTONE_SELECTION}
153
+ }
154
+ }
155
+ }`,
156
+ { id: updateId, input },
157
+ signal,
158
+ );
159
+
160
+ if (
161
+ !data.projectMilestoneUpdate.success ||
162
+ !data.projectMilestoneUpdate.projectMilestone
163
+ ) {
164
+ throw new Error('Linear projectMilestoneUpdate did not succeed.');
165
+ }
166
+
167
+ const milestone = data.projectMilestoneUpdate.projectMilestone;
168
+ return {
169
+ content: [{ type: 'text', text: JSON.stringify({ milestone }, null, 2) }],
170
+ details: { milestone },
171
+ };
172
+ }
173
+
174
+ if (!asString(input.name)) {
175
+ throw new Error('Milestone name is required for projectMilestoneCreate (name).');
176
+ }
177
+
178
+ if (!asString(input.projectId)) {
179
+ throw new Error('projectId is required for projectMilestoneCreate.');
180
+ }
181
+
182
+ const data = await linearGraphQL<{
183
+ projectMilestoneCreate: {
184
+ success: boolean;
185
+ projectMilestone?: JsonObject | null;
186
+ };
187
+ }>(
188
+ apiKey,
189
+ `mutation CreateMilestone($input: ProjectMilestoneCreateInput!) {
190
+ projectMilestoneCreate(input: $input) {
191
+ success
192
+ projectMilestone {
193
+ ${MILESTONE_SELECTION}
194
+ }
195
+ }
196
+ }`,
197
+ { input },
198
+ signal,
199
+ );
200
+
201
+ if (
202
+ !data.projectMilestoneCreate.success ||
203
+ !data.projectMilestoneCreate.projectMilestone
204
+ ) {
205
+ throw new Error('Linear projectMilestoneCreate did not succeed.');
206
+ }
207
+
208
+ const milestone = data.projectMilestoneCreate.projectMilestone;
209
+ return {
210
+ content: [{ type: 'text', text: JSON.stringify({ milestone }, null, 2) }],
211
+ details: { milestone },
212
+ };
213
+ });
214
+ },
215
+ }),
216
+ defineTool({
217
+ name: 'linear_delete_milestone',
218
+ label: 'Linear Delete Milestone',
219
+ description: 'Delete a project milestone by id.',
220
+ parameters: Type.Object({
221
+ milestoneId: Type.String(),
222
+ }),
223
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
224
+ return withLinearAuth(ctx, signal, async (apiKey) => {
225
+ const data = await linearGraphQL<{
226
+ projectMilestoneDelete: { success: boolean };
227
+ }>(
228
+ apiKey,
229
+ `mutation DeleteMilestone($id: String!) {
230
+ projectMilestoneDelete(id: $id) {
231
+ success
232
+ }
233
+ }`,
234
+ { id: params.milestoneId },
235
+ signal,
236
+ );
237
+
238
+ if (!data.projectMilestoneDelete.success) {
239
+ throw new Error('Linear projectMilestoneDelete did not succeed.');
240
+ }
241
+
242
+ return {
243
+ content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
244
+ details: { success: true },
245
+ };
246
+ });
247
+ },
248
+ }),
249
+ ];
250
+ }
@@ -0,0 +1,227 @@
1
+ import { defineTool } from '@mariozechner/pi-coding-agent';
2
+ import { Type } from '@sinclair/typebox';
3
+ import { withLinearAuth, linearGraphQL } from '../client';
4
+ import { PaginationParams, FilterParam, RawInputParam } from '../params';
5
+ import { PROJECT_LABEL_SELECTION } from '../selections';
6
+ import type { JsonObject } from '../types';
7
+ import { compactObject, asObject, asString } from '../util';
8
+
9
+ export function projectLabelTools() {
10
+ return [
11
+ defineTool({
12
+ name: 'linear_list_project_labels',
13
+ label: 'Linear List Project Labels',
14
+ description: 'List project labels. Supports full projectLabels 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 ?? 50,
26
+ includeArchived: params.includeArchived,
27
+ last: params.last,
28
+ orderBy: params.orderBy,
29
+ });
30
+
31
+ const data = await linearGraphQL<{
32
+ projectLabels: { nodes: Array<JsonObject> };
33
+ }>(
34
+ apiKey,
35
+ `query ListProjectLabels(
36
+ $after: String
37
+ $before: String
38
+ $filter: ProjectLabelFilter
39
+ $first: Int
40
+ $includeArchived: Boolean
41
+ $last: Int
42
+ $orderBy: PaginationOrderBy
43
+ ) {
44
+ projectLabels(
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
+ ${PROJECT_LABEL_SELECTION}
55
+ }
56
+ }
57
+ }`,
58
+ variables,
59
+ signal,
60
+ );
61
+
62
+ const labels = data.projectLabels.nodes;
63
+ return {
64
+ content: [{ type: 'text', text: JSON.stringify({ labels }, null, 2) }],
65
+ details: { labels },
66
+ };
67
+ });
68
+ },
69
+ }),
70
+ defineTool({
71
+ name: 'linear_create_project_label',
72
+ label: 'Linear Create Project Label',
73
+ description: 'Create a project label.',
74
+ parameters: Type.Object({
75
+ name: Type.Optional(Type.String()),
76
+ description: Type.Optional(Type.String()),
77
+ color: Type.Optional(Type.String()),
78
+ parentId: Type.Optional(Type.String()),
79
+ isGroup: Type.Optional(Type.Boolean()),
80
+ ...RawInputParam,
81
+ }),
82
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
83
+ return withLinearAuth(ctx, signal, async (apiKey) => {
84
+ const rawInput = asObject(params.input) || {};
85
+ const input = {
86
+ ...rawInput,
87
+ ...compactObject({
88
+ name: params.name,
89
+ description: params.description,
90
+ color: params.color,
91
+ parentId: params.parentId,
92
+ isGroup: params.isGroup,
93
+ }),
94
+ };
95
+
96
+ if (!asString(input.name)) {
97
+ throw new Error('Project label name is required (name).');
98
+ }
99
+
100
+ const data = await linearGraphQL<{
101
+ projectLabelCreate: {
102
+ success: boolean;
103
+ projectLabel?: JsonObject | null;
104
+ };
105
+ }>(
106
+ apiKey,
107
+ `mutation CreateProjectLabel($input: ProjectLabelCreateInput!) {
108
+ projectLabelCreate(input: $input) {
109
+ success
110
+ projectLabel {
111
+ ${PROJECT_LABEL_SELECTION}
112
+ }
113
+ }
114
+ }`,
115
+ { input },
116
+ signal,
117
+ );
118
+
119
+ const label = data.projectLabelCreate.projectLabel;
120
+ if (!data.projectLabelCreate.success || !label) {
121
+ throw new Error('Linear projectLabelCreate did not succeed.');
122
+ }
123
+
124
+ return {
125
+ content: [{ type: 'text', text: JSON.stringify({ label }, null, 2) }],
126
+ details: { label },
127
+ };
128
+ });
129
+ },
130
+ }),
131
+ defineTool({
132
+ name: 'linear_update_project_label',
133
+ label: 'Linear Update Project Label',
134
+ description: 'Update a project label by id.',
135
+ parameters: Type.Object({
136
+ id: Type.String(),
137
+ name: Type.Optional(Type.String()),
138
+ description: Type.Optional(Type.String()),
139
+ color: Type.Optional(Type.String()),
140
+ parentId: Type.Optional(Type.String()),
141
+ isGroup: Type.Optional(Type.Boolean()),
142
+ ...RawInputParam,
143
+ }),
144
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
145
+ return withLinearAuth(ctx, signal, async (apiKey) => {
146
+ const rawInput = asObject(params.input) || {};
147
+ const input = {
148
+ ...rawInput,
149
+ ...compactObject({
150
+ name: params.name,
151
+ description: params.description,
152
+ color: params.color,
153
+ parentId: params.parentId,
154
+ isGroup: params.isGroup,
155
+ }),
156
+ };
157
+
158
+ if (Object.keys(input).length === 0) {
159
+ throw new Error('No update fields were provided.');
160
+ }
161
+
162
+ const data = await linearGraphQL<{
163
+ projectLabelUpdate: {
164
+ success: boolean;
165
+ projectLabel?: JsonObject | null;
166
+ };
167
+ }>(
168
+ apiKey,
169
+ `mutation UpdateProjectLabel($id: String!, $input: ProjectLabelUpdateInput!) {
170
+ projectLabelUpdate(id: $id, input: $input) {
171
+ success
172
+ projectLabel {
173
+ ${PROJECT_LABEL_SELECTION}
174
+ }
175
+ }
176
+ }`,
177
+ { id: params.id, input },
178
+ signal,
179
+ );
180
+
181
+ const label = data.projectLabelUpdate.projectLabel;
182
+ if (!data.projectLabelUpdate.success || !label) {
183
+ throw new Error('Linear projectLabelUpdate did not succeed.');
184
+ }
185
+
186
+ return {
187
+ content: [{ type: 'text', text: JSON.stringify({ label }, null, 2) }],
188
+ details: { label },
189
+ };
190
+ });
191
+ },
192
+ }),
193
+ defineTool({
194
+ name: 'linear_delete_project_label',
195
+ label: 'Linear Delete Project Label',
196
+ description: 'Delete a project label by id.',
197
+ parameters: Type.Object({
198
+ id: Type.String(),
199
+ }),
200
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
201
+ return withLinearAuth(ctx, signal, async (apiKey) => {
202
+ const data = await linearGraphQL<{
203
+ projectLabelDelete: { success: boolean };
204
+ }>(
205
+ apiKey,
206
+ `mutation DeleteProjectLabel($id: String!) {
207
+ projectLabelDelete(id: $id) {
208
+ success
209
+ }
210
+ }`,
211
+ { id: params.id },
212
+ signal,
213
+ );
214
+
215
+ if (!data.projectLabelDelete.success) {
216
+ throw new Error('Linear projectLabelDelete did not succeed.');
217
+ }
218
+
219
+ return {
220
+ content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
221
+ details: { success: true },
222
+ };
223
+ });
224
+ },
225
+ }),
226
+ ];
227
+ }
@@ -0,0 +1,219 @@
1
+ import { defineTool } from '@mariozechner/pi-coding-agent';
2
+ import { Type } from '@sinclair/typebox';
3
+ import { withLinearAuth, linearGraphQL } from '../client';
4
+ import { PaginationParams } from '../params';
5
+ import { PROJECT_RELATION_SELECTION } from '../selections';
6
+ import type { JsonObject } from '../types';
7
+ import { compactObject } from '../util';
8
+
9
+ export function projectRelationTools() {
10
+ return [
11
+ defineTool({
12
+ name: 'linear_list_project_relations',
13
+ label: 'Linear List Project Relations',
14
+ description: 'List project relations. Supports pagination.',
15
+ parameters: Type.Object({
16
+ ...PaginationParams,
17
+ }),
18
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
19
+ return withLinearAuth(ctx, signal, async (apiKey) => {
20
+ const variables = compactObject({
21
+ after: params.after,
22
+ before: params.before,
23
+ first: params.first ?? 20,
24
+ includeArchived: params.includeArchived,
25
+ last: params.last,
26
+ orderBy: params.orderBy,
27
+ });
28
+
29
+ const data = await linearGraphQL<{
30
+ projectRelations: { nodes: Array<JsonObject> };
31
+ }>(
32
+ apiKey,
33
+ `query ListProjectRelations(
34
+ $after: String
35
+ $before: String
36
+ $first: Int
37
+ $includeArchived: Boolean
38
+ $last: Int
39
+ $orderBy: PaginationOrderBy
40
+ ) {
41
+ projectRelations(
42
+ after: $after
43
+ before: $before
44
+ first: $first
45
+ includeArchived: $includeArchived
46
+ last: $last
47
+ orderBy: $orderBy
48
+ ) {
49
+ nodes {
50
+ ${PROJECT_RELATION_SELECTION}
51
+ }
52
+ }
53
+ }`,
54
+ variables,
55
+ signal,
56
+ );
57
+
58
+ const projectRelations = data.projectRelations.nodes;
59
+ return {
60
+ content: [{ type: 'text', text: JSON.stringify({ projectRelations }, null, 2) }],
61
+ details: { projectRelations },
62
+ };
63
+ });
64
+ },
65
+ }),
66
+ defineTool({
67
+ name: 'linear_create_project_relation',
68
+ label: 'Linear Create Project Relation',
69
+ description: 'Create a relation between two projects.',
70
+ parameters: Type.Object({
71
+ projectId: Type.String(),
72
+ relatedProjectId: Type.String(),
73
+ type: Type.String({ description: 'Relation type.' }),
74
+ anchorType: Type.String({ description: 'Anchor type for the project.' }),
75
+ relatedAnchorType: Type.String({
76
+ description: 'Anchor type for the related project.',
77
+ }),
78
+ projectMilestoneId: Type.Optional(Type.String()),
79
+ relatedProjectMilestoneId: Type.Optional(Type.String()),
80
+ }),
81
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
82
+ return withLinearAuth(ctx, signal, async (apiKey) => {
83
+ const input = compactObject({
84
+ projectId: params.projectId,
85
+ relatedProjectId: params.relatedProjectId,
86
+ type: params.type,
87
+ anchorType: params.anchorType,
88
+ relatedAnchorType: params.relatedAnchorType,
89
+ projectMilestoneId: params.projectMilestoneId,
90
+ relatedProjectMilestoneId: params.relatedProjectMilestoneId,
91
+ });
92
+
93
+ const data = await linearGraphQL<{
94
+ projectRelationCreate: {
95
+ success: boolean;
96
+ projectRelation?: JsonObject | null;
97
+ };
98
+ }>(
99
+ apiKey,
100
+ `mutation CreateProjectRelation($input: ProjectRelationCreateInput!) {
101
+ projectRelationCreate(input: $input) {
102
+ success
103
+ projectRelation {
104
+ ${PROJECT_RELATION_SELECTION}
105
+ }
106
+ }
107
+ }`,
108
+ { input },
109
+ signal,
110
+ );
111
+
112
+ const projectRelation = data.projectRelationCreate.projectRelation;
113
+ if (!data.projectRelationCreate.success || !projectRelation) {
114
+ throw new Error('Linear projectRelationCreate did not succeed.');
115
+ }
116
+
117
+ return {
118
+ content: [{ type: 'text', text: JSON.stringify({ projectRelation }, null, 2) }],
119
+ details: { projectRelation },
120
+ };
121
+ });
122
+ },
123
+ }),
124
+ defineTool({
125
+ name: 'linear_update_project_relation',
126
+ label: 'Linear Update Project Relation',
127
+ description: 'Update a project relation by id.',
128
+ parameters: Type.Object({
129
+ id: Type.String(),
130
+ type: Type.Optional(Type.String()),
131
+ projectId: Type.Optional(Type.String()),
132
+ relatedProjectId: Type.Optional(Type.String()),
133
+ anchorType: Type.Optional(Type.String()),
134
+ relatedAnchorType: Type.Optional(Type.String()),
135
+ projectMilestoneId: Type.Optional(Type.String()),
136
+ relatedProjectMilestoneId: Type.Optional(Type.String()),
137
+ }),
138
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
139
+ return withLinearAuth(ctx, signal, async (apiKey) => {
140
+ const input = compactObject({
141
+ type: params.type,
142
+ projectId: params.projectId,
143
+ relatedProjectId: params.relatedProjectId,
144
+ anchorType: params.anchorType,
145
+ relatedAnchorType: params.relatedAnchorType,
146
+ projectMilestoneId: params.projectMilestoneId,
147
+ relatedProjectMilestoneId: params.relatedProjectMilestoneId,
148
+ });
149
+
150
+ if (Object.keys(input).length === 0) {
151
+ throw new Error('No update fields were provided.');
152
+ }
153
+
154
+ const data = await linearGraphQL<{
155
+ projectRelationUpdate: {
156
+ success: boolean;
157
+ projectRelation?: JsonObject | null;
158
+ };
159
+ }>(
160
+ apiKey,
161
+ `mutation UpdateProjectRelation($id: String!, $input: ProjectRelationUpdateInput!) {
162
+ projectRelationUpdate(id: $id, input: $input) {
163
+ success
164
+ projectRelation {
165
+ ${PROJECT_RELATION_SELECTION}
166
+ }
167
+ }
168
+ }`,
169
+ { id: params.id, input },
170
+ signal,
171
+ );
172
+
173
+ const projectRelation = data.projectRelationUpdate.projectRelation;
174
+ if (!data.projectRelationUpdate.success || !projectRelation) {
175
+ throw new Error('Linear projectRelationUpdate did not succeed.');
176
+ }
177
+
178
+ return {
179
+ content: [{ type: 'text', text: JSON.stringify({ projectRelation }, null, 2) }],
180
+ details: { projectRelation },
181
+ };
182
+ });
183
+ },
184
+ }),
185
+ defineTool({
186
+ name: 'linear_delete_project_relation',
187
+ label: 'Linear Delete Project Relation',
188
+ description: 'Delete a project relation by id.',
189
+ parameters: Type.Object({
190
+ id: Type.String(),
191
+ }),
192
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
193
+ return withLinearAuth(ctx, signal, async (apiKey) => {
194
+ const data = await linearGraphQL<{
195
+ projectRelationDelete: { success: boolean };
196
+ }>(
197
+ apiKey,
198
+ `mutation DeleteProjectRelation($id: String!) {
199
+ projectRelationDelete(id: $id) {
200
+ success
201
+ }
202
+ }`,
203
+ { id: params.id },
204
+ signal,
205
+ );
206
+
207
+ if (!data.projectRelationDelete.success) {
208
+ throw new Error('Linear projectRelationDelete did not succeed.');
209
+ }
210
+
211
+ return {
212
+ content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
213
+ details: { success: true },
214
+ };
215
+ });
216
+ },
217
+ }),
218
+ ];
219
+ }