@render-harness/cap-linear 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -0
- package/dist/index.js +575 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { definePack } from '@render-harness/registry';
|
|
3
|
+
import { LinearClient } from '@linear/sdk';
|
|
4
|
+
import { LinearWebhookClient } from '@linear/sdk/webhooks';
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
|
|
8
|
+
// src/normalize.ts
|
|
9
|
+
function normalizeLinearEvent(body, cfg = {}) {
|
|
10
|
+
if (!body || typeof body !== "object") return null;
|
|
11
|
+
const payload = body;
|
|
12
|
+
const type = stringValue(payload.type);
|
|
13
|
+
if (!type) return null;
|
|
14
|
+
const action = stringValue(payload.action);
|
|
15
|
+
const data = objectValue(payload.data);
|
|
16
|
+
if (!data) return null;
|
|
17
|
+
const actorId = stringAt(payload, "actor.id") ?? stringAt(payload, "actorId");
|
|
18
|
+
if (actorId && cfg.ignoredActors?.includes(actorId)) return null;
|
|
19
|
+
const teamId = stringAt(data, "team.id") ?? stringAt(data, "teamId");
|
|
20
|
+
const teamKey = stringAt(data, "team.key");
|
|
21
|
+
if (cfg.allowedTeams?.length && !matchesAny([teamId, teamKey], cfg.allowedTeams)) return null;
|
|
22
|
+
const projectId = stringAt(data, "project.id") ?? stringAt(data, "projectId");
|
|
23
|
+
const projectName = stringAt(data, "project.name");
|
|
24
|
+
if (cfg.allowedProjects?.length && !matchesAny([projectId, projectName], cfg.allowedProjects)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const state = stringAt(data, "state.name") ?? stringAt(data, "state");
|
|
28
|
+
if (cfg.states?.length && state && !cfg.states.includes(state)) return null;
|
|
29
|
+
if (cfg.labels?.length && !hasAllowedLabel(data, cfg.labels)) return null;
|
|
30
|
+
const issueId = stringValue(data.id);
|
|
31
|
+
const issueIdentifier = stringValue(data.identifier);
|
|
32
|
+
const organizationId = stringValue(payload.organizationId);
|
|
33
|
+
const url = stringValue(data.url);
|
|
34
|
+
return {
|
|
35
|
+
type,
|
|
36
|
+
...action ? { action } : {},
|
|
37
|
+
...organizationId ? { organizationId } : {},
|
|
38
|
+
...teamId ? { teamId } : {},
|
|
39
|
+
...teamKey ? { teamKey } : {},
|
|
40
|
+
...projectId ? { projectId } : {},
|
|
41
|
+
...issueId ? { issueId } : {},
|
|
42
|
+
...issueIdentifier ? { issueIdentifier } : {},
|
|
43
|
+
...state ? { state } : {},
|
|
44
|
+
...actorId ? { actorId } : {},
|
|
45
|
+
...url ? { url } : {},
|
|
46
|
+
summary: `Linear ${type}${issueIdentifier ? ` ${issueIdentifier}` : ""} ${action ?? "changed"}`
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function hasAllowedLabel(data, allowed) {
|
|
50
|
+
const labels = arrayValue(data.labels).map(
|
|
51
|
+
(label) => label && typeof label === "object" ? stringValue(label.name) : null
|
|
52
|
+
).filter((label) => !!label);
|
|
53
|
+
return labels.some((label) => allowed.includes(label));
|
|
54
|
+
}
|
|
55
|
+
function matchesAny(values, allowed) {
|
|
56
|
+
return values.some((value) => value !== void 0 && allowed.includes(value));
|
|
57
|
+
}
|
|
58
|
+
function stringAt(value, path) {
|
|
59
|
+
let cursor = value;
|
|
60
|
+
for (const part of path.split(".")) {
|
|
61
|
+
if (!cursor || typeof cursor !== "object") return void 0;
|
|
62
|
+
cursor = cursor[part];
|
|
63
|
+
}
|
|
64
|
+
return stringValue(cursor);
|
|
65
|
+
}
|
|
66
|
+
function objectValue(value) {
|
|
67
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
68
|
+
}
|
|
69
|
+
function arrayValue(value) {
|
|
70
|
+
return Array.isArray(value) ? value : [];
|
|
71
|
+
}
|
|
72
|
+
function stringValue(value) {
|
|
73
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
74
|
+
}
|
|
75
|
+
function linearTools(args) {
|
|
76
|
+
const client = new LinearClient({ apiKey: args.apiKey });
|
|
77
|
+
const tools = [
|
|
78
|
+
jsonTool(
|
|
79
|
+
"linear.get_issue",
|
|
80
|
+
"Read a Linear issue by id.",
|
|
81
|
+
idSchema("issueId"),
|
|
82
|
+
async (input) => {
|
|
83
|
+
const { issueId } = input;
|
|
84
|
+
return client.client.request(
|
|
85
|
+
`query Issue($id: String!) {
|
|
86
|
+
issue(id: $id) {
|
|
87
|
+
id
|
|
88
|
+
identifier
|
|
89
|
+
title
|
|
90
|
+
description
|
|
91
|
+
url
|
|
92
|
+
state { id name }
|
|
93
|
+
team { id key name }
|
|
94
|
+
project { id name }
|
|
95
|
+
assignee { id name email }
|
|
96
|
+
labels { nodes { id name } }
|
|
97
|
+
}
|
|
98
|
+
}`,
|
|
99
|
+
{ id: issueId }
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
),
|
|
103
|
+
jsonTool(
|
|
104
|
+
"linear.search_issues",
|
|
105
|
+
"Search Linear issues with a text query.",
|
|
106
|
+
objectSchema({ query: { type: "string" } }),
|
|
107
|
+
async (input) => {
|
|
108
|
+
const { query } = input;
|
|
109
|
+
return client.client.request(
|
|
110
|
+
`query SearchIssues($query: String!) {
|
|
111
|
+
issueSearch(query: $query) {
|
|
112
|
+
nodes {
|
|
113
|
+
id
|
|
114
|
+
identifier
|
|
115
|
+
title
|
|
116
|
+
url
|
|
117
|
+
state { name }
|
|
118
|
+
team { key name }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}`,
|
|
122
|
+
{ query }
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
),
|
|
126
|
+
jsonTool(
|
|
127
|
+
"linear.list_comments",
|
|
128
|
+
"List comments for a Linear issue.",
|
|
129
|
+
idSchema("issueId"),
|
|
130
|
+
async (input) => {
|
|
131
|
+
const { issueId } = input;
|
|
132
|
+
return client.client.request(
|
|
133
|
+
`query IssueComments($id: String!) {
|
|
134
|
+
issue(id: $id) {
|
|
135
|
+
comments {
|
|
136
|
+
nodes {
|
|
137
|
+
id
|
|
138
|
+
body
|
|
139
|
+
createdAt
|
|
140
|
+
user { id name email }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}`,
|
|
145
|
+
{ id: issueId }
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
),
|
|
149
|
+
jsonTool(
|
|
150
|
+
"linear.list_teams",
|
|
151
|
+
"List Linear teams.",
|
|
152
|
+
emptySchema(),
|
|
153
|
+
async () => client.client.request(
|
|
154
|
+
`query Teams {
|
|
155
|
+
teams {
|
|
156
|
+
nodes {
|
|
157
|
+
id
|
|
158
|
+
key
|
|
159
|
+
name
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}`
|
|
163
|
+
)
|
|
164
|
+
),
|
|
165
|
+
jsonTool(
|
|
166
|
+
"linear.list_projects",
|
|
167
|
+
"List Linear projects.",
|
|
168
|
+
emptySchema(),
|
|
169
|
+
async () => client.client.request(
|
|
170
|
+
`query Projects {
|
|
171
|
+
projects {
|
|
172
|
+
nodes {
|
|
173
|
+
id
|
|
174
|
+
name
|
|
175
|
+
url
|
|
176
|
+
state
|
|
177
|
+
teams { nodes { id key name } }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}`
|
|
181
|
+
)
|
|
182
|
+
),
|
|
183
|
+
jsonTool(
|
|
184
|
+
"linear.list_workflow_states",
|
|
185
|
+
"List Linear workflow states, optionally scoped to a team.",
|
|
186
|
+
objectSchema({ teamId: { type: "string", optional: true } }),
|
|
187
|
+
async (input) => {
|
|
188
|
+
const { teamId } = input;
|
|
189
|
+
return client.client.request(
|
|
190
|
+
`query WorkflowStates($filter: WorkflowStateFilter) {
|
|
191
|
+
workflowStates(filter: $filter) {
|
|
192
|
+
nodes {
|
|
193
|
+
id
|
|
194
|
+
name
|
|
195
|
+
type
|
|
196
|
+
team { id key name }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}`,
|
|
200
|
+
{ filter: teamId ? { team: { id: { eq: teamId } } } : void 0 }
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
jsonTool(
|
|
205
|
+
"linear.list_users",
|
|
206
|
+
"List Linear users.",
|
|
207
|
+
emptySchema(),
|
|
208
|
+
async () => client.client.request(
|
|
209
|
+
`query Users {
|
|
210
|
+
users {
|
|
211
|
+
nodes {
|
|
212
|
+
id
|
|
213
|
+
name
|
|
214
|
+
email
|
|
215
|
+
active
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}`
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
];
|
|
222
|
+
if (args.accessMode === "read_write") {
|
|
223
|
+
tools.push(
|
|
224
|
+
jsonTool(
|
|
225
|
+
"linear.create_issue",
|
|
226
|
+
"Create a Linear issue.",
|
|
227
|
+
createIssueSchema(),
|
|
228
|
+
async (input) => {
|
|
229
|
+
const { teamId, title, description, assigneeId, projectId, cycleId, stateId, priority } = input;
|
|
230
|
+
return client.client.request(
|
|
231
|
+
`mutation IssueCreate($input: IssueCreateInput!) {
|
|
232
|
+
issueCreate(input: $input) {
|
|
233
|
+
success
|
|
234
|
+
issue { id identifier title url }
|
|
235
|
+
}
|
|
236
|
+
}`,
|
|
237
|
+
{
|
|
238
|
+
input: {
|
|
239
|
+
teamId,
|
|
240
|
+
title,
|
|
241
|
+
...description ? { description } : {},
|
|
242
|
+
...assigneeId ? { assigneeId } : {},
|
|
243
|
+
...projectId ? { projectId } : {},
|
|
244
|
+
...cycleId ? { cycleId } : {},
|
|
245
|
+
...stateId ? { stateId } : {},
|
|
246
|
+
...priority !== void 0 ? { priority } : {}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
),
|
|
252
|
+
jsonTool(
|
|
253
|
+
"linear.create_comment",
|
|
254
|
+
"Create a comment on a Linear issue.",
|
|
255
|
+
objectSchema({ issueId: { type: "string" }, body: { type: "string" } }),
|
|
256
|
+
async (input) => {
|
|
257
|
+
const { issueId, body } = input;
|
|
258
|
+
return client.client.request(
|
|
259
|
+
`mutation CommentCreate($input: CommentCreateInput!) {
|
|
260
|
+
commentCreate(input: $input) {
|
|
261
|
+
success
|
|
262
|
+
comment { id url body }
|
|
263
|
+
}
|
|
264
|
+
}`,
|
|
265
|
+
{ input: { issueId, body } }
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
),
|
|
269
|
+
jsonTool(
|
|
270
|
+
"linear.update_issue",
|
|
271
|
+
"Update Linear issue fields.",
|
|
272
|
+
updateIssueSchema(),
|
|
273
|
+
async (input) => {
|
|
274
|
+
const {
|
|
275
|
+
issueId,
|
|
276
|
+
title,
|
|
277
|
+
description,
|
|
278
|
+
stateId,
|
|
279
|
+
assigneeId,
|
|
280
|
+
projectId,
|
|
281
|
+
cycleId,
|
|
282
|
+
priority,
|
|
283
|
+
labelIds
|
|
284
|
+
} = input;
|
|
285
|
+
return client.client.request(
|
|
286
|
+
`mutation IssueUpdate($id: String!, $input: IssueUpdateInput!) {
|
|
287
|
+
issueUpdate(id: $id, input: $input) {
|
|
288
|
+
success
|
|
289
|
+
issue {
|
|
290
|
+
id
|
|
291
|
+
identifier
|
|
292
|
+
title
|
|
293
|
+
state { id name }
|
|
294
|
+
assignee { id name }
|
|
295
|
+
project { id name }
|
|
296
|
+
cycle { id name }
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}`,
|
|
300
|
+
{
|
|
301
|
+
id: issueId,
|
|
302
|
+
input: {
|
|
303
|
+
...title ? { title } : {},
|
|
304
|
+
...description ? { description } : {},
|
|
305
|
+
...stateId ? { stateId } : {},
|
|
306
|
+
...assigneeId ? { assigneeId } : {},
|
|
307
|
+
...projectId ? { projectId } : {},
|
|
308
|
+
...cycleId ? { cycleId } : {},
|
|
309
|
+
...priority !== void 0 ? { priority } : {},
|
|
310
|
+
...labelIds ? { labelIds } : {}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
),
|
|
316
|
+
jsonTool(
|
|
317
|
+
"linear.update_issue_status",
|
|
318
|
+
"Update a Linear issue's status.",
|
|
319
|
+
objectSchema({ issueId: { type: "string" }, stateId: { type: "string" } }),
|
|
320
|
+
async (input) => {
|
|
321
|
+
const { issueId, stateId } = input;
|
|
322
|
+
return client.client.request(
|
|
323
|
+
`mutation IssueUpdate($id: String!, $input: IssueUpdateInput!) {
|
|
324
|
+
issueUpdate(id: $id, input: $input) {
|
|
325
|
+
success
|
|
326
|
+
issue { id identifier state { id name } }
|
|
327
|
+
}
|
|
328
|
+
}`,
|
|
329
|
+
{ id: issueId, input: { stateId } }
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
),
|
|
333
|
+
jsonTool(
|
|
334
|
+
"linear.assign_issue",
|
|
335
|
+
"Assign a Linear issue to a user.",
|
|
336
|
+
objectSchema({ issueId: { type: "string" }, assigneeId: { type: "string" } }),
|
|
337
|
+
async (input) => {
|
|
338
|
+
const { issueId, assigneeId } = input;
|
|
339
|
+
return client.client.request(
|
|
340
|
+
`mutation IssueAssign($id: String!, $input: IssueUpdateInput!) {
|
|
341
|
+
issueUpdate(id: $id, input: $input) {
|
|
342
|
+
success
|
|
343
|
+
issue { id identifier assignee { id name } }
|
|
344
|
+
}
|
|
345
|
+
}`,
|
|
346
|
+
{ id: issueId, input: { assigneeId } }
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
),
|
|
350
|
+
jsonTool(
|
|
351
|
+
"linear.link_related_issue",
|
|
352
|
+
"Create a Linear issue relation between two issues.",
|
|
353
|
+
objectSchema({
|
|
354
|
+
issueId: { type: "string" },
|
|
355
|
+
relatedIssueId: { type: "string" },
|
|
356
|
+
type: { type: "string" }
|
|
357
|
+
}),
|
|
358
|
+
async (input) => {
|
|
359
|
+
const { issueId, relatedIssueId, type } = input;
|
|
360
|
+
return client.client.request(
|
|
361
|
+
`mutation IssueRelationCreate($input: IssueRelationCreateInput!) {
|
|
362
|
+
issueRelationCreate(input: $input) {
|
|
363
|
+
success
|
|
364
|
+
issueRelation { id type }
|
|
365
|
+
}
|
|
366
|
+
}`,
|
|
367
|
+
{ input: { issueId, relatedIssueId, type } }
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
return tools;
|
|
374
|
+
}
|
|
375
|
+
function jsonTool(name, description, inputSchema, call) {
|
|
376
|
+
return {
|
|
377
|
+
definition: { name, description, inputSchema, source: "pack:cap-linear" },
|
|
378
|
+
handler: async ({ input }) => {
|
|
379
|
+
try {
|
|
380
|
+
return { content: JSON.stringify(await call(input), null, 2) };
|
|
381
|
+
} catch (err) {
|
|
382
|
+
return { content: err instanceof Error ? err.message : String(err), isError: true };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function emptySchema() {
|
|
388
|
+
return objectSchema({});
|
|
389
|
+
}
|
|
390
|
+
function idSchema(name) {
|
|
391
|
+
return objectSchema({ [name]: { type: "string" } });
|
|
392
|
+
}
|
|
393
|
+
function createIssueSchema() {
|
|
394
|
+
return objectSchema({
|
|
395
|
+
teamId: { type: "string" },
|
|
396
|
+
title: { type: "string" },
|
|
397
|
+
description: { type: "string", optional: true },
|
|
398
|
+
assigneeId: { type: "string", optional: true },
|
|
399
|
+
projectId: { type: "string", optional: true },
|
|
400
|
+
cycleId: { type: "string", optional: true },
|
|
401
|
+
stateId: { type: "string", optional: true },
|
|
402
|
+
priority: { type: "number", optional: true }
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
function updateIssueSchema() {
|
|
406
|
+
return objectSchema({
|
|
407
|
+
issueId: { type: "string" },
|
|
408
|
+
title: { type: "string", optional: true },
|
|
409
|
+
description: { type: "string", optional: true },
|
|
410
|
+
stateId: { type: "string", optional: true },
|
|
411
|
+
assigneeId: { type: "string", optional: true },
|
|
412
|
+
projectId: { type: "string", optional: true },
|
|
413
|
+
cycleId: { type: "string", optional: true },
|
|
414
|
+
priority: { type: "number", optional: true },
|
|
415
|
+
labelIds: { type: "array", items: { type: "string" }, optional: true }
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
function objectSchema(properties) {
|
|
419
|
+
return {
|
|
420
|
+
type: "object",
|
|
421
|
+
additionalProperties: false,
|
|
422
|
+
properties,
|
|
423
|
+
required: Object.entries(properties).filter(([, value]) => !value.optional).map(([key]) => key)
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function verifyLinearWebhook(args) {
|
|
427
|
+
if (!args.signature) return false;
|
|
428
|
+
try {
|
|
429
|
+
return new LinearWebhookClient(args.secret).verify(
|
|
430
|
+
Buffer.from(args.rawBody),
|
|
431
|
+
args.signature,
|
|
432
|
+
args.timestamp
|
|
433
|
+
);
|
|
434
|
+
} catch {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/index.ts
|
|
440
|
+
var DEFAULT_WEBHOOK_SECRET_ENV = "LINEAR_WEBHOOK_SECRET";
|
|
441
|
+
var DEFAULT_API_KEY_ENV = "LINEAR_API_KEY";
|
|
442
|
+
var pack = definePack({
|
|
443
|
+
name: "cap-linear",
|
|
444
|
+
version: "0.1.0",
|
|
445
|
+
envSchema: [
|
|
446
|
+
{
|
|
447
|
+
name: DEFAULT_WEBHOOK_SECRET_ENV,
|
|
448
|
+
required: true,
|
|
449
|
+
secret: true,
|
|
450
|
+
description: "Linear webhook signing secret."
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
name: DEFAULT_API_KEY_ENV,
|
|
454
|
+
required: true,
|
|
455
|
+
secret: true,
|
|
456
|
+
description: "Linear API key used for read tools and optional write tools."
|
|
457
|
+
}
|
|
458
|
+
],
|
|
459
|
+
localTools(ctx) {
|
|
460
|
+
const cfg = readConfig(ctx.config);
|
|
461
|
+
const apiKey = ctx.env(cfg.apiKeyEnv);
|
|
462
|
+
if (!apiKey) throw new Error(`cap-linear: ${cfg.apiKeyEnv} is not set`);
|
|
463
|
+
return linearTools({ apiKey, accessMode: cfg.accessMode });
|
|
464
|
+
},
|
|
465
|
+
connectors(ctx) {
|
|
466
|
+
const cfg = readConfig(ctx.config);
|
|
467
|
+
return [
|
|
468
|
+
{
|
|
469
|
+
key: "linear",
|
|
470
|
+
webhook: async (req, webCtx) => {
|
|
471
|
+
const rawBody = await req.text();
|
|
472
|
+
const parsed = parseBody(rawBody);
|
|
473
|
+
const secret = ctx.env(cfg.webhookSecretEnv);
|
|
474
|
+
if (!secret) return json({ error: "missing_secret", env: cfg.webhookSecretEnv }, 500);
|
|
475
|
+
const verifyArgs = {
|
|
476
|
+
rawBody,
|
|
477
|
+
secret,
|
|
478
|
+
signature: req.headers.get("linear-signature")
|
|
479
|
+
};
|
|
480
|
+
const timestamp = timestampFrom(parsed);
|
|
481
|
+
if (!verifyLinearWebhook({
|
|
482
|
+
...verifyArgs,
|
|
483
|
+
...timestamp !== void 0 ? { timestamp } : {}
|
|
484
|
+
})) {
|
|
485
|
+
return json({ error: "invalid_signature" }, 401);
|
|
486
|
+
}
|
|
487
|
+
const normalized = normalizeLinearEvent(parsed, cfg);
|
|
488
|
+
if (!normalized) return json({ ok: true, skipped: true });
|
|
489
|
+
const agent = webCtx.resolveAgent(cfg.agent);
|
|
490
|
+
const runId = `linear-${hash(deliverySeed(parsed, rawBody))}`;
|
|
491
|
+
const result = await webCtx.enqueueRun({
|
|
492
|
+
agentName: agent.name,
|
|
493
|
+
agentVersion: agent.version,
|
|
494
|
+
userId: cfg.userId ?? "cap-linear",
|
|
495
|
+
runId,
|
|
496
|
+
initialContent: [{ type: "text", text: normalized.summary }],
|
|
497
|
+
metadata: { connector: "cap-linear", ...normalized }
|
|
498
|
+
});
|
|
499
|
+
return json(result, result.status === "enqueued" ? 202 : 200);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
];
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
var src_default = pack;
|
|
506
|
+
function readConfig(raw) {
|
|
507
|
+
const cfg = {
|
|
508
|
+
webhookSecretEnv: stringValue2(raw.webhookSecretEnv) ?? DEFAULT_WEBHOOK_SECRET_ENV,
|
|
509
|
+
apiKeyEnv: stringValue2(raw.apiKeyEnv) ?? DEFAULT_API_KEY_ENV,
|
|
510
|
+
accessMode: raw.accessMode === "read_write" ? "read_write" : "read"
|
|
511
|
+
};
|
|
512
|
+
const agent = stringValue2(raw.agent);
|
|
513
|
+
if (agent) cfg.agent = agent;
|
|
514
|
+
const userId = stringValue2(raw.userId);
|
|
515
|
+
if (userId) cfg.userId = userId;
|
|
516
|
+
const allowedTeams = stringArray(raw.allowedTeams);
|
|
517
|
+
if (allowedTeams) cfg.allowedTeams = allowedTeams;
|
|
518
|
+
const allowedProjects = stringArray(raw.allowedProjects);
|
|
519
|
+
if (allowedProjects) cfg.allowedProjects = allowedProjects;
|
|
520
|
+
const states = stringArray(raw.states);
|
|
521
|
+
if (states) cfg.states = states;
|
|
522
|
+
const labels = stringArray(raw.labels);
|
|
523
|
+
if (labels) cfg.labels = labels;
|
|
524
|
+
const ignoredActors = stringArray(raw.ignoredActors);
|
|
525
|
+
if (ignoredActors) cfg.ignoredActors = ignoredActors;
|
|
526
|
+
return cfg;
|
|
527
|
+
}
|
|
528
|
+
function parseBody(rawBody) {
|
|
529
|
+
try {
|
|
530
|
+
return JSON.parse(rawBody);
|
|
531
|
+
} catch {
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function timestampFrom(value) {
|
|
536
|
+
if (!value || typeof value !== "object") return void 0;
|
|
537
|
+
const found = value.webhookTimestamp;
|
|
538
|
+
return typeof found === "number" ? found : void 0;
|
|
539
|
+
}
|
|
540
|
+
function deliverySeed(value, rawBody) {
|
|
541
|
+
if (!value || typeof value !== "object") return rawBody;
|
|
542
|
+
const payload = value;
|
|
543
|
+
return [
|
|
544
|
+
stringValue2(payload.organizationId),
|
|
545
|
+
stringValue2(payload.type),
|
|
546
|
+
stringValue2(payload.action),
|
|
547
|
+
stringAt2(payload, "data.id"),
|
|
548
|
+
stringAt2(payload, "data.updatedAt"),
|
|
549
|
+
stringAt2(payload, "data.createdAt")
|
|
550
|
+
].filter(Boolean).join(":");
|
|
551
|
+
}
|
|
552
|
+
function stringAt2(value, path) {
|
|
553
|
+
let cursor = value;
|
|
554
|
+
for (const part of path.split(".")) {
|
|
555
|
+
if (!cursor || typeof cursor !== "object") return void 0;
|
|
556
|
+
cursor = cursor[part];
|
|
557
|
+
}
|
|
558
|
+
return stringValue2(cursor);
|
|
559
|
+
}
|
|
560
|
+
function hash(value) {
|
|
561
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 32);
|
|
562
|
+
}
|
|
563
|
+
function json(body, status = 200) {
|
|
564
|
+
return Response.json(body, { status });
|
|
565
|
+
}
|
|
566
|
+
function stringValue2(value) {
|
|
567
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
568
|
+
}
|
|
569
|
+
function stringArray(value) {
|
|
570
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : void 0;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export { src_default as default };
|
|
574
|
+
//# sourceMappingURL=index.js.map
|
|
575
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/normalize.ts","../src/tools.ts","../src/verify.ts","../src/index.ts"],"names":["stringValue","stringAt"],"mappings":"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","file":"index.js","sourcesContent":["export interface LinearFilterConfig {\n allowedTeams?: string[];\n allowedProjects?: string[];\n states?: string[];\n labels?: string[];\n ignoredActors?: string[];\n}\n\nexport interface NormalizedLinearEvent {\n type: string;\n action?: string;\n organizationId?: string;\n teamId?: string;\n teamKey?: string;\n projectId?: string;\n issueId?: string;\n issueIdentifier?: string;\n state?: string;\n actorId?: string;\n url?: string;\n summary: string;\n}\n\nexport function normalizeLinearEvent(\n body: unknown,\n cfg: LinearFilterConfig = {},\n): NormalizedLinearEvent | null {\n if (!body || typeof body !== \"object\") return null;\n const payload = body as Record<string, unknown>;\n const type = stringValue(payload.type);\n if (!type) return null;\n const action = stringValue(payload.action);\n const data = objectValue(payload.data);\n if (!data) return null;\n\n const actorId = stringAt(payload, \"actor.id\") ?? stringAt(payload, \"actorId\");\n if (actorId && cfg.ignoredActors?.includes(actorId)) return null;\n const teamId = stringAt(data, \"team.id\") ?? stringAt(data, \"teamId\");\n const teamKey = stringAt(data, \"team.key\");\n if (cfg.allowedTeams?.length && !matchesAny([teamId, teamKey], cfg.allowedTeams)) return null;\n const projectId = stringAt(data, \"project.id\") ?? stringAt(data, \"projectId\");\n const projectName = stringAt(data, \"project.name\");\n if (cfg.allowedProjects?.length && !matchesAny([projectId, projectName], cfg.allowedProjects)) {\n return null;\n }\n const state = stringAt(data, \"state.name\") ?? stringAt(data, \"state\");\n if (cfg.states?.length && state && !cfg.states.includes(state)) return null;\n if (cfg.labels?.length && !hasAllowedLabel(data, cfg.labels)) return null;\n\n const issueId = stringValue(data.id);\n const issueIdentifier = stringValue(data.identifier);\n const organizationId = stringValue(payload.organizationId);\n const url = stringValue(data.url);\n return {\n type,\n ...(action ? { action } : {}),\n ...(organizationId ? { organizationId } : {}),\n ...(teamId ? { teamId } : {}),\n ...(teamKey ? { teamKey } : {}),\n ...(projectId ? { projectId } : {}),\n ...(issueId ? { issueId } : {}),\n ...(issueIdentifier ? { issueIdentifier } : {}),\n ...(state ? { state } : {}),\n ...(actorId ? { actorId } : {}),\n ...(url ? { url } : {}),\n summary: `Linear ${type}${issueIdentifier ? ` ${issueIdentifier}` : \"\"} ${action ?? \"changed\"}`,\n };\n}\n\nfunction hasAllowedLabel(data: Record<string, unknown>, allowed: string[]): boolean {\n const labels = arrayValue(data.labels)\n .map((label) =>\n label && typeof label === \"object\" ? stringValue((label as { name?: unknown }).name) : null,\n )\n .filter((label): label is string => !!label);\n return labels.some((label) => allowed.includes(label));\n}\n\nfunction matchesAny(values: Array<string | undefined>, allowed: string[]): boolean {\n return values.some((value) => value !== undefined && allowed.includes(value));\n}\n\nfunction stringAt(value: unknown, path: string): string | undefined {\n let cursor = value;\n for (const part of path.split(\".\")) {\n if (!cursor || typeof cursor !== \"object\") return undefined;\n cursor = (cursor as Record<string, unknown>)[part];\n }\n return stringValue(cursor);\n}\n\nfunction objectValue(value: unknown): Record<string, unknown> | null {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction arrayValue(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n","import { LinearClient } from \"@linear/sdk\";\nimport type { LocalToolHandler } from \"@render-harness/core\";\n\nexport type LinearAccessMode = \"read\" | \"read_write\";\n\nexport function linearTools(args: {\n apiKey: string;\n accessMode: LinearAccessMode;\n}): LocalToolHandler[] {\n const client = new LinearClient({ apiKey: args.apiKey });\n const tools: LocalToolHandler[] = [\n jsonTool(\n \"linear.get_issue\",\n \"Read a Linear issue by id.\",\n idSchema(\"issueId\"),\n async (input) => {\n const { issueId } = input as { issueId: string };\n return client.client.request(\n `query Issue($id: String!) {\n issue(id: $id) {\n id\n identifier\n title\n description\n url\n state { id name }\n team { id key name }\n project { id name }\n assignee { id name email }\n labels { nodes { id name } }\n }\n }`,\n { id: issueId },\n );\n },\n ),\n jsonTool(\n \"linear.search_issues\",\n \"Search Linear issues with a text query.\",\n objectSchema({ query: { type: \"string\" } }),\n async (input) => {\n const { query } = input as { query: string };\n return client.client.request(\n `query SearchIssues($query: String!) {\n issueSearch(query: $query) {\n nodes {\n id\n identifier\n title\n url\n state { name }\n team { key name }\n }\n }\n }`,\n { query },\n );\n },\n ),\n jsonTool(\n \"linear.list_comments\",\n \"List comments for a Linear issue.\",\n idSchema(\"issueId\"),\n async (input) => {\n const { issueId } = input as { issueId: string };\n return client.client.request(\n `query IssueComments($id: String!) {\n issue(id: $id) {\n comments {\n nodes {\n id\n body\n createdAt\n user { id name email }\n }\n }\n }\n }`,\n { id: issueId },\n );\n },\n ),\n jsonTool(\"linear.list_teams\", \"List Linear teams.\", emptySchema(), async () =>\n client.client.request(\n `query Teams {\n teams {\n nodes {\n id\n key\n name\n }\n }\n }`,\n ),\n ),\n jsonTool(\"linear.list_projects\", \"List Linear projects.\", emptySchema(), async () =>\n client.client.request(\n `query Projects {\n projects {\n nodes {\n id\n name\n url\n state\n teams { nodes { id key name } }\n }\n }\n }`,\n ),\n ),\n jsonTool(\n \"linear.list_workflow_states\",\n \"List Linear workflow states, optionally scoped to a team.\",\n objectSchema({ teamId: { type: \"string\", optional: true } }),\n async (input) => {\n const { teamId } = input as { teamId?: string };\n return client.client.request(\n `query WorkflowStates($filter: WorkflowStateFilter) {\n workflowStates(filter: $filter) {\n nodes {\n id\n name\n type\n team { id key name }\n }\n }\n }`,\n { filter: teamId ? { team: { id: { eq: teamId } } } : undefined },\n );\n },\n ),\n jsonTool(\"linear.list_users\", \"List Linear users.\", emptySchema(), async () =>\n client.client.request(\n `query Users {\n users {\n nodes {\n id\n name\n email\n active\n }\n }\n }`,\n ),\n ),\n ];\n\n if (args.accessMode === \"read_write\") {\n tools.push(\n jsonTool(\n \"linear.create_issue\",\n \"Create a Linear issue.\",\n createIssueSchema(),\n async (input) => {\n const { teamId, title, description, assigneeId, projectId, cycleId, stateId, priority } =\n input as CreateIssueInput;\n return client.client.request(\n `mutation IssueCreate($input: IssueCreateInput!) {\n issueCreate(input: $input) {\n success\n issue { id identifier title url }\n }\n }`,\n {\n input: {\n teamId,\n title,\n ...(description ? { description } : {}),\n ...(assigneeId ? { assigneeId } : {}),\n ...(projectId ? { projectId } : {}),\n ...(cycleId ? { cycleId } : {}),\n ...(stateId ? { stateId } : {}),\n ...(priority !== undefined ? { priority } : {}),\n },\n },\n );\n },\n ),\n jsonTool(\n \"linear.create_comment\",\n \"Create a comment on a Linear issue.\",\n objectSchema({ issueId: { type: \"string\" }, body: { type: \"string\" } }),\n async (input) => {\n const { issueId, body } = input as { issueId: string; body: string };\n return client.client.request(\n `mutation CommentCreate($input: CommentCreateInput!) {\n commentCreate(input: $input) {\n success\n comment { id url body }\n }\n }`,\n { input: { issueId, body } },\n );\n },\n ),\n jsonTool(\n \"linear.update_issue\",\n \"Update Linear issue fields.\",\n updateIssueSchema(),\n async (input) => {\n const {\n issueId,\n title,\n description,\n stateId,\n assigneeId,\n projectId,\n cycleId,\n priority,\n labelIds,\n } = input as UpdateIssueInput;\n return client.client.request(\n `mutation IssueUpdate($id: String!, $input: IssueUpdateInput!) {\n issueUpdate(id: $id, input: $input) {\n success\n issue {\n id\n identifier\n title\n state { id name }\n assignee { id name }\n project { id name }\n cycle { id name }\n }\n }\n }`,\n {\n id: issueId,\n input: {\n ...(title ? { title } : {}),\n ...(description ? { description } : {}),\n ...(stateId ? { stateId } : {}),\n ...(assigneeId ? { assigneeId } : {}),\n ...(projectId ? { projectId } : {}),\n ...(cycleId ? { cycleId } : {}),\n ...(priority !== undefined ? { priority } : {}),\n ...(labelIds ? { labelIds } : {}),\n },\n },\n );\n },\n ),\n jsonTool(\n \"linear.update_issue_status\",\n \"Update a Linear issue's status.\",\n objectSchema({ issueId: { type: \"string\" }, stateId: { type: \"string\" } }),\n async (input) => {\n const { issueId, stateId } = input as { issueId: string; stateId: string };\n return client.client.request(\n `mutation IssueUpdate($id: String!, $input: IssueUpdateInput!) {\n issueUpdate(id: $id, input: $input) {\n success\n issue { id identifier state { id name } }\n }\n }`,\n { id: issueId, input: { stateId } },\n );\n },\n ),\n jsonTool(\n \"linear.assign_issue\",\n \"Assign a Linear issue to a user.\",\n objectSchema({ issueId: { type: \"string\" }, assigneeId: { type: \"string\" } }),\n async (input) => {\n const { issueId, assigneeId } = input as { issueId: string; assigneeId: string };\n return client.client.request(\n `mutation IssueAssign($id: String!, $input: IssueUpdateInput!) {\n issueUpdate(id: $id, input: $input) {\n success\n issue { id identifier assignee { id name } }\n }\n }`,\n { id: issueId, input: { assigneeId } },\n );\n },\n ),\n jsonTool(\n \"linear.link_related_issue\",\n \"Create a Linear issue relation between two issues.\",\n objectSchema({\n issueId: { type: \"string\" },\n relatedIssueId: { type: \"string\" },\n type: { type: \"string\" },\n }),\n async (input) => {\n const { issueId, relatedIssueId, type } = input as {\n issueId: string;\n relatedIssueId: string;\n type: string;\n };\n return client.client.request(\n `mutation IssueRelationCreate($input: IssueRelationCreateInput!) {\n issueRelationCreate(input: $input) {\n success\n issueRelation { id type }\n }\n }`,\n { input: { issueId, relatedIssueId, type } },\n );\n },\n ),\n );\n }\n\n return tools;\n}\n\nfunction jsonTool(\n name: string,\n description: string,\n inputSchema: Record<string, unknown>,\n call: (input: unknown) => Promise<unknown>,\n): LocalToolHandler {\n return {\n definition: { name, description, inputSchema, source: \"pack:cap-linear\" },\n handler: async ({ input }) => {\n try {\n return { content: JSON.stringify(await call(input), null, 2) };\n } catch (err) {\n return { content: err instanceof Error ? err.message : String(err), isError: true };\n }\n },\n };\n}\n\ninterface CreateIssueInput {\n teamId: string;\n title: string;\n description?: string;\n assigneeId?: string;\n projectId?: string;\n cycleId?: string;\n stateId?: string;\n priority?: number;\n}\n\ninterface UpdateIssueInput {\n issueId: string;\n title?: string;\n description?: string;\n stateId?: string;\n assigneeId?: string;\n projectId?: string;\n cycleId?: string;\n priority?: number;\n labelIds?: string[];\n}\n\nfunction emptySchema() {\n return objectSchema({});\n}\n\nfunction idSchema(name: string) {\n return objectSchema({ [name]: { type: \"string\" } });\n}\n\nfunction createIssueSchema() {\n return objectSchema({\n teamId: { type: \"string\" },\n title: { type: \"string\" },\n description: { type: \"string\", optional: true },\n assigneeId: { type: \"string\", optional: true },\n projectId: { type: \"string\", optional: true },\n cycleId: { type: \"string\", optional: true },\n stateId: { type: \"string\", optional: true },\n priority: { type: \"number\", optional: true },\n });\n}\n\nfunction updateIssueSchema() {\n return objectSchema({\n issueId: { type: \"string\" },\n title: { type: \"string\", optional: true },\n description: { type: \"string\", optional: true },\n stateId: { type: \"string\", optional: true },\n assigneeId: { type: \"string\", optional: true },\n projectId: { type: \"string\", optional: true },\n cycleId: { type: \"string\", optional: true },\n priority: { type: \"number\", optional: true },\n labelIds: { type: \"array\", items: { type: \"string\" }, optional: true },\n });\n}\n\nfunction objectSchema(properties: Record<string, unknown>) {\n return {\n type: \"object\",\n additionalProperties: false,\n properties,\n required: Object.entries(properties)\n .filter(([, value]) => !(value as { optional?: boolean }).optional)\n .map(([key]) => key),\n };\n}\n","import { LinearWebhookClient } from \"@linear/sdk/webhooks\";\n\nexport function verifyLinearWebhook(args: {\n rawBody: string;\n signature: string | null;\n secret: string;\n timestamp?: number;\n}): boolean {\n if (!args.signature) return false;\n try {\n return new LinearWebhookClient(args.secret).verify(\n Buffer.from(args.rawBody),\n args.signature,\n args.timestamp,\n );\n } catch {\n return false;\n }\n}\n","import { createHash } from \"node:crypto\";\nimport type { LocalToolHandler } from \"@render-harness/core\";\nimport { type ConnectorContribution, definePack, type PackContext } from \"@render-harness/registry\";\nimport { type LinearFilterConfig, normalizeLinearEvent } from \"./normalize.js\";\nimport { type LinearAccessMode, linearTools } from \"./tools.js\";\nimport { verifyLinearWebhook } from \"./verify.js\";\n\ninterface LinearConfig extends LinearFilterConfig {\n agent?: string;\n userId?: string;\n webhookSecretEnv?: string;\n apiKeyEnv?: string;\n accessMode?: LinearAccessMode;\n}\n\nconst DEFAULT_WEBHOOK_SECRET_ENV = \"LINEAR_WEBHOOK_SECRET\";\nconst DEFAULT_API_KEY_ENV = \"LINEAR_API_KEY\";\n\nconst pack = definePack({\n name: \"cap-linear\",\n version: \"0.1.0\",\n envSchema: [\n {\n name: DEFAULT_WEBHOOK_SECRET_ENV,\n required: true,\n secret: true,\n description: \"Linear webhook signing secret.\",\n },\n {\n name: DEFAULT_API_KEY_ENV,\n required: true,\n secret: true,\n description: \"Linear API key used for read tools and optional write tools.\",\n },\n ],\n localTools(ctx: PackContext): LocalToolHandler[] {\n const cfg = readConfig(ctx.config);\n const apiKey = ctx.env(cfg.apiKeyEnv);\n if (!apiKey) throw new Error(`cap-linear: ${cfg.apiKeyEnv} is not set`);\n return linearTools({ apiKey, accessMode: cfg.accessMode });\n },\n connectors(ctx: PackContext): ConnectorContribution[] {\n const cfg = readConfig(ctx.config);\n return [\n {\n key: \"linear\",\n webhook: async (req, webCtx) => {\n const rawBody = await req.text();\n const parsed = parseBody(rawBody);\n const secret = ctx.env(cfg.webhookSecretEnv);\n if (!secret) return json({ error: \"missing_secret\", env: cfg.webhookSecretEnv }, 500);\n const verifyArgs = {\n rawBody,\n secret,\n signature: req.headers.get(\"linear-signature\"),\n };\n const timestamp = timestampFrom(parsed);\n if (\n !verifyLinearWebhook({\n ...verifyArgs,\n ...(timestamp !== undefined ? { timestamp } : {}),\n })\n ) {\n return json({ error: \"invalid_signature\" }, 401);\n }\n\n const normalized = normalizeLinearEvent(parsed, cfg);\n if (!normalized) return json({ ok: true, skipped: true });\n const agent = webCtx.resolveAgent(cfg.agent);\n const runId = `linear-${hash(deliverySeed(parsed, rawBody))}`;\n const result = await webCtx.enqueueRun({\n agentName: agent.name,\n agentVersion: agent.version,\n userId: cfg.userId ?? \"cap-linear\",\n runId,\n initialContent: [{ type: \"text\", text: normalized.summary }],\n metadata: { connector: \"cap-linear\", ...normalized },\n });\n return json(result, result.status === \"enqueued\" ? 202 : 200);\n },\n },\n ];\n },\n});\n\nexport default pack;\n\ntype ResolvedLinearConfig = Required<\n Pick<LinearConfig, \"webhookSecretEnv\" | \"apiKeyEnv\" | \"accessMode\">\n> &\n Omit<LinearConfig, \"webhookSecretEnv\" | \"apiKeyEnv\" | \"accessMode\">;\n\nfunction readConfig(raw: Record<string, unknown>): ResolvedLinearConfig {\n const cfg: ResolvedLinearConfig = {\n webhookSecretEnv: stringValue(raw.webhookSecretEnv) ?? DEFAULT_WEBHOOK_SECRET_ENV,\n apiKeyEnv: stringValue(raw.apiKeyEnv) ?? DEFAULT_API_KEY_ENV,\n accessMode: raw.accessMode === \"read_write\" ? \"read_write\" : \"read\",\n };\n const agent = stringValue(raw.agent);\n if (agent) cfg.agent = agent;\n const userId = stringValue(raw.userId);\n if (userId) cfg.userId = userId;\n const allowedTeams = stringArray(raw.allowedTeams);\n if (allowedTeams) cfg.allowedTeams = allowedTeams;\n const allowedProjects = stringArray(raw.allowedProjects);\n if (allowedProjects) cfg.allowedProjects = allowedProjects;\n const states = stringArray(raw.states);\n if (states) cfg.states = states;\n const labels = stringArray(raw.labels);\n if (labels) cfg.labels = labels;\n const ignoredActors = stringArray(raw.ignoredActors);\n if (ignoredActors) cfg.ignoredActors = ignoredActors;\n return cfg;\n}\n\nfunction parseBody(rawBody: string): unknown {\n try {\n return JSON.parse(rawBody);\n } catch {\n return null;\n }\n}\n\nfunction timestampFrom(value: unknown): number | undefined {\n if (!value || typeof value !== \"object\") return undefined;\n const found = (value as Record<string, unknown>).webhookTimestamp;\n return typeof found === \"number\" ? found : undefined;\n}\n\nfunction deliverySeed(value: unknown, rawBody: string): string {\n if (!value || typeof value !== \"object\") return rawBody;\n const payload = value as Record<string, unknown>;\n return [\n stringValue(payload.organizationId),\n stringValue(payload.type),\n stringValue(payload.action),\n stringAt(payload, \"data.id\"),\n stringAt(payload, \"data.updatedAt\"),\n stringAt(payload, \"data.createdAt\"),\n ]\n .filter(Boolean)\n .join(\":\");\n}\n\nfunction stringAt(value: unknown, path: string): string | undefined {\n let cursor = value;\n for (const part of path.split(\".\")) {\n if (!cursor || typeof cursor !== \"object\") return undefined;\n cursor = (cursor as Record<string, unknown>)[part];\n }\n return stringValue(cursor);\n}\n\nfunction hash(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 32);\n}\n\nfunction json(body: unknown, status = 200): Response {\n return Response.json(body, { status });\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction stringArray(value: unknown): string[] | undefined {\n return Array.isArray(value)\n ? value.filter((item): item is string => typeof item === \"string\")\n : undefined;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@render-harness/cap-linear",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Linear monitoring capability pack for the Render agent harness.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"render-harness-cap",
|
|
20
|
+
"render-harness",
|
|
21
|
+
"linear"
|
|
22
|
+
],
|
|
23
|
+
"renderHarness": {
|
|
24
|
+
"gallery": {
|
|
25
|
+
"label": "Linear",
|
|
26
|
+
"envHint": "LINEAR_API_KEY"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@linear/sdk": "^63.4.0",
|
|
31
|
+
"@render-harness/registry": "0.2.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^25.6.2",
|
|
35
|
+
"tsup": "^8.5.1",
|
|
36
|
+
"typescript": "^6.0.3",
|
|
37
|
+
"vitest": "^4.1.5",
|
|
38
|
+
"@render-harness/core": "0.2.0"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run --passWithNoTests"
|
|
47
|
+
}
|
|
48
|
+
}
|