@sideboard-ai/core 0.1.324 → 0.1.326
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/{abletime-CU2UPMJF.js → abletime-RDHYSZL2.js} +5 -4
- package/dist/{abletime-5ZY3ZUUO.js → abletime-RYYFGKMU.js} +6 -5
- package/dist/{abletime-oauth-7EOSBEFM.js → abletime-oauth-OYGS2DAU.js} +3 -3
- package/dist/{abletime-oauth-4TTCR3CT.js → abletime-oauth-QR2QDVMK.js} +3 -3
- package/dist/{abletime-rest-KOKE7V4B.js → abletime-rest-BKYKNPTC.js} +3 -3
- package/dist/{abletime-rest-3TG4G7LW.js → abletime-rest-QDPTWNS4.js} +3 -3
- package/dist/agents/claude-runner.cjs +59 -22
- package/dist/agents/claude-runner.js +39 -16
- package/dist/agents/cursor-runner.cjs +97 -32
- package/dist/agents/cursor-runner.js +74 -24
- package/dist/{agents-YK65UDLT.js → agents-HANWZNI6.js} +9 -8
- package/dist/{agents-LUSTCMDN.js → agents-QO7GKBYT.js} +12 -11
- package/dist/{app-settings-XIF4EC4K.js → app-settings-RXBH7KY4.js} +1 -1
- package/dist/{app-settings-I5HM5RMP.js → app-settings-TUVLZNDE.js} +1 -1
- package/dist/{chunk-7MQSYPAY.js → chunk-2EOLWHXH.js} +206 -188
- package/dist/{chunk-QOS4465Y.js → chunk-3Q4XNYHY.js} +1 -1
- package/dist/{chunk-RR4WCGNB.js → chunk-46FECFZU.js} +18 -15
- package/dist/{chunk-5GDJSFCT.js → chunk-4H5MWZ6J.js} +2 -2
- package/dist/{chunk-EQ52JR3Q.js → chunk-5MXLDDYF.js} +1 -1
- package/dist/{chunk-NCHOTN3C.js → chunk-5YCKATG6.js} +4 -3
- package/dist/{chunk-ROOZDDFA.js → chunk-75RPUXJS.js} +7 -8
- package/dist/{chunk-INBUV7HY.js → chunk-B244TJWC.js} +2 -2
- package/dist/{chunk-JH5SMVM5.js → chunk-BBNWKL4I.js} +268 -235
- package/dist/{chunk-ODH3XHMI.js → chunk-C7HUCBX7.js} +5 -5
- package/dist/{chunk-OIHRC2ZJ.js → chunk-D744HOI5.js} +25 -3
- package/dist/chunk-DBKHL7NC.js +214 -0
- package/dist/chunk-F6GE7V3Q.js +216 -0
- package/dist/{chunk-JE3JLHMO.js → chunk-FWVUGLBG.js} +3 -3
- package/dist/{chunk-DF5BM5OY.js → chunk-GP3RLZ6A.js} +207 -186
- package/dist/{chunk-FR3SAYUL.js → chunk-JM2TGOHP.js} +5 -5
- package/dist/{chunk-Y3WREIPQ.js → chunk-K7M7MIWL.js} +12 -210
- package/dist/{chunk-FUFNMUNR.js → chunk-KECHK3ZT.js} +7 -8
- package/dist/{chunk-EKU7FQVG.js → chunk-KXRF6RNL.js} +2 -2
- package/dist/chunk-LRHUBGEM.js +1259 -0
- package/dist/{chunk-MZIZTSR2.js → chunk-MAW7DIG7.js} +2 -2
- package/dist/{chunk-ZHGMRBSC.js → chunk-NURZOXGN.js} +4 -4
- package/dist/{chunk-QIC2JXHW.js → chunk-OOXRMTU6.js} +1 -1
- package/dist/{chunk-5XNT6BCG.js → chunk-OPXEJJBM.js} +1 -1
- package/dist/chunk-OYRN356W.js +80 -0
- package/dist/{chunk-7PVT73H6.js → chunk-PB6HW7SA.js} +12 -210
- package/dist/{chunk-B4CDSSRN.js → chunk-Q3FHTGIW.js} +2 -0
- package/dist/{chunk-7D5QIU3N.js → chunk-QG4CYRA3.js} +301 -268
- package/dist/{chunk-EJ66LEKH.js → chunk-QPSKQP6E.js} +24 -3
- package/dist/{chunk-Q3YJZPQI.js → chunk-RZVOX2MT.js} +3 -3
- package/dist/chunk-SO7G6O24.js +1445 -0
- package/dist/{chunk-YDUHLWI5.js → chunk-SUZOPJZC.js} +21 -7
- package/dist/chunk-TMCK5TLA.js +25 -0
- package/dist/chunk-W3ZLE4PO.js +77 -0
- package/dist/chunk-WUCAVIIP.js +28 -0
- package/dist/{chunk-BYUCNIXK.js → chunk-XLVLMQKN.js} +13 -9
- package/dist/{chunk-ZIRQZUZY.js → chunk-YGVGXQA7.js} +3 -3
- package/dist/{chunk-NXUN4U5P.js → chunk-YWQF7WAG.js} +2 -2
- package/dist/{chunk-IFWGG2M2.js → chunk-ZSHS46V5.js} +2 -0
- package/dist/{chunk-74OPNF7U.js → chunk-ZUAYA57W.js} +2 -2
- package/dist/{conductor-SPZOTC7W.js → conductor-4GVTHUUB.js} +7 -7
- package/dist/{conductor-JFABMXDA.js → conductor-TGWI6ETZ.js} +6 -6
- package/dist/{coordinator-prompt-K66RGNWK.js → coordinator-prompt-3E6SIDTM.js} +4 -4
- package/dist/{coordinator-prompt-Z6UFHFDH.js → coordinator-prompt-WEYJREV6.js} +4 -4
- package/dist/{cursor-recover-TEBFKAZ4.js → cursor-recover-7F46PAJ5.js} +1 -1
- package/dist/{cursor-recover-OWPQUHFH.js → cursor-recover-BDIAZIE3.js} +1 -1
- package/dist/{global-workspace-A6UO6PJV.js → global-workspace-FPDZKUPF.js} +4 -4
- package/dist/{global-workspace-RHEO55B4.js → global-workspace-UIEXAG2B.js} +4 -4
- package/dist/index.cjs +21094 -20840
- package/dist/index.d.cts +36 -17
- package/dist/index.d.ts +36 -17
- package/dist/index.js +343 -1678
- package/dist/linear-6S7MK5YK.js +69 -0
- package/dist/linear-Z2T4KUAQ.js +67 -0
- package/dist/linear-list-A37WLUBO.js +6 -0
- package/dist/linear-list-Q4M7AZO2.js +8 -0
- package/dist/mcp/run-stdio.cjs +15903 -15640
- package/dist/mcp/run-stdio.js +67 -1247
- package/dist/{optional-cli-RMMS5L6F.js → optional-cli-AANBL4U5.js} +3 -3
- package/dist/{optional-cli-Y3Z3GIPZ.js → optional-cli-CA4LL322.js} +4 -4
- package/dist/{orchestrator-7VTAUY24.js → orchestrator-7FVDBE7Y.js} +15 -13
- package/dist/{orchestrator-2O6OJQLX.js → orchestrator-RQXRZW7Z.js} +14 -12
- package/dist/{thread-store-3F7GR64A.js → thread-store-4ZN3DS2W.js} +1 -1
- package/dist/{thread-store-JRSL4HQY.js → thread-store-IC4BQXDW.js} +1 -1
- package/dist/{workspaces-NRKEXOYR.js → workspaces-LEW4UP56.js} +4 -4
- package/dist/{workspaces-42E6AGXD.js → workspaces-QONNLHNR.js} +4 -4
- package/dist/{worktree-ZDA2UJ7C.js → worktree-6GIONDQO.js} +3 -3
- package/dist/{worktree-4NGXCCUO.js → worktree-KFEJAQ2H.js} +3 -3
- package/package.json +1 -1
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -25,13 +25,11 @@ import {
|
|
|
25
25
|
findLiveThreadForCreate,
|
|
26
26
|
fireSchedule,
|
|
27
27
|
formatFleetArchive,
|
|
28
|
-
formatGitHubSearchUpdatedSince,
|
|
29
28
|
formatListedIssuesForMcp,
|
|
30
29
|
getOrchestrator,
|
|
31
30
|
getSchedule,
|
|
32
31
|
isAccountWideIssueSource,
|
|
33
32
|
isInjectedNoticeText,
|
|
34
|
-
issueMatchesUpdatedSince,
|
|
35
33
|
issueNeedsWorkspacePick,
|
|
36
34
|
listFleetArchive,
|
|
37
35
|
listSchedules,
|
|
@@ -39,9 +37,7 @@ import {
|
|
|
39
37
|
mcpIssueUpdatedSinceSchema,
|
|
40
38
|
mcpJson,
|
|
41
39
|
notifyOrchestrator,
|
|
42
|
-
parseIssueSince,
|
|
43
40
|
parseWeeklyDays,
|
|
44
|
-
previewIssueCommentBody,
|
|
45
41
|
readTurnLive,
|
|
46
42
|
registerViewerContextTools,
|
|
47
43
|
resolveNotifyCallerThread,
|
|
@@ -57,38 +53,7 @@ import {
|
|
|
57
53
|
updateSchedule,
|
|
58
54
|
workspaceTagTargetRef,
|
|
59
55
|
writeAgentBlock
|
|
60
|
-
} from "../chunk-
|
|
61
|
-
import "../chunk-LX5R5JOR.js";
|
|
62
|
-
import {
|
|
63
|
-
commentAbleTimeTask,
|
|
64
|
-
createAbleTimeTask,
|
|
65
|
-
downloadAbleTimeAttachment,
|
|
66
|
-
downloadAuthenticatedFile,
|
|
67
|
-
ensureAbleTimeTask,
|
|
68
|
-
extractHttpUrls,
|
|
69
|
-
getAbleTimeOrientation,
|
|
70
|
-
getAbleTimeTask,
|
|
71
|
-
hostMatches,
|
|
72
|
-
issueAttachmentWorktreePath,
|
|
73
|
-
listAbleTimeProjects,
|
|
74
|
-
listAbleTimeTasks,
|
|
75
|
-
looksLikeHttpUrl,
|
|
76
|
-
mergeIssueAttachments,
|
|
77
|
-
searchAbleTimeTasks,
|
|
78
|
-
toAbleTimeIssueInfo,
|
|
79
|
-
updateAbleTimeTask
|
|
80
|
-
} from "../chunk-Y3WREIPQ.js";
|
|
81
|
-
import "../chunk-NXUN4U5P.js";
|
|
82
|
-
import "../chunk-FR3SAYUL.js";
|
|
83
|
-
import {
|
|
84
|
-
normalizeServiceOrigin
|
|
85
|
-
} from "../chunk-QOS4465Y.js";
|
|
86
|
-
import {
|
|
87
|
-
PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
88
|
-
PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
89
|
-
shouldRegisterPresentPlan,
|
|
90
|
-
threadMayPresentPlan
|
|
91
|
-
} from "../chunk-4GK32AYX.js";
|
|
56
|
+
} from "../chunk-2EOLWHXH.js";
|
|
92
57
|
import "../chunk-DC3VZXMA.js";
|
|
93
58
|
import {
|
|
94
59
|
SIDEBOARD_THREAD_ID_ENV,
|
|
@@ -98,13 +63,7 @@ import {
|
|
|
98
63
|
shouldRegisterMcpWaitForTurn,
|
|
99
64
|
sideboardMcpProfile,
|
|
100
65
|
worktreeMcpToolNames
|
|
101
|
-
} from "../chunk-
|
|
102
|
-
import "../chunk-MZIZTSR2.js";
|
|
103
|
-
import "../chunk-6U6QRUHY.js";
|
|
104
|
-
import "../chunk-E63AM3PW.js";
|
|
105
|
-
import {
|
|
106
|
-
httpFetch
|
|
107
|
-
} from "../chunk-2GPQZZLY.js";
|
|
66
|
+
} from "../chunk-QG4CYRA3.js";
|
|
108
67
|
import {
|
|
109
68
|
mcpWaitForJobTimeoutMs,
|
|
110
69
|
mcpWaitForTurnTimeoutMs,
|
|
@@ -115,18 +74,44 @@ import {
|
|
|
115
74
|
waitForDetachedJob,
|
|
116
75
|
waitForTurnToolResult,
|
|
117
76
|
workspaceAgentChatSummaries
|
|
118
|
-
} from "../chunk-
|
|
77
|
+
} from "../chunk-XLVLMQKN.js";
|
|
119
78
|
import {
|
|
120
79
|
HUGE_TOOL_RESULT_SUMMARY,
|
|
121
80
|
clipToolResultForStore,
|
|
122
81
|
formatUnknownDetail,
|
|
123
82
|
isInternalAgentStatusText,
|
|
124
83
|
looksLikeHugeToolResultDump
|
|
125
|
-
} from "../chunk-
|
|
84
|
+
} from "../chunk-5MXLDDYF.js";
|
|
85
|
+
import "../chunk-LX5R5JOR.js";
|
|
86
|
+
import {
|
|
87
|
+
commentAbleTimeTask,
|
|
88
|
+
createAbleTimeTask,
|
|
89
|
+
downloadAbleTimeAttachment,
|
|
90
|
+
ensureAbleTimeTask,
|
|
91
|
+
getAbleTimeOrientation,
|
|
92
|
+
getAbleTimeTask,
|
|
93
|
+
listAbleTimeProjects,
|
|
94
|
+
listAbleTimeTasks,
|
|
95
|
+
searchAbleTimeTasks,
|
|
96
|
+
toAbleTimeIssueInfo,
|
|
97
|
+
updateAbleTimeTask
|
|
98
|
+
} from "../chunk-K7M7MIWL.js";
|
|
99
|
+
import "../chunk-YWQF7WAG.js";
|
|
100
|
+
import "../chunk-JM2TGOHP.js";
|
|
101
|
+
import {
|
|
102
|
+
normalizeServiceOrigin
|
|
103
|
+
} from "../chunk-3Q4XNYHY.js";
|
|
104
|
+
import "../chunk-MAW7DIG7.js";
|
|
105
|
+
import {
|
|
106
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
107
|
+
PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
108
|
+
shouldRegisterPresentPlan,
|
|
109
|
+
threadMayPresentPlan
|
|
110
|
+
} from "../chunk-4GK32AYX.js";
|
|
126
111
|
import {
|
|
127
112
|
GLOBAL_WORKSPACE_ID,
|
|
128
113
|
isCloudCoordinatorThread
|
|
129
|
-
} from "../chunk-
|
|
114
|
+
} from "../chunk-YGVGXQA7.js";
|
|
130
115
|
import {
|
|
131
116
|
canonicalizeRepoPath,
|
|
132
117
|
formatGhLandError,
|
|
@@ -139,10 +124,40 @@ import {
|
|
|
139
124
|
resolveProjectRemoteTag,
|
|
140
125
|
resolveRepoRoot,
|
|
141
126
|
warmGithubAgentAuth
|
|
142
|
-
} from "../chunk-
|
|
127
|
+
} from "../chunk-75RPUXJS.js";
|
|
128
|
+
import "../chunk-6U6QRUHY.js";
|
|
129
|
+
import "../chunk-E63AM3PW.js";
|
|
130
|
+
import {
|
|
131
|
+
commentLinearIssue,
|
|
132
|
+
createLinearIssue,
|
|
133
|
+
downloadLinearAttachment,
|
|
134
|
+
getLinearIssue,
|
|
135
|
+
listLinearCommentsSince,
|
|
136
|
+
listLinearIssuesFiltered,
|
|
137
|
+
listLinearProjects,
|
|
138
|
+
listLinearTeams,
|
|
139
|
+
updateLinearIssue
|
|
140
|
+
} from "../chunk-LRHUBGEM.js";
|
|
141
|
+
import {
|
|
142
|
+
formatGitHubSearchUpdatedSince,
|
|
143
|
+
issueMatchesUpdatedSince,
|
|
144
|
+
parseIssueSince,
|
|
145
|
+
previewIssueCommentBody
|
|
146
|
+
} from "../chunk-OYRN356W.js";
|
|
147
|
+
import {
|
|
148
|
+
downloadAuthenticatedFile,
|
|
149
|
+
extractHttpUrls,
|
|
150
|
+
hostMatches,
|
|
151
|
+
issueAttachmentWorktreePath,
|
|
152
|
+
mergeIssueAttachments
|
|
153
|
+
} from "../chunk-F6GE7V3Q.js";
|
|
143
154
|
import {
|
|
144
155
|
CLI_DIR
|
|
145
156
|
} from "../chunk-N2WW62LF.js";
|
|
157
|
+
import {
|
|
158
|
+
httpFetch
|
|
159
|
+
} from "../chunk-2GPQZZLY.js";
|
|
160
|
+
import "../chunk-WUCAVIIP.js";
|
|
146
161
|
import "../chunk-4YXZOXMT.js";
|
|
147
162
|
import {
|
|
148
163
|
gh,
|
|
@@ -150,7 +165,6 @@ import {
|
|
|
150
165
|
run
|
|
151
166
|
} from "../chunk-YBTSZ2SW.js";
|
|
152
167
|
import {
|
|
153
|
-
disconnectLinearConnection,
|
|
154
168
|
formatWorkspaceProfileSuffix,
|
|
155
169
|
isAbleTimeConnected,
|
|
156
170
|
isLinearConnected,
|
|
@@ -159,9 +173,8 @@ import {
|
|
|
159
173
|
resolveEffectiveIssueSource,
|
|
160
174
|
resolveProjectReviewLabel,
|
|
161
175
|
resolveViewerProfile,
|
|
162
|
-
resolveViewerProfileForRepo
|
|
163
|
-
|
|
164
|
-
} from "../chunk-OIHRC2ZJ.js";
|
|
176
|
+
resolveViewerProfileForRepo
|
|
177
|
+
} from "../chunk-D744HOI5.js";
|
|
165
178
|
import "../chunk-NSTQ6QKD.js";
|
|
166
179
|
import {
|
|
167
180
|
dropNestedElectronEnvFromProcess
|
|
@@ -172,7 +185,7 @@ import {
|
|
|
172
185
|
normalizeWorkspaceTags,
|
|
173
186
|
readThread,
|
|
174
187
|
updateThread
|
|
175
|
-
} from "../chunk-
|
|
188
|
+
} from "../chunk-Q3FHTGIW.js";
|
|
176
189
|
import "../chunk-WMZIANPV.js";
|
|
177
190
|
import "../chunk-KGZBYWZ3.js";
|
|
178
191
|
import {
|
|
@@ -768,1199 +781,6 @@ async function createGitHubIssue(input, opts) {
|
|
|
768
781
|
throw new Error(`GitHub create issue: could not parse issue from ${stdout.trim() || "empty output"}`);
|
|
769
782
|
}
|
|
770
783
|
|
|
771
|
-
// src/integrations/linear-oauth.ts
|
|
772
|
-
import { createHash, randomBytes } from "crypto";
|
|
773
|
-
import { createServer } from "http";
|
|
774
|
-
|
|
775
|
-
// src/integrations/linear-app.ts
|
|
776
|
-
var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
|
|
777
|
-
|
|
778
|
-
// src/integrations/linear-oauth.ts
|
|
779
|
-
var LINEAR_OAUTH_PORT = 19848;
|
|
780
|
-
var LINEAR_OAUTH_REDIRECT = `http://127.0.0.1:${LINEAR_OAUTH_PORT}/callback`;
|
|
781
|
-
var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
|
|
782
|
-
var REFRESH_SKEW_MS = 5 * 6e4;
|
|
783
|
-
function linearOAuthCredentials() {
|
|
784
|
-
const settings = loadAppSettings();
|
|
785
|
-
const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
|
|
786
|
-
const clientSecret = process.env.SIDEBOARD_LINEAR_CLIENT_SECRET?.trim() || settings.integrations.linearClientSecret?.trim() || "";
|
|
787
|
-
if (!clientId) {
|
|
788
|
-
throw new Error(
|
|
789
|
-
`Linear browser sign-in needs a Linear OAuth app Client ID. Create one at linear.app/settings/api/applications/new with callback ${LINEAR_OAUTH_REDIRECT}, then set SIDEBOARD_LINEAR_CLIENT_ID (PKCE does not require a secret). You can still paste a personal API key.`
|
|
790
|
-
);
|
|
791
|
-
}
|
|
792
|
-
return { clientId, clientSecret };
|
|
793
|
-
}
|
|
794
|
-
function linearAuthorizationHeader(token) {
|
|
795
|
-
const t = token.trim();
|
|
796
|
-
if (!t) return t;
|
|
797
|
-
if (/^bearer\s+/i.test(t)) return t;
|
|
798
|
-
if (t.startsWith("lin_api_")) return t;
|
|
799
|
-
return `Bearer ${t}`;
|
|
800
|
-
}
|
|
801
|
-
async function exchangeLinearToken(body) {
|
|
802
|
-
const res = await httpFetch(LINEAR_TOKEN, {
|
|
803
|
-
method: "POST",
|
|
804
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
805
|
-
body
|
|
806
|
-
});
|
|
807
|
-
const data = await res.json().catch(() => ({}));
|
|
808
|
-
if (!res.ok || data.error || !data.access_token?.trim()) {
|
|
809
|
-
const detail = data.error_description || data.error || `HTTP ${res.status}`;
|
|
810
|
-
throw new Error(`Linear OAuth token exchange failed: ${detail}`);
|
|
811
|
-
}
|
|
812
|
-
return data;
|
|
813
|
-
}
|
|
814
|
-
async function persistLinearTokens(data, viewer) {
|
|
815
|
-
return saveLinearOAuth({
|
|
816
|
-
accessToken: data.access_token.trim(),
|
|
817
|
-
refreshToken: data.refresh_token?.trim(),
|
|
818
|
-
expiresIn: data.expires_in,
|
|
819
|
-
viewerName: viewer?.name,
|
|
820
|
-
organizationName: viewer?.organizationName
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
var refreshInFlight = null;
|
|
824
|
-
async function refreshLinearAccessToken(refreshToken) {
|
|
825
|
-
const { clientId, clientSecret } = linearOAuthCredentials();
|
|
826
|
-
const body = new URLSearchParams({
|
|
827
|
-
grant_type: "refresh_token",
|
|
828
|
-
refresh_token: refreshToken,
|
|
829
|
-
client_id: clientId
|
|
830
|
-
});
|
|
831
|
-
if (clientSecret) body.set("client_secret", clientSecret);
|
|
832
|
-
try {
|
|
833
|
-
const data = await exchangeLinearToken(body);
|
|
834
|
-
await persistLinearTokens(data);
|
|
835
|
-
return data.access_token.trim();
|
|
836
|
-
} catch (err) {
|
|
837
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
838
|
-
if (/invalid_grant|invalid_token|unauthorized/i.test(message)) {
|
|
839
|
-
disconnectLinearConnection();
|
|
840
|
-
throw new Error("Linear sign-in expired \u2014 connect again in Account settings");
|
|
841
|
-
}
|
|
842
|
-
throw err;
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
async function getLinearAuthToken(settings = loadAppSettings()) {
|
|
846
|
-
const access = settings.integrations.linearAccessToken?.trim() || "";
|
|
847
|
-
const refresh = settings.integrations.linearRefreshToken?.trim() || "";
|
|
848
|
-
const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
|
|
849
|
-
const apiKey = settings.integrations.linearApiKey?.trim() || "";
|
|
850
|
-
if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS)) {
|
|
851
|
-
return access;
|
|
852
|
-
}
|
|
853
|
-
if (refresh) {
|
|
854
|
-
if (!refreshInFlight) {
|
|
855
|
-
refreshInFlight = refreshLinearAccessToken(refresh).finally(() => {
|
|
856
|
-
refreshInFlight = null;
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
return refreshInFlight.catch((err) => {
|
|
860
|
-
if (apiKey) return apiKey;
|
|
861
|
-
throw err;
|
|
862
|
-
});
|
|
863
|
-
}
|
|
864
|
-
return apiKey || null;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// src/integrations/linear-attachments.ts
|
|
868
|
-
var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
|
|
869
|
-
var LINEAR_FILE_HOSTS = ["linear.app", "uploads.linear.app", "linearusercontent.com"];
|
|
870
|
-
var LINEAR_LINK_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
871
|
-
"github",
|
|
872
|
-
"githubPullRequest",
|
|
873
|
-
"gitlab",
|
|
874
|
-
"figma",
|
|
875
|
-
"slack",
|
|
876
|
-
"sentry",
|
|
877
|
-
"zendesk",
|
|
878
|
-
"intercom",
|
|
879
|
-
"front"
|
|
880
|
-
]);
|
|
881
|
-
var ATTACHMENT_QUERY = `
|
|
882
|
-
query SideboardAttachment($id: String!) {
|
|
883
|
-
attachment(id: $id) {
|
|
884
|
-
id
|
|
885
|
-
title
|
|
886
|
-
url
|
|
887
|
-
subtitle
|
|
888
|
-
sourceType
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
`;
|
|
892
|
-
function mapLinearAttachments(node) {
|
|
893
|
-
const listed = [];
|
|
894
|
-
for (const attachment of node.attachments?.nodes ?? []) {
|
|
895
|
-
const id = String(attachment.id ?? "");
|
|
896
|
-
const url = String(attachment.url ?? "").trim();
|
|
897
|
-
if (!id && !url) continue;
|
|
898
|
-
listed.push({
|
|
899
|
-
id,
|
|
900
|
-
title: String(attachment.title ?? ""),
|
|
901
|
-
url,
|
|
902
|
-
subtitle: attachment.subtitle?.trim() || void 0,
|
|
903
|
-
sourceType: attachment.sourceType?.trim() || void 0
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
const embedded = [
|
|
907
|
-
...extractHttpUrls(node.description).filter((url) => hostMatches(url, LINEAR_FILE_HOSTS)),
|
|
908
|
-
...(node.comments?.nodes ?? []).flatMap(
|
|
909
|
-
(comment) => extractHttpUrls(comment.body).filter((url) => hostMatches(url, LINEAR_FILE_HOSTS))
|
|
910
|
-
)
|
|
911
|
-
];
|
|
912
|
-
const merged = mergeIssueAttachments([
|
|
913
|
-
...listed.map((item) => ({
|
|
914
|
-
id: item.id,
|
|
915
|
-
name: item.title || item.subtitle || "attachment",
|
|
916
|
-
url: item.url,
|
|
917
|
-
sourceType: item.sourceType
|
|
918
|
-
})),
|
|
919
|
-
...embedded.map((url) => ({
|
|
920
|
-
id: url,
|
|
921
|
-
name: url.split("/").pop() || "image",
|
|
922
|
-
url,
|
|
923
|
-
sourceType: "upload"
|
|
924
|
-
}))
|
|
925
|
-
]);
|
|
926
|
-
const byUrl = new Map(listed.map((item) => [item.url, item]));
|
|
927
|
-
return merged.map((item) => {
|
|
928
|
-
const prior = byUrl.get(item.url);
|
|
929
|
-
return {
|
|
930
|
-
id: prior?.id || item.id,
|
|
931
|
-
title: prior?.title || item.name,
|
|
932
|
-
url: item.url,
|
|
933
|
-
subtitle: prior?.subtitle,
|
|
934
|
-
sourceType: prior?.sourceType || item.sourceType
|
|
935
|
-
};
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
function assertLinearFileUrl(url, sourceType) {
|
|
939
|
-
const kind = sourceType?.trim();
|
|
940
|
-
if (kind && LINEAR_LINK_SOURCE_TYPES.has(kind)) {
|
|
941
|
-
throw new Error(
|
|
942
|
-
`Linear attachment is a ${kind} link, not a file. Open the URL in the browser instead of downloading it.`
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
if (!hostMatches(url, LINEAR_FILE_HOSTS)) {
|
|
946
|
-
throw new Error(
|
|
947
|
-
`Linear attachment URL is not a Linear upload (${url}). Only uploads.linear.app / linear.app file hosts can be downloaded with the Linear token.`
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
async function requireLinearToken(apiKey) {
|
|
952
|
-
const token = (apiKey ?? await getLinearAuthToken())?.trim();
|
|
953
|
-
if (!token) {
|
|
954
|
-
throw new Error("Linear is not connected \u2014 sign in from Account settings");
|
|
955
|
-
}
|
|
956
|
-
return token;
|
|
957
|
-
}
|
|
958
|
-
async function downloadLinearAttachment(input, opts) {
|
|
959
|
-
const id = input.id.trim();
|
|
960
|
-
if (!id) throw new Error("Linear attachment id or URL is required");
|
|
961
|
-
const apiKey = await requireLinearToken(opts?.apiKey);
|
|
962
|
-
let url = id;
|
|
963
|
-
let name = input.name?.trim();
|
|
964
|
-
let sourceType;
|
|
965
|
-
if (!looksLikeHttpUrl(id)) {
|
|
966
|
-
const res = await httpFetch(LINEAR_GRAPHQL, {
|
|
967
|
-
method: "POST",
|
|
968
|
-
headers: {
|
|
969
|
-
"Content-Type": "application/json",
|
|
970
|
-
Authorization: linearAuthorizationHeader(apiKey)
|
|
971
|
-
},
|
|
972
|
-
body: JSON.stringify({ query: ATTACHMENT_QUERY, variables: { id } })
|
|
973
|
-
});
|
|
974
|
-
if (!res.ok) {
|
|
975
|
-
throw new Error(`Linear API error ${res.status} looking up attachment ${id}`);
|
|
976
|
-
}
|
|
977
|
-
const json = await res.json();
|
|
978
|
-
if (json.errors?.length) {
|
|
979
|
-
throw new Error(json.errors.map((e) => e.message ?? "Linear error").join("; "));
|
|
980
|
-
}
|
|
981
|
-
const attachment = json.data?.attachment;
|
|
982
|
-
if (!attachment?.url?.trim()) {
|
|
983
|
-
throw new Error(`Linear attachment not found: ${id}`);
|
|
984
|
-
}
|
|
985
|
-
url = attachment.url.trim();
|
|
986
|
-
name = name || attachment.title?.trim() || attachment.subtitle?.trim() || void 0;
|
|
987
|
-
sourceType = attachment.sourceType?.trim() || void 0;
|
|
988
|
-
}
|
|
989
|
-
assertLinearFileUrl(url, sourceType);
|
|
990
|
-
return downloadAuthenticatedFile({
|
|
991
|
-
url,
|
|
992
|
-
headers: { Authorization: linearAuthorizationHeader(apiKey) },
|
|
993
|
-
worktreePath: issueAttachmentWorktreePath(opts?.destPath),
|
|
994
|
-
name
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
// src/integrations/linear.ts
|
|
999
|
-
var LINEAR_GRAPHQL2 = "https://api.linear.app/graphql";
|
|
1000
|
-
var LIST_ISSUE_FIELDS = `
|
|
1001
|
-
id
|
|
1002
|
-
identifier
|
|
1003
|
-
title
|
|
1004
|
-
url
|
|
1005
|
-
createdAt
|
|
1006
|
-
updatedAt
|
|
1007
|
-
assignee { id name }
|
|
1008
|
-
team { id key }
|
|
1009
|
-
labels(first: 10) { nodes { name } }
|
|
1010
|
-
cycle { id name number startsAt endsAt completedAt }
|
|
1011
|
-
project { id name }
|
|
1012
|
-
`;
|
|
1013
|
-
var ISSUE_REF_FIELDS = `
|
|
1014
|
-
id
|
|
1015
|
-
identifier
|
|
1016
|
-
title
|
|
1017
|
-
url
|
|
1018
|
-
`;
|
|
1019
|
-
var ISSUE_FIELDS = `
|
|
1020
|
-
id
|
|
1021
|
-
identifier
|
|
1022
|
-
title
|
|
1023
|
-
description
|
|
1024
|
-
url
|
|
1025
|
-
priority
|
|
1026
|
-
estimate
|
|
1027
|
-
dueDate
|
|
1028
|
-
branchName
|
|
1029
|
-
createdAt
|
|
1030
|
-
updatedAt
|
|
1031
|
-
completedAt
|
|
1032
|
-
state { id name type }
|
|
1033
|
-
assignee { id name }
|
|
1034
|
-
creator { id name }
|
|
1035
|
-
team { id key name }
|
|
1036
|
-
labels(first: 50) { nodes { name } }
|
|
1037
|
-
cycle { id name number startsAt endsAt completedAt }
|
|
1038
|
-
project { id name }
|
|
1039
|
-
parent { ${ISSUE_REF_FIELDS} }
|
|
1040
|
-
children(first: 25) { nodes { ${ISSUE_REF_FIELDS} } }
|
|
1041
|
-
relations(first: 25) {
|
|
1042
|
-
nodes {
|
|
1043
|
-
id
|
|
1044
|
-
type
|
|
1045
|
-
relatedIssue { ${ISSUE_REF_FIELDS} }
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
inverseRelations(first: 25) {
|
|
1049
|
-
nodes {
|
|
1050
|
-
id
|
|
1051
|
-
type
|
|
1052
|
-
issue { ${ISSUE_REF_FIELDS} }
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
comments(first: 50) {
|
|
1056
|
-
nodes {
|
|
1057
|
-
id
|
|
1058
|
-
body
|
|
1059
|
-
url
|
|
1060
|
-
createdAt
|
|
1061
|
-
updatedAt
|
|
1062
|
-
user { id name }
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
attachments(first: 20) {
|
|
1066
|
-
nodes { id title url subtitle sourceType }
|
|
1067
|
-
}
|
|
1068
|
-
`;
|
|
1069
|
-
var ASSIGNED_ISSUES_QUERY = `
|
|
1070
|
-
query SideboardAssignedIssues($first: Int!, $filter: IssueFilter) {
|
|
1071
|
-
viewer {
|
|
1072
|
-
id
|
|
1073
|
-
name
|
|
1074
|
-
assignedIssues(
|
|
1075
|
-
first: $first
|
|
1076
|
-
orderBy: updatedAt
|
|
1077
|
-
filter: $filter
|
|
1078
|
-
) {
|
|
1079
|
-
nodes { ${LIST_ISSUE_FIELDS} }
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
`;
|
|
1084
|
-
var COMMENTS_SINCE_QUERY = `
|
|
1085
|
-
query SideboardCommentsSince($first: Int!, $filter: CommentFilter) {
|
|
1086
|
-
viewer { id name }
|
|
1087
|
-
comments(first: $first, orderBy: createdAt, filter: $filter) {
|
|
1088
|
-
nodes {
|
|
1089
|
-
id
|
|
1090
|
-
body
|
|
1091
|
-
url
|
|
1092
|
-
createdAt
|
|
1093
|
-
user { id name }
|
|
1094
|
-
issue { identifier title url }
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
`;
|
|
1099
|
-
var ISSUES_QUERY = `
|
|
1100
|
-
query SideboardIssues($first: Int!, $filter: IssueFilter) {
|
|
1101
|
-
viewer { id name }
|
|
1102
|
-
issues(first: $first, orderBy: updatedAt, filter: $filter) {
|
|
1103
|
-
nodes { ${LIST_ISSUE_FIELDS} }
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
`;
|
|
1107
|
-
var SEARCH_ISSUES_QUERY = `
|
|
1108
|
-
query SideboardSearchIssues($term: String!, $first: Int!, $filter: IssueFilter) {
|
|
1109
|
-
viewer { id name }
|
|
1110
|
-
searchIssues(term: $term, first: $first, includeArchived: false, filter: $filter) {
|
|
1111
|
-
nodes { ${LIST_ISSUE_FIELDS} }
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
`;
|
|
1115
|
-
var TEAMS_QUERY = `
|
|
1116
|
-
query SideboardTeams {
|
|
1117
|
-
viewer { id name }
|
|
1118
|
-
teams(first: 50) {
|
|
1119
|
-
nodes {
|
|
1120
|
-
id
|
|
1121
|
-
key
|
|
1122
|
-
name
|
|
1123
|
-
states(first: 50) { nodes { id name type } }
|
|
1124
|
-
activeCycle { id name number startsAt endsAt completedAt }
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
`;
|
|
1129
|
-
var TEAM_CYCLES_QUERY = `
|
|
1130
|
-
query SideboardTeamCycles($id: String!) {
|
|
1131
|
-
team(id: $id) {
|
|
1132
|
-
id
|
|
1133
|
-
key
|
|
1134
|
-
activeCycle { id name number startsAt endsAt completedAt }
|
|
1135
|
-
cycles(first: 40) {
|
|
1136
|
-
nodes { id name number startsAt endsAt completedAt }
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
`;
|
|
1141
|
-
var ISSUE_QUERY = `
|
|
1142
|
-
query SideboardIssue($id: String!) {
|
|
1143
|
-
issue(id: $id) { ${ISSUE_FIELDS} }
|
|
1144
|
-
}
|
|
1145
|
-
`;
|
|
1146
|
-
var ISSUE_CREATE = `
|
|
1147
|
-
mutation SideboardIssueCreate($input: IssueCreateInput!) {
|
|
1148
|
-
issueCreate(input: $input) {
|
|
1149
|
-
success
|
|
1150
|
-
issue { ${ISSUE_FIELDS} }
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
`;
|
|
1154
|
-
var ISSUE_UPDATE = `
|
|
1155
|
-
mutation SideboardIssueUpdate($id: String!, $input: IssueUpdateInput!) {
|
|
1156
|
-
issueUpdate(id: $id, input: $input) {
|
|
1157
|
-
success
|
|
1158
|
-
issue { ${ISSUE_FIELDS} }
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
`;
|
|
1162
|
-
var COMMENT_CREATE = `
|
|
1163
|
-
mutation SideboardCommentCreate($input: CommentCreateInput!) {
|
|
1164
|
-
commentCreate(input: $input) {
|
|
1165
|
-
success
|
|
1166
|
-
comment { id body url }
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
`;
|
|
1170
|
-
var LABELS_QUERY = `
|
|
1171
|
-
query SideboardLabels($first: Int!) {
|
|
1172
|
-
issueLabels(first: $first) {
|
|
1173
|
-
nodes { id name }
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
`;
|
|
1177
|
-
var TEAM_LABELS_QUERY = `
|
|
1178
|
-
query SideboardTeamLabels($id: String!, $first: Int!, $after: String) {
|
|
1179
|
-
team(id: $id) {
|
|
1180
|
-
labels(first: $first, after: $after) {
|
|
1181
|
-
nodes { id name }
|
|
1182
|
-
pageInfo { hasNextPage endCursor }
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
`;
|
|
1187
|
-
var PROJECTS_QUERY = `
|
|
1188
|
-
query SideboardProjects($first: Int!, $filter: ProjectFilter, $after: String) {
|
|
1189
|
-
projects(first: $first, filter: $filter, after: $after) {
|
|
1190
|
-
nodes { id name slugId }
|
|
1191
|
-
pageInfo { hasNextPage endCursor }
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
`;
|
|
1195
|
-
var ISSUE_RELATION_CREATE = `
|
|
1196
|
-
mutation SideboardIssueRelationCreate($input: IssueRelationCreateInput!) {
|
|
1197
|
-
issueRelationCreate(input: $input) {
|
|
1198
|
-
success
|
|
1199
|
-
issueRelation { id type }
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
`;
|
|
1203
|
-
var ISSUE_RELATION_DELETE = `
|
|
1204
|
-
mutation SideboardIssueRelationDelete($id: String!) {
|
|
1205
|
-
issueRelationDelete(id: $id) {
|
|
1206
|
-
success
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
`;
|
|
1210
|
-
function rewriteLinearError(message) {
|
|
1211
|
-
if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
|
|
1212
|
-
return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
|
|
1213
|
-
}
|
|
1214
|
-
return message;
|
|
1215
|
-
}
|
|
1216
|
-
async function requireLinearToken2(apiKey) {
|
|
1217
|
-
const token = (apiKey ?? await getLinearAuthToken())?.trim();
|
|
1218
|
-
if (!token) {
|
|
1219
|
-
throw new Error("Linear is not connected \u2014 sign in from Account settings");
|
|
1220
|
-
}
|
|
1221
|
-
return token;
|
|
1222
|
-
}
|
|
1223
|
-
async function linearGraphql(query, variables, opts) {
|
|
1224
|
-
const apiKey = await requireLinearToken2(opts?.apiKey);
|
|
1225
|
-
const res = await httpFetch(LINEAR_GRAPHQL2, {
|
|
1226
|
-
method: "POST",
|
|
1227
|
-
headers: {
|
|
1228
|
-
"Content-Type": "application/json",
|
|
1229
|
-
Authorization: linearAuthorizationHeader(apiKey)
|
|
1230
|
-
},
|
|
1231
|
-
body: JSON.stringify({ query, variables })
|
|
1232
|
-
});
|
|
1233
|
-
if (!res.ok) {
|
|
1234
|
-
const body = await res.text().catch(() => "");
|
|
1235
|
-
throw new Error(
|
|
1236
|
-
rewriteLinearError(
|
|
1237
|
-
`Linear API error ${res.status}${body ? `: ${body.slice(0, 200)}` : ""}`
|
|
1238
|
-
)
|
|
1239
|
-
);
|
|
1240
|
-
}
|
|
1241
|
-
const json = await res.json();
|
|
1242
|
-
if (json.errors?.length) {
|
|
1243
|
-
throw new Error(
|
|
1244
|
-
rewriteLinearError(json.errors.map((e) => e.message ?? "Linear error").join("; "))
|
|
1245
|
-
);
|
|
1246
|
-
}
|
|
1247
|
-
if (json.data == null) {
|
|
1248
|
-
throw new Error("Linear API returned no data");
|
|
1249
|
-
}
|
|
1250
|
-
return json.data;
|
|
1251
|
-
}
|
|
1252
|
-
function mapState(node) {
|
|
1253
|
-
if (!node?.id) return void 0;
|
|
1254
|
-
return {
|
|
1255
|
-
id: String(node.id),
|
|
1256
|
-
name: String(node.name ?? ""),
|
|
1257
|
-
type: String(node.type ?? "")
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
function linearCycleIsActive(cycle, now = Date.now()) {
|
|
1261
|
-
if (!cycle) return false;
|
|
1262
|
-
if (cycle.completedAt) return false;
|
|
1263
|
-
const start = cycle.startsAt ? Date.parse(cycle.startsAt) : Number.NaN;
|
|
1264
|
-
const end = cycle.endsAt ? Date.parse(cycle.endsAt) : Number.NaN;
|
|
1265
|
-
if (Number.isFinite(start) && now < start) return false;
|
|
1266
|
-
if (Number.isFinite(end) && now > end) return false;
|
|
1267
|
-
return true;
|
|
1268
|
-
}
|
|
1269
|
-
function cycleDisplayName(node) {
|
|
1270
|
-
return String(node?.name ?? (node?.number != null ? `Cycle ${node.number}` : "")).trim();
|
|
1271
|
-
}
|
|
1272
|
-
function mapNamedCycle(node) {
|
|
1273
|
-
if (!node?.id) return null;
|
|
1274
|
-
const name = cycleDisplayName(node);
|
|
1275
|
-
if (!name) return null;
|
|
1276
|
-
return {
|
|
1277
|
-
id: String(node.id),
|
|
1278
|
-
name,
|
|
1279
|
-
number: typeof node.number === "number" ? node.number : void 0,
|
|
1280
|
-
isActive: linearCycleIsActive(node)
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
function mapCycle(node) {
|
|
1284
|
-
if (!node?.name && node?.number == null) return null;
|
|
1285
|
-
const name = cycleDisplayName(node);
|
|
1286
|
-
if (!name) return null;
|
|
1287
|
-
return {
|
|
1288
|
-
...node.id ? { id: String(node.id) } : {},
|
|
1289
|
-
name,
|
|
1290
|
-
number: typeof node.number === "number" ? node.number : void 0,
|
|
1291
|
-
isActive: linearCycleIsActive(node)
|
|
1292
|
-
};
|
|
1293
|
-
}
|
|
1294
|
-
function mapUser(node) {
|
|
1295
|
-
if (!node?.id && !node?.name) return void 0;
|
|
1296
|
-
return {
|
|
1297
|
-
id: String(node.id ?? ""),
|
|
1298
|
-
name: String(node.name ?? "")
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
function mapIssueRef(node) {
|
|
1302
|
-
if (!node?.id && !node?.identifier) return void 0;
|
|
1303
|
-
return {
|
|
1304
|
-
id: String(node.id ?? node.identifier ?? ""),
|
|
1305
|
-
identifier: String(node.identifier ?? node.id ?? ""),
|
|
1306
|
-
title: String(node.title ?? ""),
|
|
1307
|
-
url: String(node.url ?? "")
|
|
1308
|
-
};
|
|
1309
|
-
}
|
|
1310
|
-
function flipLinearRelationType(type) {
|
|
1311
|
-
switch (type) {
|
|
1312
|
-
case "blocks":
|
|
1313
|
-
return "blockedBy";
|
|
1314
|
-
case "blockedBy":
|
|
1315
|
-
return "blocks";
|
|
1316
|
-
case "duplicate":
|
|
1317
|
-
return "duplicateOf";
|
|
1318
|
-
case "duplicateOf":
|
|
1319
|
-
return "duplicate";
|
|
1320
|
-
default:
|
|
1321
|
-
return type;
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
function mapRelations(node) {
|
|
1325
|
-
const out = [];
|
|
1326
|
-
for (const rel of node.relations?.nodes ?? []) {
|
|
1327
|
-
const issue = mapIssueRef(rel.relatedIssue);
|
|
1328
|
-
if (!issue) continue;
|
|
1329
|
-
const id = rel.id?.trim();
|
|
1330
|
-
out.push({ type: String(rel.type ?? "related"), issue, ...id ? { id } : {} });
|
|
1331
|
-
}
|
|
1332
|
-
for (const rel of node.inverseRelations?.nodes ?? []) {
|
|
1333
|
-
const issue = mapIssueRef(rel.issue);
|
|
1334
|
-
if (!issue) continue;
|
|
1335
|
-
const id = rel.id?.trim();
|
|
1336
|
-
out.push({
|
|
1337
|
-
type: flipLinearRelationType(String(rel.type ?? "related")),
|
|
1338
|
-
issue,
|
|
1339
|
-
...id ? { id } : {}
|
|
1340
|
-
});
|
|
1341
|
-
}
|
|
1342
|
-
return out;
|
|
1343
|
-
}
|
|
1344
|
-
function mapComments2(node) {
|
|
1345
|
-
const out = [];
|
|
1346
|
-
for (const comment of node.comments?.nodes ?? []) {
|
|
1347
|
-
const id = String(comment.id ?? "");
|
|
1348
|
-
const body = String(comment.body ?? "");
|
|
1349
|
-
if (!id && !body) continue;
|
|
1350
|
-
out.push({
|
|
1351
|
-
id,
|
|
1352
|
-
body,
|
|
1353
|
-
url: comment.url?.trim() || void 0,
|
|
1354
|
-
createdAt: comment.createdAt?.trim() || void 0,
|
|
1355
|
-
updatedAt: comment.updatedAt?.trim() || void 0,
|
|
1356
|
-
user: mapUser(comment.user)
|
|
1357
|
-
});
|
|
1358
|
-
}
|
|
1359
|
-
return out;
|
|
1360
|
-
}
|
|
1361
|
-
function mapIssue(node) {
|
|
1362
|
-
const team = node.team;
|
|
1363
|
-
return {
|
|
1364
|
-
id: String(node.id ?? node.identifier ?? ""),
|
|
1365
|
-
identifier: String(node.identifier ?? node.id ?? ""),
|
|
1366
|
-
title: String(node.title ?? ""),
|
|
1367
|
-
url: String(node.url ?? ""),
|
|
1368
|
-
description: node.description?.trim() || void 0,
|
|
1369
|
-
priority: typeof node.priority === "number" ? node.priority : void 0,
|
|
1370
|
-
estimate: typeof node.estimate === "number" ? node.estimate : void 0,
|
|
1371
|
-
dueDate: node.dueDate?.trim() || void 0,
|
|
1372
|
-
branchName: node.branchName?.trim() || void 0,
|
|
1373
|
-
createdAt: node.createdAt?.trim() || void 0,
|
|
1374
|
-
updatedAt: node.updatedAt?.trim() || void 0,
|
|
1375
|
-
completedAt: node.completedAt?.trim() || void 0,
|
|
1376
|
-
state: mapState(node.state),
|
|
1377
|
-
assignee: mapUser(node.assignee),
|
|
1378
|
-
creator: mapUser(node.creator),
|
|
1379
|
-
team: team?.id ? {
|
|
1380
|
-
id: String(team.id),
|
|
1381
|
-
key: String(team.key ?? ""),
|
|
1382
|
-
name: String(team.name ?? ""),
|
|
1383
|
-
states: (team.states?.nodes ?? []).flatMap((s) => {
|
|
1384
|
-
const state = mapState(s);
|
|
1385
|
-
return state ? [state] : [];
|
|
1386
|
-
})
|
|
1387
|
-
} : void 0,
|
|
1388
|
-
labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n)),
|
|
1389
|
-
cycle: mapCycle(node.cycle),
|
|
1390
|
-
project: node.project?.id ? { id: String(node.project.id), name: String(node.project.name ?? "") } : void 0,
|
|
1391
|
-
parent: mapIssueRef(node.parent),
|
|
1392
|
-
children: (node.children?.nodes ?? []).flatMap((child) => {
|
|
1393
|
-
const ref = mapIssueRef(child);
|
|
1394
|
-
return ref ? [ref] : [];
|
|
1395
|
-
}),
|
|
1396
|
-
relations: mapRelations(node),
|
|
1397
|
-
comments: mapComments2(node),
|
|
1398
|
-
attachments: mapLinearAttachments(node)
|
|
1399
|
-
};
|
|
1400
|
-
}
|
|
1401
|
-
function toIssueInfo(issue) {
|
|
1402
|
-
return {
|
|
1403
|
-
id: issue.id,
|
|
1404
|
-
identifier: issue.identifier,
|
|
1405
|
-
title: issue.title,
|
|
1406
|
-
url: issue.url,
|
|
1407
|
-
labels: issue.labels,
|
|
1408
|
-
provider: "linear",
|
|
1409
|
-
assignee: issue.assignee?.name,
|
|
1410
|
-
assignees: issue.assignee?.name ? [issue.assignee.name] : void 0,
|
|
1411
|
-
cycle: issue.cycle ?? null,
|
|
1412
|
-
teamKey: issue.team?.key || void 0,
|
|
1413
|
-
...issue.project ? { projects: [issue.project] } : {},
|
|
1414
|
-
...issue.createdAt ? { createdAt: issue.createdAt } : {},
|
|
1415
|
-
...issue.updatedAt ? { updatedAt: issue.updatedAt } : {}
|
|
1416
|
-
};
|
|
1417
|
-
}
|
|
1418
|
-
function mapTeam(node) {
|
|
1419
|
-
const cycles = (node.cycles?.nodes ?? []).flatMap((c) => {
|
|
1420
|
-
const cycle = mapNamedCycle(c);
|
|
1421
|
-
return cycle ? [cycle] : [];
|
|
1422
|
-
});
|
|
1423
|
-
return {
|
|
1424
|
-
id: String(node.id ?? ""),
|
|
1425
|
-
key: String(node.key ?? ""),
|
|
1426
|
-
name: String(node.name ?? ""),
|
|
1427
|
-
states: (node.states?.nodes ?? []).flatMap((s) => {
|
|
1428
|
-
const state = mapState(s);
|
|
1429
|
-
return state ? [state] : [];
|
|
1430
|
-
}),
|
|
1431
|
-
activeCycle: mapNamedCycle(node.activeCycle),
|
|
1432
|
-
...cycles.length ? { cycles } : {}
|
|
1433
|
-
};
|
|
1434
|
-
}
|
|
1435
|
-
function resolveLinearTeam(teams, team) {
|
|
1436
|
-
const t = team.trim();
|
|
1437
|
-
if (!t) throw new Error("Linear team is required (id, key, or name from linear_list_teams)");
|
|
1438
|
-
const lower = t.toLowerCase();
|
|
1439
|
-
const found = teams.find(
|
|
1440
|
-
(x) => x.id === t || x.key.toLowerCase() === lower || x.name.toLowerCase() === lower
|
|
1441
|
-
);
|
|
1442
|
-
if (!found) {
|
|
1443
|
-
const keys = teams.map((x) => x.key).filter(Boolean).join(", ") || "(none)";
|
|
1444
|
-
throw new Error(`Linear team not found: ${team}. Known keys: ${keys}`);
|
|
1445
|
-
}
|
|
1446
|
-
return found;
|
|
1447
|
-
}
|
|
1448
|
-
function resolveLinearState(team, state) {
|
|
1449
|
-
const s = state.trim();
|
|
1450
|
-
if (!s) throw new Error("Linear state is empty");
|
|
1451
|
-
const lower = s.toLowerCase();
|
|
1452
|
-
const found = team.states.find((x) => x.id === s) || team.states.find((x) => x.name.toLowerCase() === lower) || team.states.find((x) => x.type.toLowerCase() === lower);
|
|
1453
|
-
if (!found) {
|
|
1454
|
-
const available = team.states.map((x) => `${x.name} (${x.type})`).join(", ") || "(none)";
|
|
1455
|
-
throw new Error(`Linear state not found on ${team.key}: ${state}. Available: ${available}`);
|
|
1456
|
-
}
|
|
1457
|
-
return found;
|
|
1458
|
-
}
|
|
1459
|
-
function resolveLinearCycle(team, cycle) {
|
|
1460
|
-
if (cycle == null) return null;
|
|
1461
|
-
const s = cycle.trim();
|
|
1462
|
-
if (!s || /^(none|clear|unschedule|null)$/i.test(s)) return null;
|
|
1463
|
-
if (/^(current|active|this)$/i.test(s)) {
|
|
1464
|
-
const active = team.activeCycle ?? team.cycles?.find((c) => c.isActive);
|
|
1465
|
-
if (!active?.id) {
|
|
1466
|
-
throw new Error(`Linear team ${team.key} has no active cycle`);
|
|
1467
|
-
}
|
|
1468
|
-
return active.id;
|
|
1469
|
-
}
|
|
1470
|
-
const lower = s.toLowerCase();
|
|
1471
|
-
const asNumber = Number.parseInt(s, 10);
|
|
1472
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1473
|
-
const cycles = [team.activeCycle, ...team.cycles ?? []].filter(
|
|
1474
|
-
(c) => {
|
|
1475
|
-
if (!c?.id || seen.has(c.id)) return false;
|
|
1476
|
-
seen.add(c.id);
|
|
1477
|
-
return true;
|
|
1478
|
-
}
|
|
1479
|
-
);
|
|
1480
|
-
const found = cycles.find((c) => c.id === s) || cycles.find((c) => c.name.toLowerCase() === lower) || (Number.isInteger(asNumber) ? cycles.find((c) => c.number === asNumber) : void 0);
|
|
1481
|
-
if (!found) {
|
|
1482
|
-
const available = cycles.map((c) => c.number != null ? `${c.name} (${c.number})` : c.name).join(", ") || "(none)";
|
|
1483
|
-
throw new Error(`Linear cycle not found on ${team.key}: ${cycle}. Available: ${available}`);
|
|
1484
|
-
}
|
|
1485
|
-
return found.id;
|
|
1486
|
-
}
|
|
1487
|
-
var LINEAR_NONE_RE = /^(none|clear|unschedule|null)$/i;
|
|
1488
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1489
|
-
function isLinearNoneToken(value) {
|
|
1490
|
-
if (value == null) return true;
|
|
1491
|
-
const s = value.trim();
|
|
1492
|
-
return !s || LINEAR_NONE_RE.test(s);
|
|
1493
|
-
}
|
|
1494
|
-
function resolveLinearLabel(labels, name) {
|
|
1495
|
-
const s = name.trim();
|
|
1496
|
-
if (!s) throw new Error("Linear label is empty");
|
|
1497
|
-
const lower = s.toLowerCase();
|
|
1498
|
-
const found = labels.find((x) => x.id === s) || labels.find((x) => x.name.toLowerCase() === lower);
|
|
1499
|
-
if (!found) {
|
|
1500
|
-
const available = labels.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
1501
|
-
throw new Error(`Linear label not found: ${name}. Available: ${available}`);
|
|
1502
|
-
}
|
|
1503
|
-
return found;
|
|
1504
|
-
}
|
|
1505
|
-
function resolveLinearProject(projects, project) {
|
|
1506
|
-
if (isLinearNoneToken(project)) return null;
|
|
1507
|
-
const s = (project ?? "").trim();
|
|
1508
|
-
const lower = s.toLowerCase();
|
|
1509
|
-
const found = projects.find((x) => x.id === s) || projects.find((x) => x.name.toLowerCase() === lower) || projects.find((x) => x.slugId?.toLowerCase() === lower);
|
|
1510
|
-
if (!found) {
|
|
1511
|
-
const available = projects.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
1512
|
-
throw new Error(`Linear project not found: ${project}. Available: ${available}`);
|
|
1513
|
-
}
|
|
1514
|
-
return found.id;
|
|
1515
|
-
}
|
|
1516
|
-
function normalizeLinearRelationType(type) {
|
|
1517
|
-
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
1518
|
-
if (s === "blocks" || s === "block") return "blocks";
|
|
1519
|
-
if (s === "blockedby" || s === "blocked") return "blockedBy";
|
|
1520
|
-
if (s === "related" || s === "relates" || s === "relatesto") return "related";
|
|
1521
|
-
if (s === "duplicate" || s === "dup") return "duplicate";
|
|
1522
|
-
if (s === "duplicateof" || s === "original") return "duplicateOf";
|
|
1523
|
-
if (s === "similar") return "similar";
|
|
1524
|
-
throw new Error(
|
|
1525
|
-
`Linear relation type must be blocks, blockedBy, related, duplicate, or duplicateOf (got ${type})`
|
|
1526
|
-
);
|
|
1527
|
-
}
|
|
1528
|
-
function linearRelationCreateInput(thisIssueId, type, relatedIssueId) {
|
|
1529
|
-
const view = normalizeLinearRelationType(type);
|
|
1530
|
-
if (view === "blockedBy") {
|
|
1531
|
-
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "blocks" };
|
|
1532
|
-
}
|
|
1533
|
-
if (view === "duplicateOf") {
|
|
1534
|
-
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "duplicate" };
|
|
1535
|
-
}
|
|
1536
|
-
return { issueId: thisIssueId, relatedIssueId, type: view };
|
|
1537
|
-
}
|
|
1538
|
-
function relationMatches(rel, type, issueRef) {
|
|
1539
|
-
if (rel.type.toLowerCase() !== type.toLowerCase()) return false;
|
|
1540
|
-
const ref = issueRef.trim().toLowerCase();
|
|
1541
|
-
return rel.issue.id.toLowerCase() === ref || rel.issue.identifier.toLowerCase() === ref;
|
|
1542
|
-
}
|
|
1543
|
-
function formatRelationList(relations) {
|
|
1544
|
-
if (!relations.length) return "(none)";
|
|
1545
|
-
return relations.map((r) => `${r.type} ${r.issue.identifier || r.issue.id}`).join(", ");
|
|
1546
|
-
}
|
|
1547
|
-
function mapLabelNodes(nodes) {
|
|
1548
|
-
return (nodes ?? []).flatMap((node) => {
|
|
1549
|
-
const id = String(node.id ?? "").trim();
|
|
1550
|
-
const name = String(node.name ?? "").trim();
|
|
1551
|
-
if (!id) return [];
|
|
1552
|
-
return [{ id, name }];
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
async function listLinearWorkspaceLabels(opts) {
|
|
1556
|
-
const json = await linearGraphql(LABELS_QUERY, { first: 250 }, opts);
|
|
1557
|
-
return mapLabelNodes(json.issueLabels?.nodes);
|
|
1558
|
-
}
|
|
1559
|
-
async function listLinearLabels(opts) {
|
|
1560
|
-
const teamId = opts?.teamId?.trim();
|
|
1561
|
-
if (teamId) {
|
|
1562
|
-
const out = [];
|
|
1563
|
-
let after;
|
|
1564
|
-
for (let page = 0; page < 4; page++) {
|
|
1565
|
-
const json = await linearGraphql(
|
|
1566
|
-
TEAM_LABELS_QUERY,
|
|
1567
|
-
after ? { id: teamId, first: 250, after } : { id: teamId, first: 250 },
|
|
1568
|
-
opts
|
|
1569
|
-
);
|
|
1570
|
-
const conn = json.team?.labels;
|
|
1571
|
-
out.push(...mapLabelNodes(conn?.nodes));
|
|
1572
|
-
if (!conn?.pageInfo?.hasNextPage || !conn.pageInfo.endCursor) break;
|
|
1573
|
-
after = conn.pageInfo.endCursor;
|
|
1574
|
-
}
|
|
1575
|
-
if (out.length) return out;
|
|
1576
|
-
}
|
|
1577
|
-
return listLinearWorkspaceLabels(opts);
|
|
1578
|
-
}
|
|
1579
|
-
async function queryLinearProjects(filter, opts) {
|
|
1580
|
-
const out = [];
|
|
1581
|
-
let after;
|
|
1582
|
-
for (let page = 0; page < 4; page++) {
|
|
1583
|
-
const json = await linearGraphql(PROJECTS_QUERY, { first: 50, filter: filter ?? {}, ...after ? { after } : {} }, opts);
|
|
1584
|
-
for (const node of json.projects?.nodes ?? []) {
|
|
1585
|
-
const id = String(node.id ?? "").trim();
|
|
1586
|
-
if (!id) continue;
|
|
1587
|
-
out.push({ id, name: String(node.name ?? ""), ...node.slugId ? { slugId: String(node.slugId) } : {} });
|
|
1588
|
-
}
|
|
1589
|
-
const pageInfo = json.projects?.pageInfo;
|
|
1590
|
-
if (!pageInfo?.hasNextPage || !pageInfo.endCursor) break;
|
|
1591
|
-
after = pageInfo.endCursor;
|
|
1592
|
-
}
|
|
1593
|
-
return out;
|
|
1594
|
-
}
|
|
1595
|
-
function listLinearProjects(opts) {
|
|
1596
|
-
return queryLinearProjects(void 0, opts);
|
|
1597
|
-
}
|
|
1598
|
-
async function resolveLinearProjectId(project, opts) {
|
|
1599
|
-
if (isLinearNoneToken(project)) return null;
|
|
1600
|
-
const s = (project ?? "").trim();
|
|
1601
|
-
const filter = UUID_RE.test(s) ? { id: { eq: s } } : { name: { eqIgnoreCase: s } };
|
|
1602
|
-
const matched = await queryLinearProjects(filter, opts);
|
|
1603
|
-
try {
|
|
1604
|
-
return resolveLinearProject(matched, s);
|
|
1605
|
-
} catch {
|
|
1606
|
-
if (UUID_RE.test(s)) return s;
|
|
1607
|
-
const listed = await queryLinearProjects(void 0, opts);
|
|
1608
|
-
return resolveLinearProject(listed, s);
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
async function resolveLinearLabelIds(names, opts) {
|
|
1612
|
-
if (names.length === 0) return [];
|
|
1613
|
-
const labels = await listLinearLabels(opts);
|
|
1614
|
-
return names.map((name) => {
|
|
1615
|
-
const s = name.trim();
|
|
1616
|
-
try {
|
|
1617
|
-
return resolveLinearLabel(labels, s).id;
|
|
1618
|
-
} catch (error) {
|
|
1619
|
-
if (UUID_RE.test(s)) return s;
|
|
1620
|
-
throw error;
|
|
1621
|
-
}
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
1624
|
-
async function applyLinearIssueRelations(issue, add, remove, opts) {
|
|
1625
|
-
const current = [...issue.relations];
|
|
1626
|
-
for (const rel of remove ?? []) {
|
|
1627
|
-
const type = normalizeLinearRelationType(rel.type);
|
|
1628
|
-
const issueRef = rel.issue.trim();
|
|
1629
|
-
if (!issueRef) throw new Error("Linear removeRelations.issue is required (uuid or ENG-123)");
|
|
1630
|
-
const match = current.find((r) => relationMatches(r, type, issueRef));
|
|
1631
|
-
if (!match?.id) {
|
|
1632
|
-
throw new Error(
|
|
1633
|
-
`Linear relation not found: ${type} ${issueRef}. Existing: ${formatRelationList(current)}`
|
|
1634
|
-
);
|
|
1635
|
-
}
|
|
1636
|
-
const json = await linearGraphql(ISSUE_RELATION_DELETE, { id: match.id }, opts);
|
|
1637
|
-
if (!json.issueRelationDelete?.success) {
|
|
1638
|
-
throw new Error(`Linear issueRelationDelete failed (${type} ${issueRef})`);
|
|
1639
|
-
}
|
|
1640
|
-
const idx = current.indexOf(match);
|
|
1641
|
-
if (idx >= 0) current.splice(idx, 1);
|
|
1642
|
-
}
|
|
1643
|
-
for (const rel of add ?? []) {
|
|
1644
|
-
const type = normalizeLinearRelationType(rel.type);
|
|
1645
|
-
const issueRef = rel.issue.trim();
|
|
1646
|
-
if (!issueRef) throw new Error("Linear relations.issue is required (uuid or ENG-123)");
|
|
1647
|
-
if (current.some((r) => relationMatches(r, type, issueRef))) continue;
|
|
1648
|
-
const related = await getLinearIssue(issueRef, opts);
|
|
1649
|
-
const input = linearRelationCreateInput(issue.id, type, related.id);
|
|
1650
|
-
const json = await linearGraphql(ISSUE_RELATION_CREATE, { input }, opts);
|
|
1651
|
-
if (!json.issueRelationCreate?.success) {
|
|
1652
|
-
throw new Error(`Linear issueRelationCreate failed (${type} ${related.identifier})`);
|
|
1653
|
-
}
|
|
1654
|
-
current.push({ type, issue: related, id: void 0 });
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
function normalizePriority(priority) {
|
|
1658
|
-
if (priority == null) return void 0;
|
|
1659
|
-
if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
|
|
1660
|
-
throw new Error("Linear priority must be 0 (none), 1 (urgent), 2 (high), 3 (medium), or 4 (low)");
|
|
1661
|
-
}
|
|
1662
|
-
return priority;
|
|
1663
|
-
}
|
|
1664
|
-
function assigneeFilterKey(assignee) {
|
|
1665
|
-
return (assignee ?? "").trim();
|
|
1666
|
-
}
|
|
1667
|
-
function applyLinearIssueQueryFilter(filter, query) {
|
|
1668
|
-
const term = query?.trim();
|
|
1669
|
-
if (!term) return;
|
|
1670
|
-
const or = [
|
|
1671
|
-
{ title: { containsIgnoreCase: term } },
|
|
1672
|
-
{ description: { containsIgnoreCase: term } }
|
|
1673
|
-
];
|
|
1674
|
-
const identifierMatch = term.match(/^[A-Za-z][\w]*-(\d+)$/);
|
|
1675
|
-
const asNumber = identifierMatch ? Number(identifierMatch[1]) : /^\d+$/.test(term) ? Number(term) : NaN;
|
|
1676
|
-
if (Number.isFinite(asNumber) && asNumber > 0) {
|
|
1677
|
-
or.push({ number: { eq: asNumber } });
|
|
1678
|
-
}
|
|
1679
|
-
filter.or = or;
|
|
1680
|
-
}
|
|
1681
|
-
function buildLinearIssueFilter(input) {
|
|
1682
|
-
const filter = {
|
|
1683
|
-
state: { type: { nin: ["completed", "canceled"] } }
|
|
1684
|
-
};
|
|
1685
|
-
const since = input?.updatedSince?.trim();
|
|
1686
|
-
if (since) filter.updatedAt = { gte: since };
|
|
1687
|
-
applyLinearIssueQueryFilter(filter, input?.query);
|
|
1688
|
-
const raw = assigneeFilterKey(input?.assignee) || "me";
|
|
1689
|
-
const key = raw.toLowerCase();
|
|
1690
|
-
if (key === "all" || key === "*") return filter;
|
|
1691
|
-
if (key === "unassigned" || key === "none" || key === "null") {
|
|
1692
|
-
filter.assignee = { null: true };
|
|
1693
|
-
return filter;
|
|
1694
|
-
}
|
|
1695
|
-
if (key === "me" || key === "@me") {
|
|
1696
|
-
filter.assignee = { isMe: { eq: true } };
|
|
1697
|
-
return filter;
|
|
1698
|
-
}
|
|
1699
|
-
if (UUID_RE.test(raw)) {
|
|
1700
|
-
filter.assignee = { id: { eq: raw } };
|
|
1701
|
-
return filter;
|
|
1702
|
-
}
|
|
1703
|
-
filter.assignee = { name: { eqIgnoreCase: raw } };
|
|
1704
|
-
return filter;
|
|
1705
|
-
}
|
|
1706
|
-
function isAssignedToMe(assignee) {
|
|
1707
|
-
const key = assignee.toLowerCase();
|
|
1708
|
-
return key === "me" || key === "@me";
|
|
1709
|
-
}
|
|
1710
|
-
function listedLinearIssues(json, nodes) {
|
|
1711
|
-
return {
|
|
1712
|
-
viewer: {
|
|
1713
|
-
id: String(json.viewer?.id ?? ""),
|
|
1714
|
-
name: String(json.viewer?.name ?? "")
|
|
1715
|
-
},
|
|
1716
|
-
issues: (nodes ?? []).map((node) => toIssueInfo(mapIssue(node)))
|
|
1717
|
-
};
|
|
1718
|
-
}
|
|
1719
|
-
async function listLinearIssuesFiltered(opts) {
|
|
1720
|
-
const first = Math.max(1, Math.min(250, opts?.limit ?? 200));
|
|
1721
|
-
const query = opts?.query?.trim() ?? "";
|
|
1722
|
-
const assignee = assigneeFilterKey(opts?.assignee) || (query ? "all" : "me");
|
|
1723
|
-
const filter = buildLinearIssueFilter({
|
|
1724
|
-
assignee,
|
|
1725
|
-
updatedSince: opts?.updatedSince
|
|
1726
|
-
});
|
|
1727
|
-
if (query) {
|
|
1728
|
-
const json2 = await linearGraphql(
|
|
1729
|
-
SEARCH_ISSUES_QUERY,
|
|
1730
|
-
{ term: query, first, filter },
|
|
1731
|
-
opts
|
|
1732
|
-
);
|
|
1733
|
-
return listedLinearIssues(json2, json2.searchIssues?.nodes);
|
|
1734
|
-
}
|
|
1735
|
-
if (isAssignedToMe(assignee)) {
|
|
1736
|
-
const json2 = await linearGraphql(
|
|
1737
|
-
ASSIGNED_ISSUES_QUERY,
|
|
1738
|
-
{ first, filter },
|
|
1739
|
-
opts
|
|
1740
|
-
);
|
|
1741
|
-
return listedLinearIssues(json2, json2.viewer?.assignedIssues?.nodes);
|
|
1742
|
-
}
|
|
1743
|
-
const json = await linearGraphql(
|
|
1744
|
-
ISSUES_QUERY,
|
|
1745
|
-
{ first, filter },
|
|
1746
|
-
opts
|
|
1747
|
-
);
|
|
1748
|
-
return listedLinearIssues(json, json.issues?.nodes);
|
|
1749
|
-
}
|
|
1750
|
-
async function listLinearCommentsSince(opts) {
|
|
1751
|
-
const first = Math.max(1, Math.min(250, opts.limit ?? 200));
|
|
1752
|
-
const query = opts.query?.trim() ?? "";
|
|
1753
|
-
const restrictToIssues = opts.issueIdentifiers != null;
|
|
1754
|
-
const allowed = new Set(
|
|
1755
|
-
(opts.issueIdentifiers ?? []).map((id) => id.trim()).filter(Boolean)
|
|
1756
|
-
);
|
|
1757
|
-
if (restrictToIssues && allowed.size === 0) return [];
|
|
1758
|
-
const assignee = assigneeFilterKey(opts.assignee) || (query ? "all" : "me");
|
|
1759
|
-
const json = await linearGraphql(
|
|
1760
|
-
COMMENTS_SINCE_QUERY,
|
|
1761
|
-
{
|
|
1762
|
-
first,
|
|
1763
|
-
filter: {
|
|
1764
|
-
createdAt: { gte: opts.since },
|
|
1765
|
-
issue: buildLinearIssueFilter({
|
|
1766
|
-
assignee,
|
|
1767
|
-
// Search hits may match identifier/comments — do not also require
|
|
1768
|
-
// title/description contains when an allowlist is already applied.
|
|
1769
|
-
query: restrictToIssues ? void 0 : query || void 0
|
|
1770
|
-
})
|
|
1771
|
-
}
|
|
1772
|
-
},
|
|
1773
|
-
opts
|
|
1774
|
-
);
|
|
1775
|
-
const out = [];
|
|
1776
|
-
for (const node of json.comments?.nodes ?? []) {
|
|
1777
|
-
const identifier = String(node.issue?.identifier ?? "").trim();
|
|
1778
|
-
if (restrictToIssues && !allowed.has(identifier)) continue;
|
|
1779
|
-
const body = previewIssueCommentBody(String(node.body ?? ""));
|
|
1780
|
-
if (!identifier && !body) continue;
|
|
1781
|
-
out.push({
|
|
1782
|
-
identifier: identifier || String(node.id ?? ""),
|
|
1783
|
-
...node.issue?.title ? { title: String(node.issue.title) } : {},
|
|
1784
|
-
...node.user?.name ? { author: String(node.user.name) } : {},
|
|
1785
|
-
...node.createdAt?.trim() ? { createdAt: node.createdAt.trim() } : {},
|
|
1786
|
-
body,
|
|
1787
|
-
...node.url?.trim() ? { url: node.url.trim() } : {}
|
|
1788
|
-
});
|
|
1789
|
-
}
|
|
1790
|
-
return out;
|
|
1791
|
-
}
|
|
1792
|
-
async function listLinearTeams(opts) {
|
|
1793
|
-
const json = await linearGraphql(TEAMS_QUERY, void 0, opts);
|
|
1794
|
-
return {
|
|
1795
|
-
viewer: {
|
|
1796
|
-
id: String(json.viewer?.id ?? ""),
|
|
1797
|
-
name: String(json.viewer?.name ?? "")
|
|
1798
|
-
},
|
|
1799
|
-
teams: (json.teams?.nodes ?? []).map(mapTeam)
|
|
1800
|
-
};
|
|
1801
|
-
}
|
|
1802
|
-
async function listLinearTeamCycles(teamId, opts) {
|
|
1803
|
-
const json = await linearGraphql(TEAM_CYCLES_QUERY, { id: teamId }, opts);
|
|
1804
|
-
if (!json.team?.id) {
|
|
1805
|
-
throw new Error(`Linear team not found: ${teamId}`);
|
|
1806
|
-
}
|
|
1807
|
-
const mapped = mapTeam(json.team);
|
|
1808
|
-
return {
|
|
1809
|
-
id: mapped.id,
|
|
1810
|
-
key: mapped.key,
|
|
1811
|
-
activeCycle: mapped.activeCycle,
|
|
1812
|
-
cycles: mapped.cycles ?? []
|
|
1813
|
-
};
|
|
1814
|
-
}
|
|
1815
|
-
async function getLinearIssue(id, opts) {
|
|
1816
|
-
const issueId = id.trim();
|
|
1817
|
-
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
1818
|
-
const json = await linearGraphql(
|
|
1819
|
-
ISSUE_QUERY,
|
|
1820
|
-
{ id: issueId },
|
|
1821
|
-
opts
|
|
1822
|
-
);
|
|
1823
|
-
if (!json.issue) {
|
|
1824
|
-
throw new Error(`Linear issue not found: ${issueId}`);
|
|
1825
|
-
}
|
|
1826
|
-
return mapIssue(json.issue);
|
|
1827
|
-
}
|
|
1828
|
-
async function createLinearIssue(input, opts) {
|
|
1829
|
-
const title = input.title.trim();
|
|
1830
|
-
if (!title) throw new Error("Linear issue title is required");
|
|
1831
|
-
const { viewer, teams } = await listLinearTeams(opts);
|
|
1832
|
-
const team = resolveLinearTeam(teams, input.team);
|
|
1833
|
-
const mutationInput = {
|
|
1834
|
-
teamId: team.id,
|
|
1835
|
-
title
|
|
1836
|
-
};
|
|
1837
|
-
const description = input.description?.trim();
|
|
1838
|
-
if (description) mutationInput.description = description;
|
|
1839
|
-
if (input.state?.trim()) {
|
|
1840
|
-
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
1841
|
-
}
|
|
1842
|
-
const parentRef = input.parent?.trim();
|
|
1843
|
-
if (parentRef) {
|
|
1844
|
-
const parent = await getLinearIssue(parentRef, opts);
|
|
1845
|
-
mutationInput.parentId = parent.id;
|
|
1846
|
-
}
|
|
1847
|
-
const assignee = input.assignee === void 0 ? void 0 : input.assignee?.trim() || null;
|
|
1848
|
-
if (assignee === "me") mutationInput.assigneeId = viewer.id;
|
|
1849
|
-
else if (assignee) mutationInput.assigneeId = assignee;
|
|
1850
|
-
const priority = normalizePriority(input.priority);
|
|
1851
|
-
if (priority != null) mutationInput.priority = priority;
|
|
1852
|
-
const json = await linearGraphql(ISSUE_CREATE, { input: mutationInput }, opts);
|
|
1853
|
-
if (!json.issueCreate?.success || !json.issueCreate.issue) {
|
|
1854
|
-
throw new Error("Linear issueCreate failed");
|
|
1855
|
-
}
|
|
1856
|
-
return mapIssue(json.issueCreate.issue);
|
|
1857
|
-
}
|
|
1858
|
-
async function updateLinearIssue(input, opts) {
|
|
1859
|
-
const issueId = input.id.trim();
|
|
1860
|
-
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
1861
|
-
const mutationInput = {};
|
|
1862
|
-
if (input.title !== void 0) {
|
|
1863
|
-
const title = input.title.trim();
|
|
1864
|
-
if (!title) throw new Error("Linear issue title cannot be empty");
|
|
1865
|
-
mutationInput.title = title;
|
|
1866
|
-
}
|
|
1867
|
-
if (input.description !== void 0) {
|
|
1868
|
-
mutationInput.description = input.description;
|
|
1869
|
-
}
|
|
1870
|
-
const addRelations = input.relations?.length ? input.relations : void 0;
|
|
1871
|
-
const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
|
|
1872
|
-
const needsIssue = Boolean(input.state?.trim()) || input.cycle !== void 0 || input.labels !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
|
|
1873
|
-
const existing = needsIssue ? await getLinearIssue(issueId, opts) : void 0;
|
|
1874
|
-
if (input.state?.trim()) {
|
|
1875
|
-
const { teams } = await listLinearTeams(opts);
|
|
1876
|
-
const teamRef = existing?.team?.key || existing?.team?.id;
|
|
1877
|
-
const team = teamRef ? resolveLinearTeam(teams, teamRef) : void 0;
|
|
1878
|
-
if (!team?.states.length) {
|
|
1879
|
-
throw new Error(`Linear issue ${existing?.identifier ?? issueId} has no workflow states to resolve "${input.state}"`);
|
|
1880
|
-
}
|
|
1881
|
-
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
1882
|
-
}
|
|
1883
|
-
if (input.cycle !== void 0) {
|
|
1884
|
-
const teamId = existing?.team?.id;
|
|
1885
|
-
if (!teamId) {
|
|
1886
|
-
throw new Error(`Linear issue ${existing?.identifier ?? issueId} has no team to resolve a cycle`);
|
|
1887
|
-
}
|
|
1888
|
-
const cycles = await listLinearTeamCycles(teamId, opts);
|
|
1889
|
-
mutationInput.cycleId = resolveLinearCycle(
|
|
1890
|
-
{ ...cycles, key: cycles.key || existing?.team?.key || teamId },
|
|
1891
|
-
input.cycle
|
|
1892
|
-
);
|
|
1893
|
-
}
|
|
1894
|
-
if (input.assignee !== void 0) {
|
|
1895
|
-
const assignee = input.assignee?.trim() || null;
|
|
1896
|
-
if (assignee === "me") {
|
|
1897
|
-
const { viewer } = await listLinearTeams(opts);
|
|
1898
|
-
mutationInput.assigneeId = viewer.id;
|
|
1899
|
-
} else {
|
|
1900
|
-
mutationInput.assigneeId = assignee;
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
if (input.priority !== void 0) {
|
|
1904
|
-
mutationInput.priority = normalizePriority(input.priority);
|
|
1905
|
-
}
|
|
1906
|
-
if (input.labels !== void 0) {
|
|
1907
|
-
mutationInput.labelIds = await resolveLinearLabelIds(input.labels, {
|
|
1908
|
-
...opts,
|
|
1909
|
-
teamId: existing?.team?.id
|
|
1910
|
-
});
|
|
1911
|
-
}
|
|
1912
|
-
if (input.project !== void 0) {
|
|
1913
|
-
mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
|
|
1914
|
-
}
|
|
1915
|
-
if (input.parent !== void 0) {
|
|
1916
|
-
if (isLinearNoneToken(input.parent)) {
|
|
1917
|
-
mutationInput.parentId = null;
|
|
1918
|
-
} else {
|
|
1919
|
-
const parent = await getLinearIssue((input.parent ?? "").trim(), opts);
|
|
1920
|
-
mutationInput.parentId = parent.id;
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
const hasIssueFields = Object.keys(mutationInput).length > 0;
|
|
1924
|
-
const hasRelationWrites = Boolean(addRelations || removeRelations);
|
|
1925
|
-
if (!hasIssueFields && !hasRelationWrites) {
|
|
1926
|
-
throw new Error(
|
|
1927
|
-
"linear_update_issue needs at least one of title, description, state, assignee, priority, cycle, labels, project, parent, relations, removeRelations"
|
|
1928
|
-
);
|
|
1929
|
-
}
|
|
1930
|
-
let updated;
|
|
1931
|
-
if (hasIssueFields) {
|
|
1932
|
-
const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
|
|
1933
|
-
if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
|
|
1934
|
-
throw new Error("Linear issueUpdate failed");
|
|
1935
|
-
}
|
|
1936
|
-
updated = mapIssue(json.issueUpdate.issue);
|
|
1937
|
-
}
|
|
1938
|
-
if (hasRelationWrites) {
|
|
1939
|
-
const base = updated ?? existing;
|
|
1940
|
-
if (!base) {
|
|
1941
|
-
throw new Error(`Linear issue not found: ${issueId}`);
|
|
1942
|
-
}
|
|
1943
|
-
await applyLinearIssueRelations(base, addRelations, removeRelations, opts);
|
|
1944
|
-
return getLinearIssue(base.id || issueId, opts);
|
|
1945
|
-
}
|
|
1946
|
-
return updated;
|
|
1947
|
-
}
|
|
1948
|
-
async function commentLinearIssue(input, opts) {
|
|
1949
|
-
const issueId = input.id.trim();
|
|
1950
|
-
const body = input.body.trim();
|
|
1951
|
-
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
1952
|
-
if (!body) throw new Error("Linear comment body is required");
|
|
1953
|
-
const json = await linearGraphql(COMMENT_CREATE, { input: { issueId, body } }, opts);
|
|
1954
|
-
if (!json.commentCreate?.success || !json.commentCreate.comment?.id) {
|
|
1955
|
-
throw new Error("Linear commentCreate failed");
|
|
1956
|
-
}
|
|
1957
|
-
return {
|
|
1958
|
-
id: String(json.commentCreate.comment.id),
|
|
1959
|
-
body: String(json.commentCreate.comment.body ?? body),
|
|
1960
|
-
url: json.commentCreate.comment.url?.trim() || void 0
|
|
1961
|
-
};
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
784
|
// src/integrations/issues.ts
|
|
1965
785
|
var CURSOR_ISSUES_UNAVAILABLE = "Cursor Origin has no issue tracker. Tickets for this repo come from Linear or AbleTime (Settings \u2192 Issues).";
|
|
1966
786
|
var ENTIRE_ISSUES_ARE_TRAILS = "Entire does not use GitHub Issues. Tickets for this repo are Trails (`entire trail list`). Linear or AbleTime still work when selected in Settings \u2192 Issues.";
|
|
@@ -4126,7 +2946,7 @@ async function startMcpServer() {
|
|
|
4126
2946
|
);
|
|
4127
2947
|
}
|
|
4128
2948
|
try {
|
|
4129
|
-
const { maxConcurrentAgents } = await import("../app-settings-
|
|
2949
|
+
const { maxConcurrentAgents } = await import("../app-settings-TUVLZNDE.js");
|
|
4130
2950
|
orch.setMaxConcurrent(maxConcurrentAgents());
|
|
4131
2951
|
} catch {
|
|
4132
2952
|
}
|
|
@@ -4674,7 +3494,7 @@ async function startMcpServer() {
|
|
|
4674
3494
|
if (!worktreeProfile) {
|
|
4675
3495
|
registerConnectedIssueVendorTools(server);
|
|
4676
3496
|
const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-H7TTUDGM.js");
|
|
4677
|
-
const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-
|
|
3497
|
+
const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-TUVLZNDE.js");
|
|
4678
3498
|
const accountDefaults = resolveThreadDefaults();
|
|
4679
3499
|
const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
|
|
4680
3500
|
server.tool(
|