@runfusion/fusion 0.54.0 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +9137 -2087
- package/dist/client/.vite/manifest.json +364 -332
- package/dist/client/assets/AgentDetailView-BPzzEIn1.js +18 -0
- package/dist/client/assets/{AgentDetailView-BzSyldiW.css → AgentDetailView-D7UY2Tup.css} +1 -1
- package/dist/client/assets/{AgentPermissionPolicyEditor-D2AQ6swg.js → AgentPermissionPolicyEditor-CqlLF0pf.js} +1 -1
- package/dist/client/assets/{AgentsView-DwEFPKuN.js → AgentsView-C4uCWzdt.js} +3 -3
- package/dist/client/assets/ChatView-BGV9F2K0.js +6 -0
- package/dist/client/assets/{CommandCenter-BABqoSkg.js → CommandCenter-BhIfVVjz.js} +16 -16
- package/dist/client/assets/{DevServerView-DlgOzRdC.js → DevServerView-Bd86MWUa.js} +1 -1
- package/dist/client/assets/DirectoryPicker-BDe9G84f.js +1 -0
- package/dist/client/assets/DocumentsView-BkAHMt19.js +1 -0
- package/dist/client/assets/{EvalsView-CQBNrO5n.js → EvalsView-BxxCVaJ8.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-2SDqP2lc.js → ExperimentalAgentOnboardingModal-7-BD6W1v.js} +1 -1
- package/dist/client/assets/{GoalsView-BgkX-_SB.js → GoalsView-B3KICwNu.js} +1 -1
- package/dist/client/assets/{InsightsView-CJY4eZcO.js → InsightsView-DO8qefU1.js} +2 -2
- package/dist/client/assets/{MemoryView-5Crtfanz.js → MemoryView-urkPdqTn.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-COmzskVa.js → PiExtensionsManager-BwVKjJIo.js} +2 -2
- package/dist/client/assets/PluginManager-C-OuqDLa.js +1 -0
- package/dist/client/assets/{PullRequestView-DAHh0FVM.js → PullRequestView-B4q4X5WV.js} +2 -2
- package/dist/client/assets/{ResearchView-cSUka3sd.js → ResearchView-DlW6z3sm.js} +1 -1
- package/dist/client/assets/{SecretsView-ChFAfFRY.js → SecretsView-C-3eC0KB.js} +1 -1
- package/dist/client/assets/{SessionTerminal-D4R3ScvQ.css → SessionTerminal-BvK8A98c.css} +1 -1
- package/dist/client/assets/SessionTerminal-DVfyVeS2.js +2 -0
- package/dist/client/assets/SettingsModal-BC_2Y56_.css +1 -0
- package/dist/client/assets/{SettingsModal-pAz_hOoN.js → SettingsModal-BzXctrhu.js} +1 -1
- package/dist/client/assets/SettingsModal-qs8w3rov.js +21 -0
- package/dist/client/assets/SetupWizardModal-DDJ8PCkH.js +2 -0
- package/dist/client/assets/{ShadcnColorPicker-CsfgWpf6.js → ShadcnColorPicker-BcD5fZ_P.js} +1 -1
- package/dist/client/assets/{SkillsView-BSUplEYb.js → SkillsView-DojlvxFh.js} +1 -1
- package/dist/client/assets/TodoView-BKZ07WxZ.js +6 -0
- package/dist/client/assets/WorkflowNodeEditor-BJNbi4_V.js +8 -0
- package/dist/client/assets/{agentSkills-qVb0x_G3.js → agentSkills-NbIIJy87.js} +1 -1
- package/dist/client/assets/app-0jcSCRQr.js +13 -0
- package/dist/client/assets/app-BPzPEE6G.js +13 -0
- package/dist/client/assets/app-BSZU5Uns.js +13 -0
- package/dist/client/assets/app-CEtD1B9K.js +13 -0
- package/dist/client/assets/app-CVyRSEUX.js +13 -0
- package/dist/client/assets/app-DoLz_4Dg.js +13 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-DEt4eIEQ.js → architectureDiagram-3BPJPVTR-CJu9RVXs.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-CJuX0UM_.js → blockDiagram-GPEHLZMM-crZsLLTq.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-a46Lm113.js → c4Diagram-AAUBKEIU-D5mmJKvW.js} +1 -1
- package/dist/client/assets/channel-Zfk0yRN2.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-iBhwyS0c.js → chunk-2J33WTMH-BZIDltTX.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-C7lS5wER.js → chunk-4BX2VUAB-ND_SzhUa.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-CQ6YOncn.js → chunk-55IACEB6-C3n-Nisk.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-CveMaMKK.js → chunk-727SXJPM-DfiPA7wv.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DErjGUEk.js → chunk-AQP2D5EJ-CDE0u57Z.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-DXUvEFfJ.js → chunk-FMBD7UC4-COR53PTE.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-w5RqSj1J.js → chunk-ND2GUHAM-CbcTdl7C.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-CAi0lJ7V.js → chunk-QZHKN3VN-DiB3PReK.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BehQ-wnv.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BehQ-wnv.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-Ck2h4uV5.js → cose-bilkent-S5V4N54A-Dg3YVyLX.js} +1 -1
- package/dist/client/assets/createLucideIcon-CshVyKO4.js +21 -0
- package/dist/client/assets/{dagre-BM42HDAG-BCRTXNj6.js → dagre-BM42HDAG-D2bK3gNb.js} +1 -1
- package/dist/client/assets/dashboard-view-BcbTWSCe.js +1 -0
- package/dist/client/assets/dashboard-view-Bsdq3OM4.css +1 -0
- package/dist/client/assets/dashboard-view-DafOfnxI.js +1 -0
- package/dist/client/assets/{dashboard-view-D9rF_PCq.js → dashboard-view-t2UfQxck.js} +1320 -1335
- package/dist/client/assets/{diagram-2AECGRRQ-DTTk-DYO.js → diagram-2AECGRRQ-BWT8KaEl.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-jDc9kMi2.js → diagram-5GNKFQAL-DWfJlASD.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-CcLmzlGr.js → diagram-KO2AKTUF-DE3hFbLT.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DCW8XKQj.js → diagram-LMA3HP47-CYCFeD4B.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-wbbKquPs.js → diagram-OG6HWLK6-C0C8lEGv.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-Fo933Mo7.js → erDiagram-TEJ5UH35-kSo7RjKA.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-Dfum7HZP.js → flowDiagram-I6XJVG4X-DxRf1SVH.js} +1 -1
- package/dist/client/assets/{folder-open-B8vOwFeP.js → folder-open-DO1rnCxq.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-BzEBCn01.js → ganttDiagram-6RSMTGT7-CGtxSeWq.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CgovghHs.js → gitGraphDiagram-PVQCEYII-BNZv_DFY.js} +1 -1
- package/dist/client/assets/index-BCf32Sgx.css +1 -0
- package/dist/client/assets/index-D7eX7QJk.js +2511 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-BzpU4Wys.js → infoDiagram-5YYISTIA-DailohMo.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-cpp5pozu.js → ishikawaDiagram-YF4QCWOH-Cy3shDZP.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-BVjcw6oo.js → journeyDiagram-JHISSGLW-B1rU18Qx.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-4unAhBzo.js → kanban-definition-UN3LZRKU-CRNUDgkO.js} +1 -1
- package/dist/client/assets/loader-circle-CMNcxXXr.js +16 -0
- package/dist/client/assets/{mermaid.core-DzIyUj9t.js → mermaid.core-Cg0po76i.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-Be_yfVir.js → mindmap-definition-RKZ34NQL-CEjwlhOe.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-Cfnw2aji.js → pieDiagram-4H26LBE5-70T-php2.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BFlC-_Uo.js → quadrantDiagram-W4KKPZXB-ChcpRG7l.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DpVPzCOx.js → requirementDiagram-4Y6WPE33-9Pzb_Cot.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DM-gKKP0.js → sankeyDiagram-5OEKKPKP-Bx6DkeKq.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DjhZCZhd.js → sequenceDiagram-3UESZ5HK-emWPkPv7.js} +1 -1
- package/dist/client/assets/{shield-alert-DCfkjjYc.js → shield-alert-BdAjnDkU.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-BCA96rdI.js → stateDiagram-AJRCARHV-B_X2-VQy.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-DvQssSEq.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-CHW2OQRw.js → timeline-definition-PNZ67QCA-CkyhunuI.js} +1 -1
- package/dist/client/assets/{upload-EJc5PJPz.js → upload-D9-yuO2D.js} +1 -1
- package/dist/client/assets/{users-C-e-99Vp.js → users-B4aebngl.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-shGh4eBY.js → vennDiagram-CIIHVFJN-8OGD6Vb7.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-l0RGrVhn.js → wardley-L42UT6IY-MnRFGNPm.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-C-USmO8A.js → wardleyDiagram-YWT4CUSO-BAEzb08c.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-CHX9Umta.js → xychartDiagram-2RQKCTM6-DUxideKC.js} +1 -1
- package/dist/client/assets/zoom-out-DzDzWQt1.js +21 -0
- package/dist/client/index.html +14 -5
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/extension.js +6720 -1170
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugin-sdk/index.d.ts +108 -4
- package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
- package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
- package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +70 -25
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-linear-import/bundled.js +658 -0
- package/dist/plugins/fusion-plugin-linear-import/manifest.json +48 -0
- package/dist/plugins/fusion-plugin-linear-import/package.json +11 -0
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +2 -1
- package/skill/fusion/SKILL.md +2 -2
- package/skill/fusion/references/engine-tools.md +1 -0
- package/skill/fusion/references/extension-tools.md +81 -0
- package/skill/fusion/references/fusion-capabilities.md +7 -0
- package/dist/client/assets/AgentDetailView-BJditQat.js +0 -18
- package/dist/client/assets/ChatView-B4bjPXFN.js +0 -6
- package/dist/client/assets/DirectoryPicker-BwMSmxYS.js +0 -1
- package/dist/client/assets/DocumentsView-BfaRXFg5.js +0 -1
- package/dist/client/assets/PluginManager-CGWXqZMa.js +0 -1
- package/dist/client/assets/SessionTerminal-CtQKDaqI.js +0 -2
- package/dist/client/assets/SettingsModal-BYaotQ2o.js +0 -21
- package/dist/client/assets/SettingsModal-BzZNPNIh.css +0 -1
- package/dist/client/assets/SetupWizardModal-eHZHi8VP.js +0 -2
- package/dist/client/assets/TodoView-DhBseLvM.js +0 -6
- package/dist/client/assets/WorkflowNodeEditor-Bi12CNif.js +0 -8
- package/dist/client/assets/app-5S4z84u5.js +0 -13
- package/dist/client/assets/app-B1VYocx2.js +0 -13
- package/dist/client/assets/app-BhtYYxin.js +0 -13
- package/dist/client/assets/app-Cts0__Vx.js +0 -13
- package/dist/client/assets/app-DczBmsP4.js +0 -13
- package/dist/client/assets/app-_GFUqcMK.js +0 -13
- package/dist/client/assets/channel-BGI9APMr.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-g6hCcufy.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-g6hCcufy.js +0 -1
- package/dist/client/assets/dashboard-view-BfLJ60Kd.js +0 -1
- package/dist/client/assets/index-BjELA24x.js +0 -1336
- package/dist/client/assets/index-DwGW7OJh.css +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-DHds08RS.js +0 -1
- package/dist/client/assets/zoom-out-NWbVkreR.js +0 -41
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
// ../plugin-sdk/src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
WORKFLOW_EXTENSION_SCHEMA_VERSION,
|
|
4
|
+
workflowExtensionRegistryId,
|
|
5
|
+
createBoardActionServices
|
|
6
|
+
} from "@fusion/core";
|
|
7
|
+
function definePlugin(plugin2) {
|
|
8
|
+
return plugin2;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// ../../plugins/fusion-plugin-linear-import/src/settings.ts
|
|
12
|
+
var LINEAR_PLUGIN_ID = "fusion-plugin-linear-import";
|
|
13
|
+
var linearSettingsSchema = {
|
|
14
|
+
apiKey: {
|
|
15
|
+
type: "password",
|
|
16
|
+
label: "Linear API key",
|
|
17
|
+
description: "Personal Linear API key used only by this plugin to browse and import issues.",
|
|
18
|
+
required: true,
|
|
19
|
+
group: "Authentication"
|
|
20
|
+
},
|
|
21
|
+
defaultTeamKey: {
|
|
22
|
+
type: "string",
|
|
23
|
+
label: "Default team key or ID",
|
|
24
|
+
description: "Optional Linear team key or UUID to prefill issue searches.",
|
|
25
|
+
group: "Defaults"
|
|
26
|
+
},
|
|
27
|
+
defaultStateFilter: {
|
|
28
|
+
type: "enum",
|
|
29
|
+
label: "Default issue state filter",
|
|
30
|
+
description: "Initial issue state filter for browse and import tools.",
|
|
31
|
+
enumValues: ["active", "backlog", "started", "unstarted", "completed", "canceled", "all"],
|
|
32
|
+
defaultValue: "active",
|
|
33
|
+
group: "Defaults"
|
|
34
|
+
},
|
|
35
|
+
defaultAssigneeId: {
|
|
36
|
+
type: "string",
|
|
37
|
+
label: "Default assignee ID",
|
|
38
|
+
description: "Optional Linear user UUID used as the default assignee filter.",
|
|
39
|
+
group: "Defaults"
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function optionalTrimmed(value) {
|
|
43
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
44
|
+
}
|
|
45
|
+
function resolveLinearSettings(settings) {
|
|
46
|
+
const rawState = optionalTrimmed(settings.defaultStateFilter);
|
|
47
|
+
const defaultStateFilter = rawState && linearSettingsSchema.defaultStateFilter.enumValues?.includes(rawState) ? rawState : "active";
|
|
48
|
+
return {
|
|
49
|
+
apiKey: optionalTrimmed(settings.apiKey),
|
|
50
|
+
defaultTeamKey: optionalTrimmed(settings.defaultTeamKey),
|
|
51
|
+
defaultStateFilter,
|
|
52
|
+
defaultAssigneeId: optionalTrimmed(settings.defaultAssigneeId)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function hasLinearApiKey(settings) {
|
|
56
|
+
return Boolean(resolveLinearSettings(settings).apiKey);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ../../plugins/fusion-plugin-linear-import/src/linear-client.ts
|
|
60
|
+
var LINEAR_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql";
|
|
61
|
+
var DEFAULT_LIMIT = 30;
|
|
62
|
+
var MAX_LIMIT = 100;
|
|
63
|
+
var PAGE_SIZE = 50;
|
|
64
|
+
var MAX_PAGES = 5;
|
|
65
|
+
var LinearApiError = class extends Error {
|
|
66
|
+
constructor(status, message, code = "linear_api_error") {
|
|
67
|
+
super(message);
|
|
68
|
+
this.status = status;
|
|
69
|
+
this.code = code;
|
|
70
|
+
this.name = "LinearApiError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function clampLinearLimit(limit) {
|
|
74
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) return DEFAULT_LIMIT;
|
|
75
|
+
return Math.max(1, Math.min(MAX_LIMIT, Math.floor(limit)));
|
|
76
|
+
}
|
|
77
|
+
function asRecord(value) {
|
|
78
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
79
|
+
}
|
|
80
|
+
function asString(value) {
|
|
81
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
82
|
+
}
|
|
83
|
+
function redactSensitiveText(message, secrets = []) {
|
|
84
|
+
let redacted = message;
|
|
85
|
+
for (const secret of secrets) {
|
|
86
|
+
if (secret) redacted = redacted.split(secret).join("[redacted]");
|
|
87
|
+
}
|
|
88
|
+
return redacted.replace(/\b(token|key|secret)[-_:=][A-Za-z0-9._-]+/giu, "$1-[redacted]");
|
|
89
|
+
}
|
|
90
|
+
function normalizeLinearErrorMessage(status, body, secrets = []) {
|
|
91
|
+
if (status === 401) return "Linear API key is missing, invalid, or expired.";
|
|
92
|
+
if (status === 403) return "Linear API key does not have access to the requested Linear workspace or team.";
|
|
93
|
+
if (status === 429) return "Linear rate limit exceeded. Try again later.";
|
|
94
|
+
const raw = asRecord(body);
|
|
95
|
+
const errors = Array.isArray(raw.errors) ? raw.errors : [];
|
|
96
|
+
const first = asRecord(errors[0]);
|
|
97
|
+
const message = asString(first.message) ?? asString(raw.error) ?? asString(raw.message);
|
|
98
|
+
return message ? `Linear GraphQL error: ${redactSensitiveText(message, secrets)}` : `Linear API request failed with status ${status}.`;
|
|
99
|
+
}
|
|
100
|
+
function mapFetchError(error) {
|
|
101
|
+
if (error instanceof LinearApiError) return error;
|
|
102
|
+
return new LinearApiError(0, "Unable to reach Linear API. Check network access and plugin configuration.", "network_error");
|
|
103
|
+
}
|
|
104
|
+
function buildLinearIssueFilter(options) {
|
|
105
|
+
const and = [];
|
|
106
|
+
if (options.teamKey?.trim()) {
|
|
107
|
+
and.push({ team: { or: [{ key: { eqIgnoreCase: options.teamKey.trim() } }, { id: { eq: options.teamKey.trim() } }] } });
|
|
108
|
+
}
|
|
109
|
+
if (options.assigneeId?.trim()) {
|
|
110
|
+
and.push({ assignee: { id: { eq: options.assigneeId.trim() } } });
|
|
111
|
+
}
|
|
112
|
+
if (options.state && options.state !== "all") {
|
|
113
|
+
and.push({ state: { type: { eq: options.state } } });
|
|
114
|
+
}
|
|
115
|
+
if (options.query?.trim()) {
|
|
116
|
+
const q = options.query.trim();
|
|
117
|
+
and.push({ or: [{ title: { containsIgnoreCase: q } }, { description: { containsIgnoreCase: q } }, { identifier: { containsIgnoreCase: q } }] });
|
|
118
|
+
}
|
|
119
|
+
return and.length > 0 ? { and } : void 0;
|
|
120
|
+
}
|
|
121
|
+
var LINEAR_ISSUES_QUERY = `query FusionLinearImportIssues($first: Int!, $after: String, $filter: IssueFilter) {
|
|
122
|
+
issues(first: $first, after: $after, filter: $filter, orderBy: updatedAt) {
|
|
123
|
+
nodes {
|
|
124
|
+
id
|
|
125
|
+
identifier
|
|
126
|
+
title
|
|
127
|
+
description
|
|
128
|
+
url
|
|
129
|
+
createdAt
|
|
130
|
+
updatedAt
|
|
131
|
+
state { id name type }
|
|
132
|
+
team { id key name }
|
|
133
|
+
assignee { id name email }
|
|
134
|
+
creator { id name email }
|
|
135
|
+
labels { nodes { id name } }
|
|
136
|
+
}
|
|
137
|
+
pageInfo { hasNextPage endCursor }
|
|
138
|
+
}
|
|
139
|
+
}`;
|
|
140
|
+
var LINEAR_ISSUE_QUERY = `query FusionLinearImportIssue($id: String!) {
|
|
141
|
+
issue(id: $id) {
|
|
142
|
+
id
|
|
143
|
+
identifier
|
|
144
|
+
title
|
|
145
|
+
description
|
|
146
|
+
url
|
|
147
|
+
createdAt
|
|
148
|
+
updatedAt
|
|
149
|
+
state { id name type }
|
|
150
|
+
team { id key name }
|
|
151
|
+
assignee { id name email }
|
|
152
|
+
creator { id name email }
|
|
153
|
+
labels { nodes { id name } }
|
|
154
|
+
}
|
|
155
|
+
}`;
|
|
156
|
+
function normalizeIssue(rawIssue) {
|
|
157
|
+
const raw = asRecord(rawIssue);
|
|
158
|
+
const id = asString(raw.id);
|
|
159
|
+
const identifier = asString(raw.identifier);
|
|
160
|
+
const title = asString(raw.title);
|
|
161
|
+
const url = asString(raw.url);
|
|
162
|
+
if (!id || !identifier || !title || !url) return null;
|
|
163
|
+
const labelsRaw = asRecord(raw.labels).nodes;
|
|
164
|
+
const labels = Array.isArray(labelsRaw) ? labelsRaw.flatMap((label) => {
|
|
165
|
+
const rawLabel = asRecord(label);
|
|
166
|
+
const name = asString(rawLabel.name);
|
|
167
|
+
return name ? [{ id: asString(rawLabel.id), name }] : [];
|
|
168
|
+
}) : [];
|
|
169
|
+
const state = asRecord(raw.state);
|
|
170
|
+
const team = asRecord(raw.team);
|
|
171
|
+
const assignee = asRecord(raw.assignee);
|
|
172
|
+
const creator = asRecord(raw.creator);
|
|
173
|
+
return {
|
|
174
|
+
id,
|
|
175
|
+
identifier,
|
|
176
|
+
title,
|
|
177
|
+
description: typeof raw.description === "string" ? raw.description : null,
|
|
178
|
+
url,
|
|
179
|
+
state: Object.keys(state).length ? { id: asString(state.id), name: asString(state.name), type: asString(state.type) } : null,
|
|
180
|
+
team: Object.keys(team).length ? { id: asString(team.id), key: asString(team.key), name: asString(team.name) } : null,
|
|
181
|
+
assignee: Object.keys(assignee).length ? { id: asString(assignee.id), name: asString(assignee.name), email: asString(assignee.email) } : null,
|
|
182
|
+
creator: Object.keys(creator).length ? { id: asString(creator.id), name: asString(creator.name), email: asString(creator.email) } : null,
|
|
183
|
+
labels,
|
|
184
|
+
createdAt: asString(raw.createdAt),
|
|
185
|
+
updatedAt: asString(raw.updatedAt)
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
var LinearClient = class {
|
|
189
|
+
constructor(apiKey, fetchImpl = fetch) {
|
|
190
|
+
this.apiKey = apiKey;
|
|
191
|
+
this.fetchImpl = fetchImpl;
|
|
192
|
+
}
|
|
193
|
+
async request(query, variables) {
|
|
194
|
+
let response2;
|
|
195
|
+
try {
|
|
196
|
+
response2 = await this.fetchImpl(LINEAR_GRAPHQL_ENDPOINT, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: {
|
|
199
|
+
"Content-Type": "application/json",
|
|
200
|
+
Accept: "application/json",
|
|
201
|
+
Authorization: this.apiKey
|
|
202
|
+
},
|
|
203
|
+
body: JSON.stringify({ query, variables })
|
|
204
|
+
});
|
|
205
|
+
} catch (error) {
|
|
206
|
+
throw mapFetchError(error);
|
|
207
|
+
}
|
|
208
|
+
const body = await response2.json().catch(() => ({}));
|
|
209
|
+
if (!response2.ok) {
|
|
210
|
+
throw new LinearApiError(response2.status, normalizeLinearErrorMessage(response2.status, body, [this.apiKey]));
|
|
211
|
+
}
|
|
212
|
+
const errors = Array.isArray(asRecord(body).errors) ? asRecord(body).errors : [];
|
|
213
|
+
if (errors.length > 0) {
|
|
214
|
+
throw new LinearApiError(400, normalizeLinearErrorMessage(400, body, [this.apiKey]), "graphql_error");
|
|
215
|
+
}
|
|
216
|
+
return asRecord(body).data;
|
|
217
|
+
}
|
|
218
|
+
/*
|
|
219
|
+
FNXC:LinearImport 2026-07-02-00:00:
|
|
220
|
+
FN-7443 integrates Linear as a plugin-owned SaaS GraphQL HTTP client. Keep pagination bounded and errors credential-safe so plugin routes/tools can expose actionable failures without ever returning the API key.
|
|
221
|
+
*/
|
|
222
|
+
async listIssues(options = {}) {
|
|
223
|
+
const limit = clampLinearLimit(options.limit);
|
|
224
|
+
const issues = [];
|
|
225
|
+
let after = options.after;
|
|
226
|
+
let pageInfo = { hasNextPage: false, endCursor: null };
|
|
227
|
+
for (let page = 0; issues.length < limit && page < MAX_PAGES; page += 1) {
|
|
228
|
+
const data = await this.request(LINEAR_ISSUES_QUERY, {
|
|
229
|
+
first: Math.min(PAGE_SIZE, limit - issues.length),
|
|
230
|
+
after: after ?? null,
|
|
231
|
+
filter: buildLinearIssueFilter(options) ?? null
|
|
232
|
+
});
|
|
233
|
+
const connection = data.issues ?? {};
|
|
234
|
+
const nodes = Array.isArray(connection.nodes) ? connection.nodes : [];
|
|
235
|
+
issues.push(...nodes.map(normalizeIssue).filter((issue) => Boolean(issue)));
|
|
236
|
+
pageInfo = {
|
|
237
|
+
hasNextPage: connection.pageInfo?.hasNextPage === true,
|
|
238
|
+
endCursor: connection.pageInfo?.endCursor ?? null
|
|
239
|
+
};
|
|
240
|
+
if (!pageInfo.hasNextPage || !pageInfo.endCursor || nodes.length === 0) break;
|
|
241
|
+
after = pageInfo.endCursor;
|
|
242
|
+
}
|
|
243
|
+
return { issues: issues.slice(0, limit), pageInfo };
|
|
244
|
+
}
|
|
245
|
+
async getIssue(idOrIdentifier) {
|
|
246
|
+
const id = idOrIdentifier.trim();
|
|
247
|
+
if (!id) throw new LinearApiError(400, "Linear issue id or identifier is required.", "validation_error");
|
|
248
|
+
const data = await this.request(LINEAR_ISSUE_QUERY, { id });
|
|
249
|
+
const issue = normalizeIssue(data.issue);
|
|
250
|
+
if (!issue) throw new LinearApiError(404, "Linear issue was not found or is inaccessible.", "not_found");
|
|
251
|
+
return issue;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
function linearErrorToResponse(error) {
|
|
255
|
+
if (error instanceof LinearApiError) {
|
|
256
|
+
const status = error.status === 0 ? 502 : error.status;
|
|
257
|
+
return { status, error: error.message, code: error.code };
|
|
258
|
+
}
|
|
259
|
+
return { status: 500, error: "Linear import failed unexpectedly.", code: "unexpected_error" };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ../../plugins/fusion-plugin-linear-import/src/import-linear.ts
|
|
263
|
+
function cleanText(value) {
|
|
264
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
265
|
+
}
|
|
266
|
+
function linearRepository(issue) {
|
|
267
|
+
return issue.team?.key ?? issue.team?.id ?? "linear";
|
|
268
|
+
}
|
|
269
|
+
function getLinearDuplicateKeys(issue) {
|
|
270
|
+
return {
|
|
271
|
+
issueId: issue.id,
|
|
272
|
+
identifier: issue.identifier,
|
|
273
|
+
url: issue.url
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function buildLinearSourceMetadata(issue) {
|
|
277
|
+
return {
|
|
278
|
+
provider: "linear",
|
|
279
|
+
issueId: issue.id,
|
|
280
|
+
identifier: issue.identifier,
|
|
281
|
+
url: issue.url,
|
|
282
|
+
teamId: issue.team?.id,
|
|
283
|
+
teamKey: issue.team?.key,
|
|
284
|
+
teamName: issue.team?.name,
|
|
285
|
+
stateName: issue.state?.name,
|
|
286
|
+
stateType: issue.state?.type,
|
|
287
|
+
assigneeId: issue.assignee?.id,
|
|
288
|
+
assigneeName: issue.assignee?.name,
|
|
289
|
+
createdAt: issue.createdAt,
|
|
290
|
+
updatedAt: issue.updatedAt
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function buildLinearImportPreview(issue) {
|
|
294
|
+
const description = cleanText(issue.description) ?? "(no description)";
|
|
295
|
+
const details = [
|
|
296
|
+
description,
|
|
297
|
+
"",
|
|
298
|
+
`Source: ${issue.url}`,
|
|
299
|
+
`Linear: ${issue.identifier}`,
|
|
300
|
+
issue.team?.key || issue.team?.name ? `Team: ${issue.team.key ?? issue.team.name}` : void 0,
|
|
301
|
+
issue.state?.name ? `State: ${issue.state.name}` : void 0
|
|
302
|
+
].filter(Boolean).join("\n");
|
|
303
|
+
return {
|
|
304
|
+
title: `[${issue.identifier}] ${issue.title}`,
|
|
305
|
+
description: details,
|
|
306
|
+
sourceIssue: {
|
|
307
|
+
provider: "linear",
|
|
308
|
+
repository: linearRepository(issue),
|
|
309
|
+
externalIssueId: issue.id,
|
|
310
|
+
issueNumber: Number.parseInt(issue.identifier.replace(/^[A-Z]+-/iu, ""), 10) || 0,
|
|
311
|
+
url: issue.url
|
|
312
|
+
},
|
|
313
|
+
sourceMetadata: buildLinearSourceMetadata(issue),
|
|
314
|
+
duplicateKeys: getLinearDuplicateKeys(issue)
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function getTaskSourceMetadata(task) {
|
|
318
|
+
return task.source?.sourceMetadata ?? {};
|
|
319
|
+
}
|
|
320
|
+
function taskMatchesLinearIssue(task, issue) {
|
|
321
|
+
const metadata = getTaskSourceMetadata(task);
|
|
322
|
+
if (task.sourceIssue?.provider === "linear") {
|
|
323
|
+
if (task.sourceIssue.externalIssueId === issue.id) return true;
|
|
324
|
+
if (task.sourceIssue.url && task.sourceIssue.url === issue.url) return true;
|
|
325
|
+
}
|
|
326
|
+
if (task.source?.sourceType === "api" && metadata.provider === "linear") {
|
|
327
|
+
if (metadata.issueId === issue.id || metadata.identifier === issue.identifier || metadata.url === issue.url) return true;
|
|
328
|
+
}
|
|
329
|
+
const description = typeof task.description === "string" ? task.description : "";
|
|
330
|
+
return description.includes(`Source: ${issue.url}`);
|
|
331
|
+
}
|
|
332
|
+
async function findExistingLinearTask(taskStore, issue) {
|
|
333
|
+
if (typeof taskStore.listTasks !== "function") return null;
|
|
334
|
+
const tasks = await taskStore.listTasks({ includeArchived: false, slim: false });
|
|
335
|
+
return tasks.find((task) => taskMatchesLinearIssue(task, issue)) ?? null;
|
|
336
|
+
}
|
|
337
|
+
function buildLinearTaskCreateInput(issue) {
|
|
338
|
+
const preview = buildLinearImportPreview(issue);
|
|
339
|
+
return {
|
|
340
|
+
title: preview.title,
|
|
341
|
+
description: preview.description,
|
|
342
|
+
column: "triage",
|
|
343
|
+
sourceIssue: preview.sourceIssue,
|
|
344
|
+
source: {
|
|
345
|
+
sourceType: "api",
|
|
346
|
+
sourceMetadata: preview.sourceMetadata
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
async function importLinearIssue(taskStore, issue) {
|
|
351
|
+
const preview = buildLinearImportPreview(issue);
|
|
352
|
+
const existing = await findExistingLinearTask(taskStore, issue);
|
|
353
|
+
if (existing) {
|
|
354
|
+
return { imported: false, duplicate: true, taskId: existing.id, task: existing, issue: preview };
|
|
355
|
+
}
|
|
356
|
+
if (typeof taskStore.createTask !== "function") {
|
|
357
|
+
throw new Error("Plugin task store cannot create tasks in this context.");
|
|
358
|
+
}
|
|
359
|
+
const task = await taskStore.createTask(buildLinearTaskCreateInput(issue));
|
|
360
|
+
return { imported: true, duplicate: false, taskId: task.id, task, issue: preview };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ../../plugins/fusion-plugin-linear-import/src/routes.ts
|
|
364
|
+
function asRecord2(value) {
|
|
365
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
366
|
+
}
|
|
367
|
+
function readBody(req) {
|
|
368
|
+
return asRecord2(req.body);
|
|
369
|
+
}
|
|
370
|
+
function readString(value) {
|
|
371
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
372
|
+
}
|
|
373
|
+
function readNumber(value) {
|
|
374
|
+
if (typeof value === "number") return value;
|
|
375
|
+
if (typeof value === "string" && value.trim()) {
|
|
376
|
+
const parsed = Number(value);
|
|
377
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
378
|
+
}
|
|
379
|
+
return void 0;
|
|
380
|
+
}
|
|
381
|
+
function response(status, body) {
|
|
382
|
+
return { status, body };
|
|
383
|
+
}
|
|
384
|
+
function requireClient(ctx) {
|
|
385
|
+
const settings = resolveLinearSettings(ctx.settings);
|
|
386
|
+
if (!settings.apiKey) {
|
|
387
|
+
return response(401, { ok: false, authenticated: false, error: "Configure a Linear API key in Plugin Manager settings before importing issues.", code: "missing_api_key" });
|
|
388
|
+
}
|
|
389
|
+
return new LinearClient(settings.apiKey);
|
|
390
|
+
}
|
|
391
|
+
function readListOptions(ctx, source) {
|
|
392
|
+
const settings = resolveLinearSettings(ctx.settings);
|
|
393
|
+
return {
|
|
394
|
+
query: readString(source.query),
|
|
395
|
+
teamKey: readString(source.teamKey) ?? readString(source.teamId) ?? settings.defaultTeamKey,
|
|
396
|
+
state: readString(source.state) ?? settings.defaultStateFilter,
|
|
397
|
+
assigneeId: readString(source.assigneeId) ?? settings.defaultAssigneeId,
|
|
398
|
+
limit: readNumber(source.limit),
|
|
399
|
+
after: readString(source.after)
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function errorResponse(error) {
|
|
403
|
+
const mapped = linearErrorToResponse(error);
|
|
404
|
+
return response(mapped.status, { ok: false, error: mapped.error, code: mapped.code });
|
|
405
|
+
}
|
|
406
|
+
async function getLinearStatus(_req, ctx) {
|
|
407
|
+
const settings = resolveLinearSettings(ctx.settings);
|
|
408
|
+
if (!settings.apiKey) {
|
|
409
|
+
return response(200, { ok: true, authenticated: false, configured: false, defaultTeamKey: settings.defaultTeamKey ?? null, defaultStateFilter: settings.defaultStateFilter });
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const client = new LinearClient(settings.apiKey);
|
|
413
|
+
await client.listIssues({ limit: 1, teamKey: settings.defaultTeamKey, state: settings.defaultStateFilter, assigneeId: settings.defaultAssigneeId });
|
|
414
|
+
return response(200, { ok: true, authenticated: true, configured: true, defaultTeamKey: settings.defaultTeamKey ?? null, defaultStateFilter: settings.defaultStateFilter });
|
|
415
|
+
} catch (error) {
|
|
416
|
+
const mapped = linearErrorToResponse(error);
|
|
417
|
+
return response(mapped.status, { ok: false, authenticated: false, configured: true, error: mapped.error, code: mapped.code });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async function listLinearIssues(req, ctx) {
|
|
421
|
+
const client = requireClient(ctx);
|
|
422
|
+
if (!(client instanceof LinearClient)) return client;
|
|
423
|
+
try {
|
|
424
|
+
const query = { ...asRecord2(req.query), ...readBody(req) };
|
|
425
|
+
const result = await client.listIssues(readListOptions(ctx, query));
|
|
426
|
+
return response(200, { ok: true, issues: result.issues, pageInfo: result.pageInfo });
|
|
427
|
+
} catch (error) {
|
|
428
|
+
return errorResponse(error);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async function getLinearIssueDetail(req, ctx) {
|
|
432
|
+
const client = requireClient(ctx);
|
|
433
|
+
if (!(client instanceof LinearClient)) return client;
|
|
434
|
+
const body = readBody(req);
|
|
435
|
+
const issueId = readString(body.issueId) ?? readString(body.id) ?? readString(body.identifier);
|
|
436
|
+
if (!issueId) return response(400, { ok: false, error: "issueId or identifier is required.", code: "validation_error" });
|
|
437
|
+
try {
|
|
438
|
+
const issue = await client.getIssue(issueId);
|
|
439
|
+
return response(200, { ok: true, issue });
|
|
440
|
+
} catch (error) {
|
|
441
|
+
return errorResponse(error);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
async function importSingleLinearIssue(req, ctx) {
|
|
445
|
+
const client = requireClient(ctx);
|
|
446
|
+
if (!(client instanceof LinearClient)) return client;
|
|
447
|
+
const body = readBody(req);
|
|
448
|
+
const issueId = readString(body.issueId) ?? readString(body.id) ?? readString(body.identifier);
|
|
449
|
+
if (!issueId) return response(400, { ok: false, error: "issueId or identifier is required.", code: "validation_error" });
|
|
450
|
+
try {
|
|
451
|
+
const issue = await client.getIssue(issueId);
|
|
452
|
+
const result = await importLinearIssue(ctx.taskStore, issue);
|
|
453
|
+
return response(result.duplicate ? 200 : 201, { ok: true, ...result, task: void 0 });
|
|
454
|
+
} catch (error) {
|
|
455
|
+
return errorResponse(error);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
async function importBatchLinearIssues(req, ctx) {
|
|
459
|
+
const client = requireClient(ctx);
|
|
460
|
+
if (!(client instanceof LinearClient)) return client;
|
|
461
|
+
const body = readBody(req);
|
|
462
|
+
const issueIds = Array.isArray(body.issueIds) ? body.issueIds.map(readString).filter((id) => Boolean(id)) : [];
|
|
463
|
+
if (issueIds.length === 0) return response(400, { ok: false, error: "issueIds must include at least one Linear issue id or identifier.", code: "validation_error" });
|
|
464
|
+
if (issueIds.length > 25) return response(400, { ok: false, error: "Batch import is limited to 25 Linear issues at a time.", code: "limit_exceeded" });
|
|
465
|
+
try {
|
|
466
|
+
const results = [];
|
|
467
|
+
for (const issueId of issueIds) {
|
|
468
|
+
const issue = await client.getIssue(issueId);
|
|
469
|
+
const result = await importLinearIssue(ctx.taskStore, issue);
|
|
470
|
+
results.push({ ...result, task: void 0 });
|
|
471
|
+
}
|
|
472
|
+
return response(200, {
|
|
473
|
+
ok: true,
|
|
474
|
+
results,
|
|
475
|
+
imported: results.filter((result) => result.imported).length,
|
|
476
|
+
duplicates: results.filter((result) => result.duplicate).length
|
|
477
|
+
});
|
|
478
|
+
} catch (error) {
|
|
479
|
+
return errorResponse(error);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
var linearImportRoutes = [
|
|
483
|
+
{ method: "GET", path: "/status", handler: getLinearStatus, description: "Check Linear import plugin authentication status." },
|
|
484
|
+
{ method: "POST", path: "/issues", handler: listLinearIssues, description: "List/search Linear issues using plugin settings." },
|
|
485
|
+
{ method: "POST", path: "/issues/detail", handler: getLinearIssueDetail, description: "Fetch one Linear issue by id or identifier." },
|
|
486
|
+
{ method: "POST", path: "/issues/import", handler: importSingleLinearIssue, description: "Import one Linear issue as a Fusion task." },
|
|
487
|
+
{ method: "POST", path: "/issues/import-batch", handler: importBatchLinearIssues, description: "Import selected Linear issues as Fusion tasks." }
|
|
488
|
+
];
|
|
489
|
+
|
|
490
|
+
// ../../plugins/fusion-plugin-linear-import/src/tools.ts
|
|
491
|
+
function textResult(text, details, isError = false) {
|
|
492
|
+
return { content: [{ type: "text", text }], details, isError };
|
|
493
|
+
}
|
|
494
|
+
function readString2(value) {
|
|
495
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
496
|
+
}
|
|
497
|
+
function readNumber2(value) {
|
|
498
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
499
|
+
}
|
|
500
|
+
function getClient(ctx) {
|
|
501
|
+
const settings = resolveLinearSettings(ctx.settings);
|
|
502
|
+
if (!settings.apiKey) {
|
|
503
|
+
return textResult("Linear Import is not configured. Add a Linear API key in Plugin Manager settings.", { code: "missing_api_key" }, true);
|
|
504
|
+
}
|
|
505
|
+
return new LinearClient(settings.apiKey);
|
|
506
|
+
}
|
|
507
|
+
function listOptions(params, ctx) {
|
|
508
|
+
const settings = resolveLinearSettings(ctx.settings);
|
|
509
|
+
return {
|
|
510
|
+
query: readString2(params.query),
|
|
511
|
+
teamKey: readString2(params.teamKey) ?? readString2(params.teamId) ?? settings.defaultTeamKey,
|
|
512
|
+
state: readString2(params.state) ?? settings.defaultStateFilter,
|
|
513
|
+
assigneeId: readString2(params.assigneeId) ?? settings.defaultAssigneeId,
|
|
514
|
+
limit: readNumber2(params.limit),
|
|
515
|
+
after: readString2(params.after)
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function safeErrorResult(error) {
|
|
519
|
+
const mapped = linearErrorToResponse(error);
|
|
520
|
+
return textResult(`Linear import failed: ${mapped.error}`, { code: mapped.code, status: mapped.status }, true);
|
|
521
|
+
}
|
|
522
|
+
var browseParams = {
|
|
523
|
+
type: "object",
|
|
524
|
+
properties: {
|
|
525
|
+
query: { type: "string", description: "Optional text search over Linear issue title, description, or identifier." },
|
|
526
|
+
teamKey: { type: "string", description: "Optional Linear team key or UUID." },
|
|
527
|
+
state: { type: "string", enum: ["active", "backlog", "started", "unstarted", "completed", "canceled", "all"], description: "Optional Linear state type filter." },
|
|
528
|
+
assigneeId: { type: "string", description: "Optional Linear assignee user UUID." },
|
|
529
|
+
limit: { type: "number", minimum: 1, maximum: 100, description: "Maximum issues to return." },
|
|
530
|
+
after: { type: "string", description: "Optional Linear pagination cursor." }
|
|
531
|
+
},
|
|
532
|
+
required: []
|
|
533
|
+
};
|
|
534
|
+
var browseLinearIssuesTool = {
|
|
535
|
+
name: "linear_import_browse_issues",
|
|
536
|
+
description: "Browse/search Linear issues through the Linear Import plugin settings.",
|
|
537
|
+
parameters: browseParams,
|
|
538
|
+
execute: async (params, ctx) => {
|
|
539
|
+
const client = getClient(ctx);
|
|
540
|
+
if (!(client instanceof LinearClient)) return client;
|
|
541
|
+
try {
|
|
542
|
+
const result = await client.listIssues(listOptions(params, ctx));
|
|
543
|
+
const lines = result.issues.length === 0 ? ["No Linear issues matched the filters."] : result.issues.map((issue) => `- ${issue.identifier}: ${issue.title} (${issue.url})`);
|
|
544
|
+
return textResult(lines.join("\n"), { issues: result.issues, pageInfo: result.pageInfo });
|
|
545
|
+
} catch (error) {
|
|
546
|
+
return safeErrorResult(error);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
var importLinearIssueTool = {
|
|
551
|
+
name: "linear_import_issue",
|
|
552
|
+
description: "Import one Linear issue into Fusion as a triage task, skipping duplicates.",
|
|
553
|
+
parameters: {
|
|
554
|
+
type: "object",
|
|
555
|
+
properties: {
|
|
556
|
+
issueId: { type: "string", description: "Linear issue UUID or human identifier such as ENG-123." }
|
|
557
|
+
},
|
|
558
|
+
required: ["issueId"]
|
|
559
|
+
},
|
|
560
|
+
execute: async (params, ctx) => {
|
|
561
|
+
const issueId = readString2(params.issueId);
|
|
562
|
+
if (!issueId) return textResult("issueId is required.", { code: "validation_error" }, true);
|
|
563
|
+
const client = getClient(ctx);
|
|
564
|
+
if (!(client instanceof LinearClient)) return client;
|
|
565
|
+
try {
|
|
566
|
+
const issue = await client.getIssue(issueId);
|
|
567
|
+
const result = await importLinearIssue(ctx.taskStore, issue);
|
|
568
|
+
const message = result.duplicate ? `Skipped duplicate Linear issue ${issue.identifier}; existing task ${result.taskId}.` : `Imported Linear issue ${issue.identifier} as task ${result.taskId}.`;
|
|
569
|
+
return textResult(message, { imported: result.imported, duplicate: result.duplicate, taskId: result.taskId, issue: result.issue });
|
|
570
|
+
} catch (error) {
|
|
571
|
+
return safeErrorResult(error);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
var importLinearIssuesTool = {
|
|
576
|
+
name: "linear_import_issues",
|
|
577
|
+
description: "Import multiple Linear issues into Fusion as triage tasks, skipping duplicates.",
|
|
578
|
+
parameters: {
|
|
579
|
+
type: "object",
|
|
580
|
+
properties: {
|
|
581
|
+
issueIds: { type: "array", items: { type: "string" }, description: "Linear issue UUIDs or identifiers to import. Maximum 25." }
|
|
582
|
+
},
|
|
583
|
+
required: ["issueIds"]
|
|
584
|
+
},
|
|
585
|
+
execute: async (params, ctx) => {
|
|
586
|
+
const issueIds = Array.isArray(params.issueIds) ? params.issueIds.map(readString2).filter((id) => Boolean(id)) : [];
|
|
587
|
+
if (issueIds.length === 0) return textResult("issueIds must include at least one Linear issue id or identifier.", { code: "validation_error" }, true);
|
|
588
|
+
if (issueIds.length > 25) return textResult("Batch import is limited to 25 Linear issues at a time.", { code: "limit_exceeded" }, true);
|
|
589
|
+
const client = getClient(ctx);
|
|
590
|
+
if (!(client instanceof LinearClient)) return client;
|
|
591
|
+
try {
|
|
592
|
+
const results = [];
|
|
593
|
+
for (const issueId of issueIds) {
|
|
594
|
+
const issue = await client.getIssue(issueId);
|
|
595
|
+
results.push(await importLinearIssue(ctx.taskStore, issue));
|
|
596
|
+
}
|
|
597
|
+
const imported = results.filter((result) => result.imported).length;
|
|
598
|
+
const duplicates = results.filter((result) => result.duplicate).length;
|
|
599
|
+
return textResult(`Linear batch import complete: ${imported} imported, ${duplicates} duplicates skipped.`, {
|
|
600
|
+
imported,
|
|
601
|
+
duplicates,
|
|
602
|
+
results: results.map((result) => ({ imported: result.imported, duplicate: result.duplicate, taskId: result.taskId, issue: result.issue }))
|
|
603
|
+
});
|
|
604
|
+
} catch (error) {
|
|
605
|
+
return safeErrorResult(error);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
var linearImportTools = [browseLinearIssuesTool, importLinearIssueTool, importLinearIssuesTool];
|
|
610
|
+
|
|
611
|
+
// ../../plugins/fusion-plugin-linear-import/src/index.ts
|
|
612
|
+
var dashboardViews = [
|
|
613
|
+
{
|
|
614
|
+
viewId: "linear-import",
|
|
615
|
+
label: "Linear Import",
|
|
616
|
+
componentPath: "./dashboard-view",
|
|
617
|
+
icon: "ListPlus",
|
|
618
|
+
placement: "more",
|
|
619
|
+
order: 55,
|
|
620
|
+
description: "Browse Linear issues and import selected issues as Fusion tasks."
|
|
621
|
+
}
|
|
622
|
+
];
|
|
623
|
+
var plugin = definePlugin({
|
|
624
|
+
manifest: {
|
|
625
|
+
id: LINEAR_PLUGIN_ID,
|
|
626
|
+
name: "Linear Import",
|
|
627
|
+
version: "0.1.0",
|
|
628
|
+
description: "Import Linear issues into Fusion tasks through plugin-owned settings, routes, tools, and dashboard view.",
|
|
629
|
+
author: "Fusion",
|
|
630
|
+
fusionVersion: ">=0.1.0",
|
|
631
|
+
settingsSchema: linearSettingsSchema
|
|
632
|
+
},
|
|
633
|
+
state: "installed",
|
|
634
|
+
hooks: {},
|
|
635
|
+
routes: linearImportRoutes,
|
|
636
|
+
tools: linearImportTools,
|
|
637
|
+
dashboardViews
|
|
638
|
+
});
|
|
639
|
+
var index_default = plugin;
|
|
640
|
+
export {
|
|
641
|
+
LINEAR_GRAPHQL_ENDPOINT,
|
|
642
|
+
LINEAR_PLUGIN_ID,
|
|
643
|
+
LinearApiError,
|
|
644
|
+
LinearClient,
|
|
645
|
+
buildLinearImportPreview,
|
|
646
|
+
buildLinearIssueFilter,
|
|
647
|
+
buildLinearTaskCreateInput,
|
|
648
|
+
index_default as default,
|
|
649
|
+
findExistingLinearTask,
|
|
650
|
+
hasLinearApiKey,
|
|
651
|
+
importLinearIssue,
|
|
652
|
+
linearErrorToResponse,
|
|
653
|
+
linearImportRoutes,
|
|
654
|
+
linearImportTools,
|
|
655
|
+
linearSettingsSchema,
|
|
656
|
+
resolveLinearSettings,
|
|
657
|
+
taskMatchesLinearIssue
|
|
658
|
+
};
|