patchwork-os 0.2.0-alpha.3 → 0.2.0-alpha.5
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/bridge.js +23 -10
- package/dist/bridge.js.map +1 -1
- package/dist/connectors/github.d.ts +58 -8
- package/dist/connectors/github.js +321 -84
- package/dist/connectors/github.js.map +1 -1
- package/dist/connectors/gmail.js +7 -0
- package/dist/connectors/gmail.js.map +1 -1
- package/dist/connectors/googleCalendar.d.ts +57 -0
- package/dist/connectors/googleCalendar.js +308 -0
- package/dist/connectors/googleCalendar.js.map +1 -0
- package/dist/connectors/linear.d.ts +52 -19
- package/dist/connectors/linear.js +167 -129
- package/dist/connectors/linear.js.map +1 -1
- package/dist/connectors/mcpClient.d.ts +56 -0
- package/dist/connectors/mcpClient.js +189 -0
- package/dist/connectors/mcpClient.js.map +1 -0
- package/dist/connectors/mcpOAuth.d.ts +73 -0
- package/dist/connectors/mcpOAuth.js +338 -0
- package/dist/connectors/mcpOAuth.js.map +1 -0
- package/dist/connectors/sentry.d.ts +17 -21
- package/dist/connectors/sentry.js +124 -131
- package/dist/connectors/sentry.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/recipes/yamlRunner.js +32 -42
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/recipesHttp.d.ts +13 -1
- package/dist/recipesHttp.js +9 -1
- package/dist/recipesHttp.js.map +1 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js +220 -49
- package/dist/server.js.map +1 -1
- package/dist/tools/createLinearIssue.d.ts +84 -0
- package/dist/tools/createLinearIssue.js +146 -0
- package/dist/tools/createLinearIssue.js.map +1 -0
- package/dist/tools/fetchCalendarEvents.d.ts +94 -0
- package/dist/tools/fetchCalendarEvents.js +97 -0
- package/dist/tools/fetchCalendarEvents.js.map +1 -0
- package/dist/tools/fetchGithubIssue.d.ts +80 -0
- package/dist/tools/fetchGithubIssue.js +84 -0
- package/dist/tools/fetchGithubIssue.js.map +1 -0
- package/dist/tools/fetchGithubPR.d.ts +89 -0
- package/dist/tools/fetchGithubPR.js +96 -0
- package/dist/tools/fetchGithubPR.js.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/start-all.sh +56 -19
- package/templates/recipes/ctx-loop-test.yaml +75 -0
- package/templates/recipes/morning-brief.yaml +12 -4
- package/templates/recipes/sentry-to-linear.yaml +77 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Calendar OAuth 2.0 connector.
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* GET /connections/google-calendar/auth — redirect to Google consent screen
|
|
6
|
+
* GET /connections/google-calendar/callback — exchange code for tokens, store locally
|
|
7
|
+
* POST /connections/google-calendar/test — verify stored token works
|
|
8
|
+
* DELETE /connections/google-calendar — revoke + delete stored token
|
|
9
|
+
*
|
|
10
|
+
* Tokens stored at ~/.patchwork/tokens/google-calendar.json (mode 0600).
|
|
11
|
+
* Client credentials read from env: GOOGLE_CALENDAR_CLIENT_ID, GOOGLE_CALENDAR_CLIENT_SECRET
|
|
12
|
+
*/
|
|
13
|
+
import crypto from "node:crypto";
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
const SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"];
|
|
18
|
+
const REDIRECT_URI = process.env.PATCHWORK_DASHBOARD_URL
|
|
19
|
+
? `${process.env.PATCHWORK_DASHBOARD_URL}/connections/google-calendar/callback`
|
|
20
|
+
: "http://localhost:3200/connections/google-calendar/callback";
|
|
21
|
+
const CALENDAR_API = "https://www.googleapis.com/calendar/v3";
|
|
22
|
+
const TOKEN_PATH = path.join(homedir(), ".patchwork", "tokens", "google-calendar.json");
|
|
23
|
+
function clientId() {
|
|
24
|
+
return process.env.GOOGLE_CALENDAR_CLIENT_ID ?? "";
|
|
25
|
+
}
|
|
26
|
+
function clientSecret() {
|
|
27
|
+
return process.env.GOOGLE_CALENDAR_CLIENT_SECRET ?? "";
|
|
28
|
+
}
|
|
29
|
+
function isConfigured() {
|
|
30
|
+
return Boolean(clientId() && clientSecret());
|
|
31
|
+
}
|
|
32
|
+
// ── Token storage ─────────────────────────────────────────────────────────────
|
|
33
|
+
export function loadTokens() {
|
|
34
|
+
if (!existsSync(TOKEN_PATH))
|
|
35
|
+
return null;
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(readFileSync(TOKEN_PATH, "utf-8"));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function saveTokens(tokens) {
|
|
44
|
+
mkdirSync(path.dirname(TOKEN_PATH), { recursive: true, mode: 0o700 });
|
|
45
|
+
writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2), { mode: 0o600 });
|
|
46
|
+
}
|
|
47
|
+
function deleteTokens() {
|
|
48
|
+
if (existsSync(TOKEN_PATH))
|
|
49
|
+
unlinkSync(TOKEN_PATH);
|
|
50
|
+
}
|
|
51
|
+
export function getStatus() {
|
|
52
|
+
const tokens = loadTokens();
|
|
53
|
+
return {
|
|
54
|
+
id: "google-calendar",
|
|
55
|
+
status: tokens ? "connected" : "disconnected",
|
|
56
|
+
lastSync: tokens?.connected_at,
|
|
57
|
+
calendarId: tokens?.calendar_id,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// ── OAuth helpers ─────────────────────────────────────────────────────────────
|
|
61
|
+
function buildAuthUrl(state) {
|
|
62
|
+
const params = new URLSearchParams({
|
|
63
|
+
client_id: clientId(),
|
|
64
|
+
redirect_uri: REDIRECT_URI,
|
|
65
|
+
response_type: "code",
|
|
66
|
+
scope: SCOPES.join(" "),
|
|
67
|
+
access_type: "offline",
|
|
68
|
+
prompt: "consent",
|
|
69
|
+
state,
|
|
70
|
+
});
|
|
71
|
+
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
72
|
+
}
|
|
73
|
+
async function exchangeCode(code) {
|
|
74
|
+
const res = await fetch("https://oauth2.googleapis.com/token", {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
77
|
+
body: new URLSearchParams({
|
|
78
|
+
code,
|
|
79
|
+
client_id: clientId(),
|
|
80
|
+
client_secret: clientSecret(),
|
|
81
|
+
redirect_uri: REDIRECT_URI,
|
|
82
|
+
grant_type: "authorization_code",
|
|
83
|
+
}).toString(),
|
|
84
|
+
});
|
|
85
|
+
if (!res.ok) {
|
|
86
|
+
const body = await res.text();
|
|
87
|
+
throw new Error(`Token exchange failed: ${res.status} ${body}`);
|
|
88
|
+
}
|
|
89
|
+
const json = (await res.json());
|
|
90
|
+
return {
|
|
91
|
+
access_token: json.access_token,
|
|
92
|
+
refresh_token: json.refresh_token,
|
|
93
|
+
expiry_date: json.expires_in
|
|
94
|
+
? Date.now() + json.expires_in * 1000
|
|
95
|
+
: undefined,
|
|
96
|
+
token_type: json.token_type,
|
|
97
|
+
scope: json.scope,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function refreshAccessToken(tokens) {
|
|
101
|
+
if (!tokens.refresh_token)
|
|
102
|
+
throw new Error("No refresh token available");
|
|
103
|
+
const res = await fetch("https://oauth2.googleapis.com/token", {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
106
|
+
body: new URLSearchParams({
|
|
107
|
+
refresh_token: tokens.refresh_token,
|
|
108
|
+
client_id: clientId(),
|
|
109
|
+
client_secret: clientSecret(),
|
|
110
|
+
grant_type: "refresh_token",
|
|
111
|
+
}).toString(),
|
|
112
|
+
});
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
const body = await res.text();
|
|
115
|
+
throw new Error(`Token refresh failed: ${res.status} ${body}`);
|
|
116
|
+
}
|
|
117
|
+
const json = (await res.json());
|
|
118
|
+
const updated = {
|
|
119
|
+
...tokens,
|
|
120
|
+
access_token: json.access_token,
|
|
121
|
+
expiry_date: json.expires_in
|
|
122
|
+
? Date.now() + json.expires_in * 1000
|
|
123
|
+
: tokens.expiry_date,
|
|
124
|
+
};
|
|
125
|
+
saveTokens(updated);
|
|
126
|
+
return updated;
|
|
127
|
+
}
|
|
128
|
+
/** Returns a valid access token, refreshing if needed. */
|
|
129
|
+
export async function getValidAccessToken() {
|
|
130
|
+
let tokens = loadTokens();
|
|
131
|
+
if (!tokens)
|
|
132
|
+
throw new Error("Google Calendar not connected");
|
|
133
|
+
const bufferMs = 60_000;
|
|
134
|
+
if (tokens.expiry_date && Date.now() > tokens.expiry_date - bufferMs) {
|
|
135
|
+
tokens = await refreshAccessToken(tokens);
|
|
136
|
+
}
|
|
137
|
+
return tokens.access_token;
|
|
138
|
+
}
|
|
139
|
+
async function revokeToken(token) {
|
|
140
|
+
await fetch(`https://oauth2.googleapis.com/revoke?token=${encodeURIComponent(token)}`, { method: "POST" }).catch(() => { });
|
|
141
|
+
}
|
|
142
|
+
// ── State map (in-memory CSRF protection) ────────────────────────────────────
|
|
143
|
+
const pendingStates = new Set();
|
|
144
|
+
function generateState() {
|
|
145
|
+
const state = crypto.randomBytes(32).toString("hex");
|
|
146
|
+
pendingStates.add(state);
|
|
147
|
+
setTimeout(() => pendingStates.delete(state), 10 * 60 * 1000);
|
|
148
|
+
return state;
|
|
149
|
+
}
|
|
150
|
+
// ── API helpers ───────────────────────────────────────────────────────────────
|
|
151
|
+
async function calendarGet(endpoint, accessToken, params = {}, signal) {
|
|
152
|
+
const qs = new URLSearchParams(params);
|
|
153
|
+
const res = await fetch(`${CALENDAR_API}${endpoint}?${qs}`, {
|
|
154
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
155
|
+
signal,
|
|
156
|
+
});
|
|
157
|
+
if (!res.ok) {
|
|
158
|
+
const body = await res.text();
|
|
159
|
+
throw new Error(`Google Calendar API error ${res.status}: ${body.slice(0, 200)}`);
|
|
160
|
+
}
|
|
161
|
+
return res.json();
|
|
162
|
+
}
|
|
163
|
+
async function fetchCalendarSummary(accessToken, calendarId) {
|
|
164
|
+
const data = (await calendarGet(`/calendars/${encodeURIComponent(calendarId)}`, accessToken));
|
|
165
|
+
return data.summary ?? calendarId;
|
|
166
|
+
}
|
|
167
|
+
// ── Event fetching ────────────────────────────────────────────────────────────
|
|
168
|
+
export async function listEvents(opts = {}, signal) {
|
|
169
|
+
const accessToken = await getValidAccessToken();
|
|
170
|
+
const tokens = loadTokens();
|
|
171
|
+
const calendarId = opts.calendarId ?? tokens.calendar_id ?? "primary";
|
|
172
|
+
const daysAhead = Math.min(opts.daysAhead ?? 7, 30);
|
|
173
|
+
const maxResults = Math.min(opts.maxResults ?? 20, 50);
|
|
174
|
+
const now = new Date();
|
|
175
|
+
const end = new Date(now.getTime() + daysAhead * 24 * 60 * 60 * 1000);
|
|
176
|
+
const data = (await calendarGet(`/calendars/${encodeURIComponent(calendarId)}/events`, accessToken, {
|
|
177
|
+
timeMin: now.toISOString(),
|
|
178
|
+
timeMax: end.toISOString(),
|
|
179
|
+
maxResults: String(maxResults),
|
|
180
|
+
singleEvents: "true",
|
|
181
|
+
orderBy: "startTime",
|
|
182
|
+
}, signal));
|
|
183
|
+
return (data.items ?? []).map((item) => {
|
|
184
|
+
const startRaw = item.start?.dateTime ?? item.start?.date ?? "";
|
|
185
|
+
const endRaw = item.end?.dateTime ?? item.end?.date ?? "";
|
|
186
|
+
const allDay = !item.start?.dateTime;
|
|
187
|
+
return {
|
|
188
|
+
id: item.id,
|
|
189
|
+
summary: item.summary ?? "(no title)",
|
|
190
|
+
description: item.description,
|
|
191
|
+
start: startRaw,
|
|
192
|
+
end: endRaw,
|
|
193
|
+
allDay,
|
|
194
|
+
location: item.location,
|
|
195
|
+
htmlLink: item.htmlLink ?? "",
|
|
196
|
+
attendees: (item.attendees ?? [])
|
|
197
|
+
.map((a) => a.displayName ?? a.email ?? "")
|
|
198
|
+
.filter(Boolean),
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
// ── HTTP handlers ─────────────────────────────────────────────────────────────
|
|
203
|
+
export function handleCalendarAuthRedirect() {
|
|
204
|
+
if (!isConfigured()) {
|
|
205
|
+
return {
|
|
206
|
+
status: 400,
|
|
207
|
+
contentType: "application/json",
|
|
208
|
+
body: JSON.stringify({
|
|
209
|
+
ok: false,
|
|
210
|
+
error: "GOOGLE_CALENDAR_CLIENT_ID and GOOGLE_CALENDAR_CLIENT_SECRET env vars not set",
|
|
211
|
+
}),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const state = generateState();
|
|
215
|
+
return { status: 302, body: "", redirect: buildAuthUrl(state) };
|
|
216
|
+
}
|
|
217
|
+
export async function handleCalendarCallback(code, state, error) {
|
|
218
|
+
if (error) {
|
|
219
|
+
return {
|
|
220
|
+
status: 400,
|
|
221
|
+
contentType: "application/json",
|
|
222
|
+
body: JSON.stringify({ ok: false, error }),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (!code || !state || !pendingStates.has(state)) {
|
|
226
|
+
return {
|
|
227
|
+
status: 400,
|
|
228
|
+
contentType: "application/json",
|
|
229
|
+
body: JSON.stringify({ ok: false, error: "Invalid OAuth state" }),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
pendingStates.delete(state);
|
|
233
|
+
try {
|
|
234
|
+
const oauthTokens = await exchangeCode(code);
|
|
235
|
+
// Default to "primary" calendar; user can update later
|
|
236
|
+
const calId = "primary";
|
|
237
|
+
const summary = await fetchCalendarSummary(oauthTokens.access_token, calId);
|
|
238
|
+
const tokens = {
|
|
239
|
+
...oauthTokens,
|
|
240
|
+
calendar_id: calId,
|
|
241
|
+
connected_at: new Date().toISOString(),
|
|
242
|
+
};
|
|
243
|
+
saveTokens(tokens);
|
|
244
|
+
return {
|
|
245
|
+
status: 200,
|
|
246
|
+
contentType: "application/json",
|
|
247
|
+
body: JSON.stringify({ ok: true, calendarId: calId, summary }),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
return {
|
|
252
|
+
status: 400,
|
|
253
|
+
contentType: "application/json",
|
|
254
|
+
body: JSON.stringify({
|
|
255
|
+
ok: false,
|
|
256
|
+
error: err instanceof Error ? err.message : String(err),
|
|
257
|
+
}),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
export async function handleCalendarTest() {
|
|
262
|
+
const tokens = loadTokens();
|
|
263
|
+
if (!tokens) {
|
|
264
|
+
return {
|
|
265
|
+
status: 400,
|
|
266
|
+
contentType: "application/json",
|
|
267
|
+
body: JSON.stringify({
|
|
268
|
+
ok: false,
|
|
269
|
+
error: "Google Calendar not connected",
|
|
270
|
+
}),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
const accessToken = await getValidAccessToken();
|
|
275
|
+
const summary = await fetchCalendarSummary(accessToken, tokens.calendar_id);
|
|
276
|
+
return {
|
|
277
|
+
status: 200,
|
|
278
|
+
contentType: "application/json",
|
|
279
|
+
body: JSON.stringify({
|
|
280
|
+
ok: true,
|
|
281
|
+
calendarId: tokens.calendar_id,
|
|
282
|
+
summary,
|
|
283
|
+
}),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
return {
|
|
288
|
+
status: 400,
|
|
289
|
+
contentType: "application/json",
|
|
290
|
+
body: JSON.stringify({
|
|
291
|
+
ok: false,
|
|
292
|
+
error: err instanceof Error ? err.message : String(err),
|
|
293
|
+
}),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
export async function handleCalendarDisconnect() {
|
|
298
|
+
const tokens = loadTokens();
|
|
299
|
+
if (tokens?.access_token)
|
|
300
|
+
await revokeToken(tokens.access_token);
|
|
301
|
+
deleteTokens();
|
|
302
|
+
return {
|
|
303
|
+
status: 200,
|
|
304
|
+
contentType: "application/json",
|
|
305
|
+
body: JSON.stringify({ ok: true }),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=googleCalendar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"googleCalendar.js","sourceRoot":"","sources":["../../src/connectors/googleCalendar.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Linear connector.
|
|
2
|
+
* Linear connector — routes through Linear's official MCP server.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Env var: LINEAR_API_KEY
|
|
4
|
+
* Endpoint: https://mcp.linear.app/mcp
|
|
5
|
+
* Auth: OAuth 2.1 w/ PKCE; dynamic client registration (RFC 7591).
|
|
7
6
|
*
|
|
8
|
-
* HTTP routes
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* HTTP routes (wired in src/server.ts):
|
|
8
|
+
* GET /connections/linear/authorize — returns { url } for popup
|
|
9
|
+
* GET /connections/linear/callback — token exchange
|
|
10
|
+
* POST /connections/linear/test — ping MCP server
|
|
11
|
+
* DELETE /connections/linear — revoke + delete token
|
|
12
|
+
*
|
|
13
|
+
* Back-compat: loadTokens() returns a shape compatible with legacy code
|
|
14
|
+
* that expected { api_key }. Set LINEAR_API_KEY to bypass OAuth for CI/headless.
|
|
12
15
|
*/
|
|
13
16
|
export interface LinearTokens {
|
|
14
17
|
api_key: string;
|
|
@@ -21,9 +24,14 @@ export interface ConnectorStatus {
|
|
|
21
24
|
lastSync?: string;
|
|
22
25
|
workspace?: string;
|
|
23
26
|
}
|
|
27
|
+
export interface ConnectorHandlerResult {
|
|
28
|
+
status: number;
|
|
29
|
+
body: string;
|
|
30
|
+
contentType?: string;
|
|
31
|
+
redirect?: string;
|
|
32
|
+
}
|
|
24
33
|
export declare function loadTokens(): LinearTokens | null;
|
|
25
34
|
export declare function getStatus(): ConnectorStatus;
|
|
26
|
-
export declare function linearQuery<T>(query: string, variables: Record<string, unknown>, apiKey: string, signal?: AbortSignal): Promise<T>;
|
|
27
35
|
export interface LinearIssue {
|
|
28
36
|
id: string;
|
|
29
37
|
identifier: string;
|
|
@@ -52,16 +60,41 @@ export interface LinearIssue {
|
|
|
52
60
|
}>;
|
|
53
61
|
};
|
|
54
62
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Fetch a Linear issue by ID or URL.
|
|
57
|
-
* Accepts: "LIN-123", "abc123def456...", "https://linear.app/.../issue/LIN-123/..."
|
|
58
|
-
*/
|
|
59
63
|
export declare function fetchIssue(issueIdOrUrl: string, signal?: AbortSignal): Promise<LinearIssue>;
|
|
60
|
-
export interface
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
export interface ListLinearIssuesOpts {
|
|
65
|
+
team?: string;
|
|
66
|
+
assigneeMe?: boolean;
|
|
67
|
+
states?: string[];
|
|
68
|
+
limit?: number;
|
|
64
69
|
}
|
|
65
|
-
export declare function
|
|
70
|
+
export declare function listIssues(opts?: ListLinearIssuesOpts, signal?: AbortSignal): Promise<Record<string, unknown>[]>;
|
|
71
|
+
export declare function handleLinearAuthorize(): Promise<ConnectorHandlerResult>;
|
|
72
|
+
export declare function handleLinearCallback(code: string | null, state: string | null, error: string | null): Promise<ConnectorHandlerResult>;
|
|
66
73
|
export declare function handleLinearTest(): Promise<ConnectorHandlerResult>;
|
|
67
|
-
export declare function handleLinearDisconnect(): ConnectorHandlerResult
|
|
74
|
+
export declare function handleLinearDisconnect(): Promise<ConnectorHandlerResult>;
|
|
75
|
+
export interface LinearTeam {
|
|
76
|
+
id: string;
|
|
77
|
+
key: string;
|
|
78
|
+
name: string;
|
|
79
|
+
}
|
|
80
|
+
export declare function listTeams(signal?: AbortSignal): Promise<LinearTeam[]>;
|
|
81
|
+
export declare function listLabels(signal?: AbortSignal): Promise<Array<{
|
|
82
|
+
id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
}>>;
|
|
85
|
+
export interface CreateIssueInput {
|
|
86
|
+
team: string;
|
|
87
|
+
title: string;
|
|
88
|
+
description?: string;
|
|
89
|
+
priority?: number;
|
|
90
|
+
labels?: string[];
|
|
91
|
+
}
|
|
92
|
+
export declare function createIssue(input: CreateIssueInput, signal?: AbortSignal): Promise<{
|
|
93
|
+
id: string;
|
|
94
|
+
identifier: string;
|
|
95
|
+
title: string;
|
|
96
|
+
url: string;
|
|
97
|
+
state: {
|
|
98
|
+
name: string;
|
|
99
|
+
};
|
|
100
|
+
}>;
|