openclaw-linear 0.4.0 → 0.5.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/README.md +100 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +80 -3
- package/dist/index.js.map +1 -1
- package/dist/linear-api.d.ts +13 -0
- package/dist/linear-api.d.ts.map +1 -0
- package/dist/linear-api.js +120 -0
- package/dist/linear-api.js.map +1 -0
- package/dist/tools/linear-comment-tool.d.ts +3 -0
- package/dist/tools/linear-comment-tool.d.ts.map +1 -0
- package/dist/tools/linear-comment-tool.js +113 -0
- package/dist/tools/linear-comment-tool.js.map +1 -0
- package/dist/tools/linear-issue-tool.d.ts +3 -0
- package/dist/tools/linear-issue-tool.d.ts.map +1 -0
- package/dist/tools/linear-issue-tool.js +240 -0
- package/dist/tools/linear-issue-tool.js.map +1 -0
- package/dist/tools/linear-project-tool.d.ts +3 -0
- package/dist/tools/linear-project-tool.d.ts.map +1 -0
- package/dist/tools/linear-project-tool.js +126 -0
- package/dist/tools/linear-project-tool.js.map +1 -0
- package/dist/tools/linear-relation-tool.d.ts +3 -0
- package/dist/tools/linear-relation-tool.d.ts.map +1 -0
- package/dist/tools/linear-relation-tool.js +148 -0
- package/dist/tools/linear-relation-tool.js.map +1 -0
- package/dist/tools/linear-team-tool.d.ts +3 -0
- package/dist/tools/linear-team-tool.d.ts.map +1 -0
- package/dist/tools/linear-team-tool.js +64 -0
- package/dist/tools/linear-team-tool.js.map +1 -0
- package/dist/{queue-tool.d.ts → tools/queue-tool.d.ts} +1 -1
- package/dist/tools/queue-tool.d.ts.map +1 -0
- package/dist/{queue-tool.js → tools/queue-tool.js} +19 -4
- package/dist/tools/queue-tool.js.map +1 -0
- package/dist/work-queue.d.ts +8 -3
- package/dist/work-queue.d.ts.map +1 -1
- package/dist/work-queue.js +60 -14
- package/dist/work-queue.js.map +1 -1
- package/openclaw.plugin.json +6 -1
- package/package.json +2 -2
- package/skills/linear/SKILL.md +126 -0
- package/dist/queue-tool.d.ts.map +0 -1
- package/dist/queue-tool.js.map +0 -1
- package/skills/linear-queue/SKILL.md +0 -47
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-issue-tool.d.ts","sourceRoot":"","sources":["../../src/tools/linear-issue-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA0ExD,wBAAgB,eAAe,IAAI,YAAY,CAgC9C"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { jsonResult } from "openclaw/plugin-sdk";
|
|
3
|
+
import { graphql, resolveIssueId, resolveTeamId, resolveStateId, resolveUserId, resolveLabelIds, resolveProjectId, } from "../linear-api.js";
|
|
4
|
+
const Params = Type.Object({
|
|
5
|
+
action: Type.Unsafe({
|
|
6
|
+
type: "string",
|
|
7
|
+
enum: ["view", "list", "create", "update", "delete"],
|
|
8
|
+
description: "view: get full issue details. " +
|
|
9
|
+
"list: search/filter issues. " +
|
|
10
|
+
"create: create a new issue. " +
|
|
11
|
+
"update: modify an existing issue. " +
|
|
12
|
+
"delete: delete an issue.",
|
|
13
|
+
}),
|
|
14
|
+
issueId: Type.Optional(Type.String({
|
|
15
|
+
description: "Issue identifier (e.g. ENG-123). Required for view, update, delete.",
|
|
16
|
+
})),
|
|
17
|
+
title: Type.Optional(Type.String({ description: "Issue title (required for create)." })),
|
|
18
|
+
description: Type.Optional(Type.String({ description: "Issue description (markdown)." })),
|
|
19
|
+
assignee: Type.Optional(Type.String({ description: "Assignee display name or email." })),
|
|
20
|
+
state: Type.Optional(Type.String({
|
|
21
|
+
description: "Workflow state name (e.g. In Progress, Done).",
|
|
22
|
+
})),
|
|
23
|
+
priority: Type.Optional(Type.Number({
|
|
24
|
+
description: "Priority (0=None, 1=Urgent, 2=High, 3=Medium, 4=Low).",
|
|
25
|
+
})),
|
|
26
|
+
team: Type.Optional(Type.String({
|
|
27
|
+
description: "Team key (e.g. ENG). Required for create if you belong to multiple teams. Used as filter for list.",
|
|
28
|
+
})),
|
|
29
|
+
project: Type.Optional(Type.String({ description: "Project name." })),
|
|
30
|
+
parent: Type.Optional(Type.String({
|
|
31
|
+
description: "Parent issue identifier for sub-issues (e.g. ENG-100). Used with create.",
|
|
32
|
+
})),
|
|
33
|
+
labels: Type.Optional(Type.Array(Type.String(), { description: "Label names." })),
|
|
34
|
+
limit: Type.Optional(Type.Number({
|
|
35
|
+
description: "Max results for list (default 50).",
|
|
36
|
+
})),
|
|
37
|
+
});
|
|
38
|
+
export function createIssueTool() {
|
|
39
|
+
return {
|
|
40
|
+
name: "linear_issue",
|
|
41
|
+
label: "Linear Issue",
|
|
42
|
+
description: "Manage Linear issues. Actions: view, list, create, update, delete.",
|
|
43
|
+
parameters: Params,
|
|
44
|
+
async execute(_toolCallId, params) {
|
|
45
|
+
try {
|
|
46
|
+
switch (params.action) {
|
|
47
|
+
case "view":
|
|
48
|
+
return await viewIssue(params);
|
|
49
|
+
case "list":
|
|
50
|
+
return await listIssues(params);
|
|
51
|
+
case "create":
|
|
52
|
+
return await createIssue(params);
|
|
53
|
+
case "update":
|
|
54
|
+
return await updateIssue(params);
|
|
55
|
+
case "delete":
|
|
56
|
+
return await deleteIssue(params);
|
|
57
|
+
default:
|
|
58
|
+
return jsonResult({
|
|
59
|
+
error: `Unknown action: ${params.action}`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
return jsonResult({
|
|
65
|
+
error: `linear_issue error: ${err instanceof Error ? err.message : String(err)}`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function viewIssue(params) {
|
|
72
|
+
if (!params.issueId) {
|
|
73
|
+
return jsonResult({ error: "issueId is required for view" });
|
|
74
|
+
}
|
|
75
|
+
const id = await resolveIssueId(params.issueId);
|
|
76
|
+
const data = await graphql(`query($id: String!) {
|
|
77
|
+
issue(id: $id) {
|
|
78
|
+
id
|
|
79
|
+
identifier
|
|
80
|
+
title
|
|
81
|
+
description
|
|
82
|
+
url
|
|
83
|
+
priority
|
|
84
|
+
priorityLabel
|
|
85
|
+
estimate
|
|
86
|
+
createdAt
|
|
87
|
+
updatedAt
|
|
88
|
+
state { id name type }
|
|
89
|
+
assignee { id name email }
|
|
90
|
+
team { id name key }
|
|
91
|
+
project { id name }
|
|
92
|
+
parent { id identifier title }
|
|
93
|
+
labels { nodes { id name } }
|
|
94
|
+
children { nodes { id identifier title state { name } } }
|
|
95
|
+
}
|
|
96
|
+
}`, { id });
|
|
97
|
+
return jsonResult(data.issue);
|
|
98
|
+
}
|
|
99
|
+
async function listIssues(params) {
|
|
100
|
+
const filterParts = [];
|
|
101
|
+
const variables = {};
|
|
102
|
+
if (params.state) {
|
|
103
|
+
filterParts.push("state: { name: { eqIgnoreCase: $state } }");
|
|
104
|
+
variables.state = params.state;
|
|
105
|
+
}
|
|
106
|
+
if (params.assignee) {
|
|
107
|
+
filterParts.push("assignee: { or: [{ name: { eqIgnoreCase: $assignee } }, { email: { eq: $assignee } }] }");
|
|
108
|
+
variables.assignee = params.assignee;
|
|
109
|
+
}
|
|
110
|
+
if (params.team) {
|
|
111
|
+
filterParts.push("team: { key: { eq: $team } }");
|
|
112
|
+
variables.team = params.team.toUpperCase();
|
|
113
|
+
}
|
|
114
|
+
if (params.project) {
|
|
115
|
+
filterParts.push("project: { name: { eqIgnoreCase: $project } }");
|
|
116
|
+
variables.project = params.project;
|
|
117
|
+
}
|
|
118
|
+
const limit = params.limit ?? 50;
|
|
119
|
+
variables.first = limit;
|
|
120
|
+
const filterStr = filterParts.length
|
|
121
|
+
? `filter: { ${filterParts.join(", ")} }, `
|
|
122
|
+
: "";
|
|
123
|
+
// Build variable declarations
|
|
124
|
+
const varDecls = ["$first: Int!"];
|
|
125
|
+
if (params.state)
|
|
126
|
+
varDecls.push("$state: String!");
|
|
127
|
+
if (params.assignee)
|
|
128
|
+
varDecls.push("$assignee: String!");
|
|
129
|
+
if (params.team)
|
|
130
|
+
varDecls.push("$team: String!");
|
|
131
|
+
if (params.project)
|
|
132
|
+
varDecls.push("$project: String!");
|
|
133
|
+
const data = await graphql(`query(${varDecls.join(", ")}) {
|
|
134
|
+
issues(${filterStr}first: $first) {
|
|
135
|
+
nodes {
|
|
136
|
+
id
|
|
137
|
+
identifier
|
|
138
|
+
title
|
|
139
|
+
priority
|
|
140
|
+
priorityLabel
|
|
141
|
+
state { name type }
|
|
142
|
+
assignee { name }
|
|
143
|
+
team { key }
|
|
144
|
+
project { name }
|
|
145
|
+
labels { nodes { name } }
|
|
146
|
+
updatedAt
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}`, variables);
|
|
150
|
+
return jsonResult({ issues: data.issues.nodes });
|
|
151
|
+
}
|
|
152
|
+
async function createIssue(params) {
|
|
153
|
+
if (!params.title) {
|
|
154
|
+
return jsonResult({ error: "title is required for create" });
|
|
155
|
+
}
|
|
156
|
+
const input = { title: params.title };
|
|
157
|
+
if (params.team) {
|
|
158
|
+
input.teamId = await resolveTeamId(params.team);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// Need a team — fetch the first one
|
|
162
|
+
const teams = await graphql(`{ teams(first: 1) { nodes { id } } }`);
|
|
163
|
+
if (teams.teams.nodes.length === 0) {
|
|
164
|
+
return jsonResult({ error: "No teams found" });
|
|
165
|
+
}
|
|
166
|
+
input.teamId = teams.teams.nodes[0].id;
|
|
167
|
+
}
|
|
168
|
+
if (params.description)
|
|
169
|
+
input.description = params.description;
|
|
170
|
+
if (params.priority !== undefined)
|
|
171
|
+
input.priority = params.priority;
|
|
172
|
+
if (params.state) {
|
|
173
|
+
input.stateId = await resolveStateId(input.teamId, params.state);
|
|
174
|
+
}
|
|
175
|
+
if (params.assignee) {
|
|
176
|
+
input.assigneeId = await resolveUserId(params.assignee);
|
|
177
|
+
}
|
|
178
|
+
if (params.project) {
|
|
179
|
+
input.projectId = await resolveProjectId(params.project);
|
|
180
|
+
}
|
|
181
|
+
if (params.parent) {
|
|
182
|
+
input.parentId = await resolveIssueId(params.parent);
|
|
183
|
+
}
|
|
184
|
+
if (params.labels?.length) {
|
|
185
|
+
input.labelIds = await resolveLabelIds(input.teamId, params.labels);
|
|
186
|
+
}
|
|
187
|
+
const data = await graphql(`mutation($input: IssueCreateInput!) {
|
|
188
|
+
issueCreate(input: $input) {
|
|
189
|
+
success
|
|
190
|
+
issue { id identifier url title }
|
|
191
|
+
}
|
|
192
|
+
}`, { input });
|
|
193
|
+
return jsonResult(data.issueCreate);
|
|
194
|
+
}
|
|
195
|
+
async function updateIssue(params) {
|
|
196
|
+
if (!params.issueId) {
|
|
197
|
+
return jsonResult({ error: "issueId is required for update" });
|
|
198
|
+
}
|
|
199
|
+
const id = await resolveIssueId(params.issueId);
|
|
200
|
+
const input = {};
|
|
201
|
+
// We need the team ID for state/label resolution — fetch it from the issue
|
|
202
|
+
let teamId;
|
|
203
|
+
if (params.state || params.labels?.length) {
|
|
204
|
+
const issueData = await graphql(`query($id: String!) { issue(id: $id) { team { id } } }`, { id });
|
|
205
|
+
teamId = issueData.issue.team.id;
|
|
206
|
+
}
|
|
207
|
+
if (params.title)
|
|
208
|
+
input.title = params.title;
|
|
209
|
+
if (params.description !== undefined)
|
|
210
|
+
input.description = params.description;
|
|
211
|
+
if (params.priority !== undefined)
|
|
212
|
+
input.priority = params.priority;
|
|
213
|
+
if (params.state)
|
|
214
|
+
input.stateId = await resolveStateId(teamId, params.state);
|
|
215
|
+
if (params.assignee)
|
|
216
|
+
input.assigneeId = await resolveUserId(params.assignee);
|
|
217
|
+
if (params.project)
|
|
218
|
+
input.projectId = await resolveProjectId(params.project);
|
|
219
|
+
if (params.labels?.length) {
|
|
220
|
+
input.labelIds = await resolveLabelIds(teamId, params.labels);
|
|
221
|
+
}
|
|
222
|
+
const data = await graphql(`mutation($id: String!, $input: IssueUpdateInput!) {
|
|
223
|
+
issueUpdate(id: $id, input: $input) {
|
|
224
|
+
success
|
|
225
|
+
issue { id identifier title }
|
|
226
|
+
}
|
|
227
|
+
}`, { id, input });
|
|
228
|
+
return jsonResult(data.issueUpdate);
|
|
229
|
+
}
|
|
230
|
+
async function deleteIssue(params) {
|
|
231
|
+
if (!params.issueId) {
|
|
232
|
+
return jsonResult({ error: "issueId is required for delete" });
|
|
233
|
+
}
|
|
234
|
+
const id = await resolveIssueId(params.issueId);
|
|
235
|
+
const data = await graphql(`mutation($id: String!) {
|
|
236
|
+
issueDelete(id: $id) { success }
|
|
237
|
+
}`, { id });
|
|
238
|
+
return jsonResult({ success: data.issueDelete.success, issueId: params.issueId });
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=linear-issue-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-issue-tool.js","sourceRoot":"","sources":["../../src/tools/linear-issue-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAe,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACL,OAAO,EACP,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,eAAe,EACf,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACzB,MAAM,EAAE,IAAI,CAAC,MAAM,CAAmD;QACpE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;QACpD,WAAW,EACT,gCAAgC;YAChC,8BAA8B;YAC9B,8BAA8B;YAC9B,oCAAoC;YACpC,0BAA0B;KAC7B,CAAC;IACF,OAAO,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EACT,qEAAqE;KACxE,CAAC,CACH;IACD,KAAK,EAAE,IAAI,CAAC,QAAQ,CAClB,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,oCAAoC,EAAE,CAAC,CACnE;IACD,WAAW,EAAE,IAAI,CAAC,QAAQ,CACxB,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC,CAC9D;IACD,QAAQ,EAAE,IAAI,CAAC,QAAQ,CACrB,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,iCAAiC,EAAE,CAAC,CAChE;IACD,KAAK,EAAE,IAAI,CAAC,QAAQ,CAClB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EAAE,+CAA+C;KAC7D,CAAC,CACH;IACD,QAAQ,EAAE,IAAI,CAAC,QAAQ,CACrB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EAAE,uDAAuD;KACrE,CAAC,CACH;IACD,IAAI,EAAE,IAAI,CAAC,QAAQ,CACjB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EACT,oGAAoG;KACvG,CAAC,CACH;IACD,OAAO,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC,CAC9C;IACD,MAAM,EAAE,IAAI,CAAC,QAAQ,CACnB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EACT,0EAA0E;KAC7E,CAAC,CACH;IACD,MAAM,EAAE,IAAI,CAAC,QAAQ,CACnB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAC3D;IACD,KAAK,EAAE,IAAI,CAAC,QAAQ,CAClB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EAAE,oCAAoC;KAClD,CAAC,CACH;CACF,CAAC,CAAC;AAGH,MAAM,UAAU,eAAe;IAC7B,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,oEAAoE;QACtE,UAAU,EAAE,MAAM;QAClB,KAAK,CAAC,OAAO,CAAC,WAAmB,EAAE,MAAc;YAC/C,IAAI,CAAC;gBACH,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;oBACtB,KAAK,MAAM;wBACT,OAAO,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;oBACjC,KAAK,MAAM;wBACT,OAAO,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;oBAClC,KAAK,QAAQ;wBACX,OAAO,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;oBACnC,KAAK,QAAQ;wBACX,OAAO,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;oBACnC,KAAK,QAAQ;wBACX,OAAO,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;oBACnC;wBACE,OAAO,UAAU,CAAC;4BAChB,KAAK,EAAE,mBAAoB,MAA6B,CAAC,MAAM,EAAE;yBAClE,CAAC,CAAC;gBACP,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,UAAU,CAAC;oBAChB,KAAK,EAAE,uBAAuB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;iBACjF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,MAAc;IACrC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEhD,MAAM,IAAI,GAAG,MAAM,OAAO,CACxB;;;;;;;;;;;;;;;;;;;;MAoBE,EACF,EAAE,EAAE,EAAE,CACP,CAAC;IAEF,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAChC,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,MAAc;IACtC,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,SAAS,GAA4B,EAAE,CAAC;IAE9C,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,WAAW,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;QAC9D,SAAS,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IACjC,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,WAAW,CAAC,IAAI,CACd,yFAAyF,CAC1F,CAAC;QACF,SAAS,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACvC,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,WAAW,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QACjD,SAAS,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7C,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,WAAW,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;QAClE,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IACrC,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;IACjC,SAAS,CAAC,KAAK,GAAG,KAAK,CAAC;IAExB,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM;QAClC,CAAC,CAAC,aAAa,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;QAC3C,CAAC,CAAC,EAAE,CAAC;IAEP,8BAA8B;IAC9B,MAAM,QAAQ,GAAa,CAAC,cAAc,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,KAAK;QAAE,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACnD,IAAI,MAAM,CAAC,QAAQ;QAAE,QAAQ,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACzD,IAAI,MAAM,CAAC,IAAI;QAAE,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACjD,IAAI,MAAM,CAAC,OAAO;QAAE,QAAQ,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAEvD,MAAM,IAAI,GAAG,MAAM,OAAO,CAKxB,SAAS,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;eACjB,SAAS;;;;;;;;;;;;;;;MAelB,EACF,SAAS,CACV,CAAC;IAEF,OAAO,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,MAAc;IACvC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,KAAK,GAA4B,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;IAE/D,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;SAAM,CAAC;QACN,oCAAoC;QACpC,MAAM,KAAK,GAAG,MAAM,OAAO,CAExB,sCAAsC,CAAC,CAAC;QAC3C,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnC,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzC,CAAC;IAED,IAAI,MAAM,CAAC,WAAW;QAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;IAC/D,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS;QAAE,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IAEpE,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,KAAK,CAAC,OAAO,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,MAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7E,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,KAAK,CAAC,UAAU,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,KAAK,CAAC,SAAS,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,KAAK,CAAC,QAAQ,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAC1B,KAAK,CAAC,QAAQ,GAAG,MAAM,eAAe,CACpC,KAAK,CAAC,MAAgB,EACtB,MAAM,CAAC,MAAM,CACd,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,OAAO,CAMxB;;;;;MAKE,EACF,EAAE,KAAK,EAAE,CACV,CAAC;IAEF,OAAO,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,MAAc;IACvC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChD,MAAM,KAAK,GAA4B,EAAE,CAAC;IAE1C,2EAA2E;IAC3E,IAAI,MAA0B,CAAC;IAC/B,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAC1C,MAAM,SAAS,GAAG,MAAM,OAAO,CAG7B,wDAAwD,EACxD,EAAE,EAAE,EAAE,CACP,CAAC;QACF,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;IACnC,CAAC;IAED,IAAI,MAAM,CAAC,KAAK;QAAE,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC7C,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS;QAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;IAC7E,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS;QAAE,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACpE,IAAI,MAAM,CAAC,KAAK;QAAE,KAAK,CAAC,OAAO,GAAG,MAAM,cAAc,CAAC,MAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9E,IAAI,MAAM,CAAC,QAAQ;QAAE,KAAK,CAAC,UAAU,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC7E,IAAI,MAAM,CAAC,OAAO;QAAE,KAAK,CAAC,SAAS,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7E,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAC1B,KAAK,CAAC,QAAQ,GAAG,MAAM,eAAe,CAAC,MAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,OAAO,CAMxB;;;;;MAKE,EACF,EAAE,EAAE,EAAE,KAAK,EAAE,CACd,CAAC;IAEF,OAAO,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,MAAc;IACvC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEhD,MAAM,IAAI,GAAG,MAAM,OAAO,CAGxB;;MAEE,EACF,EAAE,EAAE,EAAE,CACP,CAAC;IAEF,OAAO,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;AACpF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-project-tool.d.ts","sourceRoot":"","sources":["../../src/tools/linear-project-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA0CxD,wBAAgB,iBAAiB,IAAI,YAAY,CA4BhD"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { jsonResult } from "openclaw/plugin-sdk";
|
|
3
|
+
import { graphql, resolveTeamId } from "../linear-api.js";
|
|
4
|
+
const Params = Type.Object({
|
|
5
|
+
action: Type.Unsafe({
|
|
6
|
+
type: "string",
|
|
7
|
+
enum: ["list", "view", "create"],
|
|
8
|
+
description: "list: search/filter projects. " +
|
|
9
|
+
"view: get full project details. " +
|
|
10
|
+
"create: create a new project.",
|
|
11
|
+
}),
|
|
12
|
+
projectId: Type.Optional(Type.String({
|
|
13
|
+
description: "Project ID. Required for view.",
|
|
14
|
+
})),
|
|
15
|
+
name: Type.Optional(Type.String({
|
|
16
|
+
description: "Project name. Required for create.",
|
|
17
|
+
})),
|
|
18
|
+
description: Type.Optional(Type.String({
|
|
19
|
+
description: "Project description (used with create).",
|
|
20
|
+
})),
|
|
21
|
+
team: Type.Optional(Type.String({
|
|
22
|
+
description: "Team key for filtering (list) or association (create).",
|
|
23
|
+
})),
|
|
24
|
+
status: Type.Optional(Type.String({
|
|
25
|
+
description: "Project status filter for list (e.g. planned, started, completed).",
|
|
26
|
+
})),
|
|
27
|
+
});
|
|
28
|
+
export function createProjectTool() {
|
|
29
|
+
return {
|
|
30
|
+
name: "linear_project",
|
|
31
|
+
label: "Linear Project",
|
|
32
|
+
description: "Manage Linear projects. Actions: list, view, create.",
|
|
33
|
+
parameters: Params,
|
|
34
|
+
async execute(_toolCallId, params) {
|
|
35
|
+
try {
|
|
36
|
+
switch (params.action) {
|
|
37
|
+
case "list":
|
|
38
|
+
return await listProjects(params);
|
|
39
|
+
case "view":
|
|
40
|
+
return await viewProject(params);
|
|
41
|
+
case "create":
|
|
42
|
+
return await createProject(params);
|
|
43
|
+
default:
|
|
44
|
+
return jsonResult({
|
|
45
|
+
error: `Unknown action: ${params.action}`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
return jsonResult({
|
|
51
|
+
error: `linear_project error: ${err instanceof Error ? err.message : String(err)}`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function listProjects(params) {
|
|
58
|
+
const filterParts = [];
|
|
59
|
+
const variables = {};
|
|
60
|
+
const varDecls = [];
|
|
61
|
+
if (params.status) {
|
|
62
|
+
filterParts.push("state: { eqIgnoreCase: $status }");
|
|
63
|
+
variables.status = params.status;
|
|
64
|
+
varDecls.push("$status: String!");
|
|
65
|
+
}
|
|
66
|
+
// Team filtering for projects uses accessibleTeams
|
|
67
|
+
if (params.team) {
|
|
68
|
+
filterParts.push("accessibleTeams: { some: { key: { eq: $team } } }");
|
|
69
|
+
variables.team = params.team.toUpperCase();
|
|
70
|
+
varDecls.push("$team: String!");
|
|
71
|
+
}
|
|
72
|
+
const filterStr = filterParts.length
|
|
73
|
+
? `filter: { ${filterParts.join(", ")} }, `
|
|
74
|
+
: "";
|
|
75
|
+
const varStr = varDecls.length ? `(${varDecls.join(", ")})` : "";
|
|
76
|
+
const data = await graphql(`query${varStr} {
|
|
77
|
+
projects(${filterStr}first: 50) {
|
|
78
|
+
nodes {
|
|
79
|
+
id
|
|
80
|
+
name
|
|
81
|
+
state
|
|
82
|
+
teams { nodes { name key } }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}`, variables);
|
|
86
|
+
return jsonResult({ projects: data.projects.nodes });
|
|
87
|
+
}
|
|
88
|
+
async function viewProject(params) {
|
|
89
|
+
if (!params.projectId) {
|
|
90
|
+
return jsonResult({ error: "projectId is required for view" });
|
|
91
|
+
}
|
|
92
|
+
const data = await graphql(`query($id: String!) {
|
|
93
|
+
project(id: $id) {
|
|
94
|
+
id
|
|
95
|
+
name
|
|
96
|
+
description
|
|
97
|
+
state
|
|
98
|
+
url
|
|
99
|
+
createdAt
|
|
100
|
+
updatedAt
|
|
101
|
+
teams { nodes { id name key } }
|
|
102
|
+
members { nodes { id name } }
|
|
103
|
+
}
|
|
104
|
+
}`, { id: params.projectId });
|
|
105
|
+
return jsonResult(data.project);
|
|
106
|
+
}
|
|
107
|
+
async function createProject(params) {
|
|
108
|
+
if (!params.name) {
|
|
109
|
+
return jsonResult({ error: "name is required for create" });
|
|
110
|
+
}
|
|
111
|
+
const input = { name: params.name };
|
|
112
|
+
if (params.description)
|
|
113
|
+
input.description = params.description;
|
|
114
|
+
if (params.team) {
|
|
115
|
+
const teamId = await resolveTeamId(params.team);
|
|
116
|
+
input.teamIds = [teamId];
|
|
117
|
+
}
|
|
118
|
+
const data = await graphql(`mutation($input: ProjectCreateInput!) {
|
|
119
|
+
projectCreate(input: $input) {
|
|
120
|
+
success
|
|
121
|
+
project { id name url }
|
|
122
|
+
}
|
|
123
|
+
}`, { input });
|
|
124
|
+
return jsonResult(data.projectCreate);
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=linear-project-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-project-tool.js","sourceRoot":"","sources":["../../src/tools/linear-project-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAe,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE1D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACzB,MAAM,EAAE,IAAI,CAAC,MAAM,CAA6B;QAC9C,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;QAChC,WAAW,EACT,gCAAgC;YAChC,kCAAkC;YAClC,+BAA+B;KAClC,CAAC;IACF,SAAS,EAAE,IAAI,CAAC,QAAQ,CACtB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EAAE,gCAAgC;KAC9C,CAAC,CACH;IACD,IAAI,EAAE,IAAI,CAAC,QAAQ,CACjB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EAAE,oCAAoC;KAClD,CAAC,CACH;IACD,WAAW,EAAE,IAAI,CAAC,QAAQ,CACxB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EAAE,yCAAyC;KACvD,CAAC,CACH;IACD,IAAI,EAAE,IAAI,CAAC,QAAQ,CACjB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EAAE,wDAAwD;KACtE,CAAC,CACH;IACD,MAAM,EAAE,IAAI,CAAC,QAAQ,CACnB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EACT,oEAAoE;KACvE,CAAC,CACH;CACF,CAAC,CAAC;AAGH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,gBAAgB;QACvB,WAAW,EACT,sDAAsD;QACxD,UAAU,EAAE,MAAM;QAClB,KAAK,CAAC,OAAO,CAAC,WAAmB,EAAE,MAAc;YAC/C,IAAI,CAAC;gBACH,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;oBACtB,KAAK,MAAM;wBACT,OAAO,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;oBACpC,KAAK,MAAM;wBACT,OAAO,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;oBACnC,KAAK,QAAQ;wBACX,OAAO,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;oBACrC;wBACE,OAAO,UAAU,CAAC;4BAChB,KAAK,EAAE,mBAAoB,MAA6B,CAAC,MAAM,EAAE;yBAClE,CAAC,CAAC;gBACP,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,UAAU,CAAC;oBAChB,KAAK,EAAE,yBAAyB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;iBACnF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,MAAc;IACxC,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,SAAS,GAA4B,EAAE,CAAC;IAC9C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,WAAW,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QACrD,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACjC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACpC,CAAC;IAED,mDAAmD;IACnD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,WAAW,CAAC,IAAI,CACd,mDAAmD,CACpD,CAAC;QACF,SAAS,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAC3C,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM;QAClC,CAAC,CAAC,aAAa,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;QAC3C,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAEjE,MAAM,IAAI,GAAG,MAAM,OAAO,CAUxB,QAAQ,MAAM;iBACD,SAAS;;;;;;;;MAQpB,EACF,SAAS,CACV,CAAC;IAEF,OAAO,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,MAAc;IACvC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,OAAO,CAGxB;;;;;;;;;;;;MAYE,EACF,EAAE,EAAE,EAAE,MAAM,CAAC,SAAS,EAAE,CACzB,CAAC;IAEF,OAAO,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAClC,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,MAAc;IACzC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,KAAK,GAA4B,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IAE7D,IAAI,MAAM,CAAC,WAAW;QAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;IAC/D,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChD,KAAK,CAAC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,OAAO,CAMxB;;;;;MAKE,EACF,EAAE,KAAK,EAAE,CACV,CAAC;IAEF,OAAO,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-relation-tool.d.ts","sourceRoot":"","sources":["../../src/tools/linear-relation-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAgDxD,wBAAgB,kBAAkB,IAAI,YAAY,CA4BjD"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { jsonResult } from "openclaw/plugin-sdk";
|
|
3
|
+
import { graphql, resolveIssueId } from "../linear-api.js";
|
|
4
|
+
const RELATION_TYPE_MAP = {
|
|
5
|
+
blocks: "blocks",
|
|
6
|
+
"blocked-by": "blocks", // reversed direction
|
|
7
|
+
related: "related",
|
|
8
|
+
duplicate: "duplicate",
|
|
9
|
+
};
|
|
10
|
+
const Params = Type.Object({
|
|
11
|
+
action: Type.Unsafe({
|
|
12
|
+
type: "string",
|
|
13
|
+
enum: ["list", "add", "delete"],
|
|
14
|
+
description: "list: show all relations for an issue. " +
|
|
15
|
+
"add: create a relation between two issues. " +
|
|
16
|
+
"delete: remove a relation.",
|
|
17
|
+
}),
|
|
18
|
+
issueId: Type.Optional(Type.String({
|
|
19
|
+
description: "Issue identifier (e.g. ENG-123). Required for list and add.",
|
|
20
|
+
})),
|
|
21
|
+
type: Type.Optional(Type.Unsafe({
|
|
22
|
+
type: "string",
|
|
23
|
+
enum: ["blocks", "blocked-by", "related", "duplicate"],
|
|
24
|
+
description: "Relation type. Required for add.",
|
|
25
|
+
})),
|
|
26
|
+
relatedIssueId: Type.Optional(Type.String({
|
|
27
|
+
description: "Related issue identifier (e.g. ENG-456). Required for add.",
|
|
28
|
+
})),
|
|
29
|
+
relationId: Type.Optional(Type.String({
|
|
30
|
+
description: "Relation ID. Required for delete.",
|
|
31
|
+
})),
|
|
32
|
+
});
|
|
33
|
+
export function createRelationTool() {
|
|
34
|
+
return {
|
|
35
|
+
name: "linear_relation",
|
|
36
|
+
label: "Linear Relation",
|
|
37
|
+
description: "Manage issue relations in Linear. Actions: list, add, delete.",
|
|
38
|
+
parameters: Params,
|
|
39
|
+
async execute(_toolCallId, params) {
|
|
40
|
+
try {
|
|
41
|
+
switch (params.action) {
|
|
42
|
+
case "list":
|
|
43
|
+
return await listRelations(params);
|
|
44
|
+
case "add":
|
|
45
|
+
return await addRelation(params);
|
|
46
|
+
case "delete":
|
|
47
|
+
return await deleteRelation(params);
|
|
48
|
+
default:
|
|
49
|
+
return jsonResult({
|
|
50
|
+
error: `Unknown action: ${params.action}`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
return jsonResult({
|
|
56
|
+
error: `linear_relation error: ${err instanceof Error ? err.message : String(err)}`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function listRelations(params) {
|
|
63
|
+
if (!params.issueId) {
|
|
64
|
+
return jsonResult({ error: "issueId is required for list" });
|
|
65
|
+
}
|
|
66
|
+
const id = await resolveIssueId(params.issueId);
|
|
67
|
+
const data = await graphql(`query($id: String!) {
|
|
68
|
+
issue(id: $id) {
|
|
69
|
+
relations {
|
|
70
|
+
nodes {
|
|
71
|
+
id
|
|
72
|
+
type
|
|
73
|
+
relatedIssue { identifier title }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
inverseRelations {
|
|
77
|
+
nodes {
|
|
78
|
+
id
|
|
79
|
+
type
|
|
80
|
+
issue { identifier title }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}`, { id });
|
|
85
|
+
const relations = data.issue.relations.nodes.map((r) => ({
|
|
86
|
+
id: r.id,
|
|
87
|
+
type: r.type,
|
|
88
|
+
issue: r.relatedIssue,
|
|
89
|
+
}));
|
|
90
|
+
const inverseRelations = data.issue.inverseRelations.nodes.map((r) => ({
|
|
91
|
+
id: r.id,
|
|
92
|
+
type: r.type,
|
|
93
|
+
direction: "inverse",
|
|
94
|
+
issue: r.issue,
|
|
95
|
+
}));
|
|
96
|
+
return jsonResult({
|
|
97
|
+
relations: [...relations, ...inverseRelations],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async function addRelation(params) {
|
|
101
|
+
if (!params.issueId) {
|
|
102
|
+
return jsonResult({ error: "issueId is required for add" });
|
|
103
|
+
}
|
|
104
|
+
if (!params.type) {
|
|
105
|
+
return jsonResult({ error: "type is required for add" });
|
|
106
|
+
}
|
|
107
|
+
if (!params.relatedIssueId) {
|
|
108
|
+
return jsonResult({ error: "relatedIssueId is required for add" });
|
|
109
|
+
}
|
|
110
|
+
const apiType = RELATION_TYPE_MAP[params.type];
|
|
111
|
+
if (!apiType) {
|
|
112
|
+
return jsonResult({ error: `Unknown relation type: ${params.type}` });
|
|
113
|
+
}
|
|
114
|
+
// For "blocked-by", swap the direction: the related issue blocks this one
|
|
115
|
+
let issueId;
|
|
116
|
+
let relatedIssueId;
|
|
117
|
+
if (params.type === "blocked-by") {
|
|
118
|
+
issueId = await resolveIssueId(params.relatedIssueId);
|
|
119
|
+
relatedIssueId = await resolveIssueId(params.issueId);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
issueId = await resolveIssueId(params.issueId);
|
|
123
|
+
relatedIssueId = await resolveIssueId(params.relatedIssueId);
|
|
124
|
+
}
|
|
125
|
+
const data = await graphql(`mutation($input: IssueRelationCreateInput!) {
|
|
126
|
+
issueRelationCreate(input: $input) {
|
|
127
|
+
success
|
|
128
|
+
issueRelation { id type }
|
|
129
|
+
}
|
|
130
|
+
}`, {
|
|
131
|
+
input: {
|
|
132
|
+
issueId,
|
|
133
|
+
relatedIssueId,
|
|
134
|
+
type: apiType,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
return jsonResult(data.issueRelationCreate);
|
|
138
|
+
}
|
|
139
|
+
async function deleteRelation(params) {
|
|
140
|
+
if (!params.relationId) {
|
|
141
|
+
return jsonResult({ error: "relationId is required for delete" });
|
|
142
|
+
}
|
|
143
|
+
const data = await graphql(`mutation($id: String!) {
|
|
144
|
+
issueRelationDelete(id: $id) { success }
|
|
145
|
+
}`, { id: params.relationId });
|
|
146
|
+
return jsonResult({ success: data.issueRelationDelete.success });
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=linear-relation-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-relation-tool.js","sourceRoot":"","sources":["../../src/tools/linear-relation-tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-team-tool.d.ts","sourceRoot":"","sources":["../../src/tools/linear-team-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAoBxD,wBAAgB,cAAc,IAAI,YAAY,CAyB7C"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { jsonResult } from "openclaw/plugin-sdk";
|
|
3
|
+
import { graphql } from "../linear-api.js";
|
|
4
|
+
const Params = Type.Object({
|
|
5
|
+
action: Type.Unsafe({
|
|
6
|
+
type: "string",
|
|
7
|
+
enum: ["list", "members"],
|
|
8
|
+
description: "list: get all teams. " +
|
|
9
|
+
"members: get members of a specific team.",
|
|
10
|
+
}),
|
|
11
|
+
team: Type.Optional(Type.String({
|
|
12
|
+
description: "Team key (e.g. ENG). Required for members.",
|
|
13
|
+
})),
|
|
14
|
+
});
|
|
15
|
+
export function createTeamTool() {
|
|
16
|
+
return {
|
|
17
|
+
name: "linear_team",
|
|
18
|
+
label: "Linear Team",
|
|
19
|
+
description: "View Linear teams and their members. Actions: list, members.",
|
|
20
|
+
parameters: Params,
|
|
21
|
+
async execute(_toolCallId, params) {
|
|
22
|
+
try {
|
|
23
|
+
switch (params.action) {
|
|
24
|
+
case "list":
|
|
25
|
+
return await listTeams();
|
|
26
|
+
case "members":
|
|
27
|
+
return await listMembers(params);
|
|
28
|
+
default:
|
|
29
|
+
return jsonResult({
|
|
30
|
+
error: `Unknown action: ${params.action}`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return jsonResult({
|
|
36
|
+
error: `linear_team error: ${err instanceof Error ? err.message : String(err)}`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function listTeams() {
|
|
43
|
+
const data = await graphql(`{ teams { nodes { id name key } } }`);
|
|
44
|
+
return jsonResult({ teams: data.teams.nodes });
|
|
45
|
+
}
|
|
46
|
+
async function listMembers(params) {
|
|
47
|
+
if (!params.team) {
|
|
48
|
+
return jsonResult({ error: "team is required for members" });
|
|
49
|
+
}
|
|
50
|
+
const data = await graphql(`query($key: String!) {
|
|
51
|
+
teams(filter: { key: { eq: $key } }) {
|
|
52
|
+
nodes {
|
|
53
|
+
members {
|
|
54
|
+
nodes { id name email }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}`, { key: params.team.toUpperCase() });
|
|
59
|
+
if (data.teams.nodes.length === 0) {
|
|
60
|
+
return jsonResult({ error: `Team "${params.team}" not found` });
|
|
61
|
+
}
|
|
62
|
+
return jsonResult({ members: data.teams.nodes[0].members.nodes });
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=linear-team-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-team-tool.js","sourceRoot":"","sources":["../../src/tools/linear-team-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAe,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACzB,MAAM,EAAE,IAAI,CAAC,MAAM,CAAqB;QACtC,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;QACzB,WAAW,EACT,uBAAuB;YACvB,0CAA0C;KAC7C,CAAC;IACF,IAAI,EAAE,IAAI,CAAC,QAAQ,CACjB,IAAI,CAAC,MAAM,CAAC;QACV,WAAW,EAAE,4CAA4C;KAC1D,CAAC,CACH;CACF,CAAC,CAAC;AAGH,MAAM,UAAU,cAAc;IAC5B,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,aAAa;QACpB,WAAW,EAAE,8DAA8D;QAC3E,UAAU,EAAE,MAAM;QAClB,KAAK,CAAC,OAAO,CAAC,WAAmB,EAAE,MAAc;YAC/C,IAAI,CAAC;gBACH,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;oBACtB,KAAK,MAAM;wBACT,OAAO,MAAM,SAAS,EAAE,CAAC;oBAC3B,KAAK,SAAS;wBACZ,OAAO,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;oBACnC;wBACE,OAAO,UAAU,CAAC;4BAChB,KAAK,EAAE,mBAAoB,MAA6B,CAAC,MAAM,EAAE;yBAClE,CAAC,CAAC;gBACP,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,UAAU,CAAC;oBAChB,KAAK,EAAE,sBAAsB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;iBAChF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,SAAS;IACtB,MAAM,IAAI,GAAG,MAAM,OAAO,CAIvB,qCAAqC,CAAC,CAAC;IAE1C,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;AACjD,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,MAAc;IACvC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,OAAO,CASxB;;;;;;;;MAQE,EACF,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CACnC,CAAC;IAEF,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,SAAS,MAAM,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;AACpE,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { AnyAgentTool } from "openclaw/plugin-sdk";
|
|
2
|
-
import type { InboxQueue } from "
|
|
2
|
+
import type { InboxQueue } from "../work-queue.js";
|
|
3
3
|
export declare function createQueueTool(queue: InboxQueue): AnyAgentTool;
|
|
4
4
|
//# sourceMappingURL=queue-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-tool.d.ts","sourceRoot":"","sources":["../../src/tools/queue-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAqBnD,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,YAAY,CA0C/D"}
|