@linkegringo/mcp 1.0.5 → 1.0.7
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/LICENSE +21 -0
- package/dist/index.d.ts +176 -82
- package/dist/index.js +818 -367
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -9,229 +9,234 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
9
9
|
// src/tools/audit-profile.ts
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
-
// src/
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
12
|
+
// src/bridge/state.ts
|
|
13
|
+
import { EventEmitter } from "events";
|
|
14
|
+
var BridgeJobStore = class extends EventEmitter {
|
|
15
|
+
jobs = /* @__PURE__ */ new Map();
|
|
16
|
+
pendingQueue = [];
|
|
17
|
+
createJob(type, payload) {
|
|
18
|
+
const id = `job_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const job = {
|
|
21
|
+
id,
|
|
22
|
+
type,
|
|
23
|
+
payload,
|
|
24
|
+
status: "pending",
|
|
25
|
+
createdAt: now,
|
|
26
|
+
updatedAt: now
|
|
27
|
+
};
|
|
28
|
+
this.jobs.set(id, job);
|
|
29
|
+
this.pendingQueue.push(id);
|
|
30
|
+
this.emit("job:created", job);
|
|
31
|
+
this.emit(`job:${id}`, job);
|
|
32
|
+
return job;
|
|
33
|
+
}
|
|
34
|
+
getJob(id) {
|
|
35
|
+
return this.jobs.get(id);
|
|
36
|
+
}
|
|
37
|
+
getPendingJob(id) {
|
|
38
|
+
if (id) {
|
|
39
|
+
const job = this.jobs.get(id);
|
|
40
|
+
return job && (job.status === "pending" || job.status === "processing") ? job : void 0;
|
|
41
|
+
}
|
|
42
|
+
while (this.pendingQueue.length > 0) {
|
|
43
|
+
const nextId = this.pendingQueue[0];
|
|
44
|
+
const job = this.jobs.get(nextId);
|
|
45
|
+
if (job && job.status === "pending") {
|
|
46
|
+
return job;
|
|
43
47
|
}
|
|
48
|
+
this.pendingQueue.shift();
|
|
44
49
|
}
|
|
50
|
+
return void 0;
|
|
45
51
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
markProcessing(id) {
|
|
53
|
+
const job = this.jobs.get(id);
|
|
54
|
+
if (!job) return void 0;
|
|
55
|
+
job.status = "processing";
|
|
56
|
+
job.updatedAt = Date.now();
|
|
57
|
+
this.emit(`job:${id}`, job);
|
|
58
|
+
return job;
|
|
59
|
+
}
|
|
60
|
+
completeJob(id, result) {
|
|
61
|
+
const job = this.jobs.get(id);
|
|
62
|
+
if (!job) {
|
|
63
|
+
throw new Error(`Job n\xE3o encontrado: ${id}`);
|
|
64
|
+
}
|
|
65
|
+
job.status = "completed";
|
|
66
|
+
job.result = result;
|
|
67
|
+
job.updatedAt = Date.now();
|
|
68
|
+
const idx = this.pendingQueue.indexOf(id);
|
|
69
|
+
if (idx !== -1) {
|
|
70
|
+
this.pendingQueue.splice(idx, 1);
|
|
71
|
+
}
|
|
72
|
+
this.emit(`job:${id}`, job);
|
|
73
|
+
this.emit("job:completed", job);
|
|
74
|
+
return job;
|
|
75
|
+
}
|
|
76
|
+
failJob(id, error) {
|
|
77
|
+
const job = this.jobs.get(id);
|
|
78
|
+
if (!job) {
|
|
79
|
+
throw new Error(`Job n\xE3o encontrado: ${id}`);
|
|
80
|
+
}
|
|
81
|
+
job.status = "failed";
|
|
82
|
+
job.error = error;
|
|
83
|
+
job.updatedAt = Date.now();
|
|
84
|
+
const idx = this.pendingQueue.indexOf(id);
|
|
85
|
+
if (idx !== -1) {
|
|
86
|
+
this.pendingQueue.splice(idx, 1);
|
|
87
|
+
}
|
|
88
|
+
this.emit(`job:${id}`, job);
|
|
89
|
+
this.emit("job:failed", job);
|
|
90
|
+
return job;
|
|
91
|
+
}
|
|
92
|
+
async waitForJob(id, timeoutMs = 6e4) {
|
|
93
|
+
const existing = this.jobs.get(id);
|
|
94
|
+
if (existing && (existing.status === "completed" || existing.status === "failed")) {
|
|
95
|
+
return existing;
|
|
96
|
+
}
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
const timer = setTimeout(() => {
|
|
99
|
+
this.off(`job:${id}`, onUpdate);
|
|
100
|
+
const current = this.jobs.get(id);
|
|
101
|
+
if (current) resolve(current);
|
|
102
|
+
else reject(new Error(`Timeout aguardando pelo job ${id}`));
|
|
103
|
+
}, timeoutMs);
|
|
104
|
+
const onUpdate = (job) => {
|
|
105
|
+
if (job.status === "completed" || job.status === "failed") {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
this.off(`job:${id}`, onUpdate);
|
|
108
|
+
resolve(job);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
this.on(`job:${id}`, onUpdate);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
getAllJobs() {
|
|
115
|
+
return Array.from(this.jobs.values());
|
|
116
|
+
}
|
|
117
|
+
getPendingCount() {
|
|
118
|
+
return this.jobs.size > 0 ? Array.from(this.jobs.values()).filter((j) => j.status === "pending" || j.status === "processing").length : 0;
|
|
119
|
+
}
|
|
120
|
+
// Watcher tracking
|
|
121
|
+
activeWatchers = /* @__PURE__ */ new Set();
|
|
122
|
+
registerWatcher(id) {
|
|
123
|
+
this.activeWatchers.add(id);
|
|
124
|
+
this.emit("watcher:connected", id);
|
|
125
|
+
}
|
|
126
|
+
unregisterWatcher(id) {
|
|
127
|
+
this.activeWatchers.delete(id);
|
|
128
|
+
this.emit("watcher:disconnected", id);
|
|
129
|
+
}
|
|
130
|
+
getWatcherCount() {
|
|
131
|
+
return this.activeWatchers.size;
|
|
132
|
+
}
|
|
133
|
+
hasActiveWatcher() {
|
|
134
|
+
return this.activeWatchers.size > 0;
|
|
135
|
+
}
|
|
136
|
+
cancelAllPending(reason = "Cancelado pelo usu\xE1rio via interface web") {
|
|
137
|
+
let count = 0;
|
|
138
|
+
while (this.pendingQueue.length > 0) {
|
|
139
|
+
const id = this.pendingQueue.shift();
|
|
140
|
+
const job = this.jobs.get(id);
|
|
141
|
+
if (job && (job.status === "pending" || job.status === "processing")) {
|
|
142
|
+
job.status = "failed";
|
|
143
|
+
job.error = reason;
|
|
144
|
+
job.updatedAt = Date.now();
|
|
145
|
+
count++;
|
|
146
|
+
this.emit(`job:${id}`, job);
|
|
147
|
+
this.emit("job:failed", job);
|
|
57
148
|
}
|
|
58
|
-
|
|
59
|
-
|
|
149
|
+
}
|
|
150
|
+
this.emit("queue:cleared", count);
|
|
151
|
+
return count;
|
|
152
|
+
}
|
|
153
|
+
clear() {
|
|
154
|
+
this.jobs.clear();
|
|
155
|
+
this.pendingQueue = [];
|
|
156
|
+
this.activeWatchers.clear();
|
|
157
|
+
this.removeAllListeners();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
var bridgeJobStore = new BridgeJobStore();
|
|
161
|
+
|
|
162
|
+
// src/bridge/client.ts
|
|
163
|
+
var DEFAULT_BRIDGE_URL = process.env.LINKEGRINGO_BRIDGE_URL || (process.env.NODE_ENV === "test" ? "" : "http://127.0.0.1:5174");
|
|
164
|
+
async function getRemoteOrLocalPendingJob(jobId, bridgeUrl = DEFAULT_BRIDGE_URL) {
|
|
165
|
+
if (bridgeUrl) {
|
|
60
166
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
167
|
+
const url = jobId ? `${bridgeUrl}/api/jobs/pending?id=${encodeURIComponent(jobId)}` : `${bridgeUrl}/api/jobs/pending`;
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
const timer = setTimeout(() => controller.abort(), 2e3);
|
|
170
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
63
171
|
clearTimeout(timer);
|
|
64
|
-
|
|
65
|
-
|
|
172
|
+
if (res.ok) {
|
|
173
|
+
const data = await res.json();
|
|
174
|
+
if (data.ok && data.job) {
|
|
175
|
+
return data.job;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
66
179
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
otherTabsCount = pageTargets.length - matched.length;
|
|
86
|
-
if (linkeGringoTabs.length === 0) {
|
|
87
|
-
clearTimeout(timer);
|
|
88
|
-
ws.close();
|
|
89
|
-
resolve({ linkeGringoTabs: [], otherTabsCount, sessionState: null });
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
linkeTarget = matched[0];
|
|
93
|
-
ws.send(
|
|
94
|
-
JSON.stringify({
|
|
95
|
-
id: 2,
|
|
96
|
-
method: "Target.attachToTarget",
|
|
97
|
-
params: { targetId: linkeTarget.targetId, flatten: true }
|
|
98
|
-
})
|
|
99
|
-
);
|
|
100
|
-
} else if (msg.id === 2) {
|
|
101
|
-
const sessionId = msg.result?.sessionId;
|
|
102
|
-
ws.send(
|
|
103
|
-
JSON.stringify({
|
|
104
|
-
id: 3,
|
|
105
|
-
sessionId,
|
|
106
|
-
method: "Runtime.evaluate",
|
|
107
|
-
params: {
|
|
108
|
-
expression: 'window.localStorage.getItem("linkegringo_active_session")',
|
|
109
|
-
returnByValue: true
|
|
110
|
-
}
|
|
111
|
-
})
|
|
112
|
-
);
|
|
113
|
-
} else if (msg.id === 3) {
|
|
114
|
-
clearTimeout(timer);
|
|
115
|
-
ws.close();
|
|
116
|
-
const raw = msg.result?.result?.value;
|
|
117
|
-
let session = null;
|
|
180
|
+
}
|
|
181
|
+
return bridgeJobStore.getPendingJob(jobId);
|
|
182
|
+
}
|
|
183
|
+
async function completeRemoteOrLocalJob(jobId, result, bridgeUrl = DEFAULT_BRIDGE_URL) {
|
|
184
|
+
if (bridgeUrl) {
|
|
185
|
+
try {
|
|
186
|
+
const controller = new AbortController();
|
|
187
|
+
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
188
|
+
const res = await fetch(`${bridgeUrl}/api/jobs/${encodeURIComponent(jobId)}/complete`, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: { "Content-Type": "application/json" },
|
|
191
|
+
body: JSON.stringify({ result }),
|
|
192
|
+
signal: controller.signal
|
|
193
|
+
});
|
|
194
|
+
clearTimeout(timer);
|
|
195
|
+
if (res.ok) {
|
|
196
|
+
const data = await res.json();
|
|
197
|
+
if (data.ok && data.job) {
|
|
118
198
|
try {
|
|
119
|
-
if (
|
|
199
|
+
if (bridgeJobStore.getJob(jobId)) {
|
|
200
|
+
bridgeJobStore.completeJob(jobId, result);
|
|
201
|
+
}
|
|
120
202
|
} catch {
|
|
121
203
|
}
|
|
122
|
-
|
|
123
|
-
const sessionState = {
|
|
124
|
-
hasUploadedProfile,
|
|
125
|
-
candidateName: session?.profile?.name,
|
|
126
|
-
targetRole: session?.profile?.targetRole || session?.objective,
|
|
127
|
-
step: session?.step || (hasUploadedProfile ? "diagnostic" : "upload"),
|
|
128
|
-
inboundScore: session?.review?.inboundReadinessScore,
|
|
129
|
-
profile: session?.profile || null,
|
|
130
|
-
review: session?.review || null
|
|
131
|
-
};
|
|
132
|
-
resolve({
|
|
133
|
-
linkeGringoTabs,
|
|
134
|
-
otherTabsCount,
|
|
135
|
-
sessionState
|
|
136
|
-
});
|
|
204
|
+
return data.job;
|
|
137
205
|
}
|
|
138
|
-
} catch {
|
|
139
|
-
clearTimeout(timer);
|
|
140
|
-
try {
|
|
141
|
-
ws?.close();
|
|
142
|
-
} catch {
|
|
143
|
-
}
|
|
144
|
-
resolve(null);
|
|
145
206
|
}
|
|
146
|
-
}
|
|
147
|
-
ws.onerror = () => {
|
|
148
|
-
clearTimeout(timer);
|
|
149
|
-
resolve(null);
|
|
150
|
-
};
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
async function checkChromeCdp(port = 9222, host = "127.0.0.1", timeoutMs = 5e3) {
|
|
154
|
-
const activePortData = findDevToolsActivePort();
|
|
155
|
-
if (activePortData) {
|
|
156
|
-
const wsResult = await probeViaWebSocket(activePortData.port, activePortData.wsPath, timeoutMs);
|
|
157
|
-
if (wsResult) {
|
|
158
|
-
const found = wsResult.linkeGringoTabs.length > 0;
|
|
159
|
-
return {
|
|
160
|
-
isRunning: true,
|
|
161
|
-
port: activePortData.port,
|
|
162
|
-
host: "127.0.0.1",
|
|
163
|
-
browser: "Google Chrome (DevTools Protocol)",
|
|
164
|
-
protocolVersion: "1.3",
|
|
165
|
-
activeTabs: wsResult.linkeGringoTabs,
|
|
166
|
-
linkeGringoTabs: wsResult.linkeGringoTabs,
|
|
167
|
-
otherTabsCount: wsResult.otherTabsCount,
|
|
168
|
-
linkeGringoTabFound: found,
|
|
169
|
-
linkeGringoTabUrl: wsResult.linkeGringoTabs[0]?.url,
|
|
170
|
-
sessionState: wsResult.sessionState
|
|
171
|
-
};
|
|
207
|
+
} catch {
|
|
172
208
|
}
|
|
173
209
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
signal: controller.signal
|
|
179
|
-
});
|
|
180
|
-
if (!versionRes.ok) {
|
|
181
|
-
throw new Error(`HTTP ${versionRes.status}: ${versionRes.statusText}`);
|
|
182
|
-
}
|
|
183
|
-
const versionData = await versionRes.json();
|
|
184
|
-
let pageTabs = [];
|
|
210
|
+
return bridgeJobStore.completeJob(jobId, result);
|
|
211
|
+
}
|
|
212
|
+
async function failRemoteOrLocalJob(jobId, error, bridgeUrl = DEFAULT_BRIDGE_URL) {
|
|
213
|
+
if (bridgeUrl) {
|
|
185
214
|
try {
|
|
186
|
-
const
|
|
215
|
+
const controller = new AbortController();
|
|
216
|
+
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
217
|
+
const res = await fetch(`${bridgeUrl}/api/jobs/${encodeURIComponent(jobId)}/fail`, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: { "Content-Type": "application/json" },
|
|
220
|
+
body: JSON.stringify({ error }),
|
|
187
221
|
signal: controller.signal
|
|
188
222
|
});
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
223
|
+
clearTimeout(timer);
|
|
224
|
+
if (res.ok) {
|
|
225
|
+
const data = await res.json();
|
|
226
|
+
if (data.ok && data.job) {
|
|
227
|
+
try {
|
|
228
|
+
if (bridgeJobStore.getJob(jobId)) {
|
|
229
|
+
bridgeJobStore.failJob(jobId, error);
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
}
|
|
233
|
+
return data.job;
|
|
193
234
|
}
|
|
194
235
|
}
|
|
195
236
|
} catch {
|
|
196
237
|
}
|
|
197
|
-
const linkeGringoTabs = pageTabs.filter((t) => isLinkeGringoUrl(t.url)).map((t) => ({
|
|
198
|
-
id: t.id,
|
|
199
|
-
title: t.title,
|
|
200
|
-
url: t.url,
|
|
201
|
-
webSocketDebuggerUrl: t.webSocketDebuggerUrl
|
|
202
|
-
}));
|
|
203
|
-
const otherTabsCount = pageTabs.length - linkeGringoTabs.length;
|
|
204
|
-
const found = linkeGringoTabs.length > 0;
|
|
205
|
-
return {
|
|
206
|
-
isRunning: true,
|
|
207
|
-
port,
|
|
208
|
-
host,
|
|
209
|
-
browser: versionData.Browser,
|
|
210
|
-
protocolVersion: versionData["Protocol-Version"],
|
|
211
|
-
activeTabs: linkeGringoTabs,
|
|
212
|
-
linkeGringoTabs,
|
|
213
|
-
otherTabsCount,
|
|
214
|
-
linkeGringoTabFound: found,
|
|
215
|
-
linkeGringoTabUrl: linkeGringoTabs[0]?.url,
|
|
216
|
-
sessionState: null
|
|
217
|
-
};
|
|
218
|
-
} catch (err) {
|
|
219
|
-
const error = err;
|
|
220
|
-
const isTimeout = error.name === "AbortError" || error.name === "TimeoutError";
|
|
221
|
-
return {
|
|
222
|
-
isRunning: false,
|
|
223
|
-
port,
|
|
224
|
-
host,
|
|
225
|
-
activeTabs: [],
|
|
226
|
-
linkeGringoTabs: [],
|
|
227
|
-
otherTabsCount: 0,
|
|
228
|
-
linkeGringoTabFound: false,
|
|
229
|
-
sessionState: null,
|
|
230
|
-
error: isTimeout ? "Conex\xE3o expirou (Chrome n\xE3o respondeu em 2s na porta " + port + ")" : "Porta fechada ou depura\xE7\xE3o remota desativada. Acesse chrome://inspect/#remote-debugging para ativar."
|
|
231
|
-
};
|
|
232
|
-
} finally {
|
|
233
|
-
clearTimeout(timeoutId);
|
|
234
238
|
}
|
|
239
|
+
return bridgeJobStore.failJob(jobId, error);
|
|
235
240
|
}
|
|
236
241
|
|
|
237
242
|
// src/tools/audit-profile.ts
|
|
@@ -286,74 +291,59 @@ async function handleAuditProfile(input) {
|
|
|
286
291
|
let skills = input.skills || [];
|
|
287
292
|
const rawExperiences = [...input.experiences || []];
|
|
288
293
|
if (!headline && !input.profileText && rawExperiences.length === 0) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
rawExperiences.push({
|
|
299
|
-
company: exp.companyName || exp.company || "Company",
|
|
300
|
-
title: exp.title || "Engineer",
|
|
301
|
-
bullets: exp.bullets,
|
|
302
|
-
description: exp.description
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
} else if (cdp.linkeGringoTabFound) {
|
|
307
|
-
return {
|
|
308
|
-
content: [
|
|
309
|
-
{
|
|
310
|
-
type: "text",
|
|
311
|
-
text: `
|
|
312
|
-
# \u26A0\uFE0F Nenhum Perfil Carregado no LinkeGringo
|
|
294
|
+
const pendingJob = await getRemoteOrLocalPendingJob();
|
|
295
|
+
if (pendingJob) {
|
|
296
|
+
const p = pendingJob.payload || {};
|
|
297
|
+
return {
|
|
298
|
+
content: [
|
|
299
|
+
{
|
|
300
|
+
type: "text",
|
|
301
|
+
text: `
|
|
302
|
+
# \u{1F4C4} Job Pendente da Interface Web Detectado!
|
|
313
303
|
|
|
314
|
-
|
|
304
|
+
O front-end em \`localhost:5173\` submeteu o seguinte job para o Agente de IA:
|
|
305
|
+
- **Job ID**: \`${pendingJob.id}\`
|
|
306
|
+
- **Tipo**: \`${pendingJob.type}\`
|
|
307
|
+
- **Cargo Almejado**: ${p.targetRole || input.targetRole || "Senior Software Engineer"}
|
|
308
|
+
- **Possui PDF Base64**: ${Boolean(p.pdfBase64)}
|
|
315
309
|
|
|
316
310
|
---
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
Assim que o upload for processado pela aplica\xE7\xE3o web, chame o \`audit_profile\` novamente para auditar o perfil real automaticamente!
|
|
311
|
+
\u{1F4A1} **A\xE7\xE3o para o Agente de IA**:
|
|
312
|
+
Processe a auditoria deste perfil e chame a ferramenta \`submit_job_result\` passando \`jobId: "${pendingJob.id}"\` e o resultado formatado (\`{ profile, review }\`).
|
|
313
|
+
A tela do usu\xE1rio avan\xE7ar\xE1 instantaneamente para o Diagn\xF3stico!
|
|
322
314
|
`.trim()
|
|
323
|
-
}
|
|
324
|
-
],
|
|
325
|
-
structuredData: {
|
|
326
|
-
status: "waiting_for_upload",
|
|
327
|
-
linkeGringoTabUrl: cdp.linkeGringoTabUrl,
|
|
328
|
-
message: "O usu\xE1rio ainda n\xE3o subiu o PDF do LinkedIn na aplica\xE7\xE3o web.",
|
|
329
|
-
actionRequired: "upload_pdf"
|
|
330
315
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
316
|
+
],
|
|
317
|
+
structuredData: {
|
|
318
|
+
status: "pending_bridge_job",
|
|
319
|
+
jobId: pendingJob.id,
|
|
320
|
+
jobType: pendingJob.type,
|
|
321
|
+
payload: pendingJob.payload,
|
|
322
|
+
actionRequired: "submit_job_result"
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
content: [
|
|
328
|
+
{
|
|
329
|
+
type: "text",
|
|
330
|
+
text: `
|
|
338
331
|
# \u26A0\uFE0F Nenhum Perfil Fornecido para Auditoria
|
|
339
332
|
|
|
340
|
-
Nenhum dado de perfil foi informado e
|
|
333
|
+
Nenhum dado de perfil foi informado e n\xE3o h\xE1 nenhum job pendente na interface web (porta 5174).
|
|
341
334
|
|
|
342
335
|
### Como prosseguir:
|
|
343
|
-
1. **Pela Web**: Abra o LinkeGringo
|
|
344
|
-
2. **Via Par\xE2metros**: Forne\xE7a o texto bruto do perfil no par\xE2metro \`profileText\` ou informe \`headline\`, \`experiences\` e \`skills
|
|
336
|
+
1. **Pela Interface Web**: Abra o LinkeGringo (http://localhost:5173), selecione o cargo-alvo e envie o PDF do LinkedIn no Modo Agente MCP; OU
|
|
337
|
+
2. **Via Par\xE2metros MCP**: Forne\xE7a o texto bruto do perfil no par\xE2metro \`profileText\` ou informe \`headline\`, \`experiences\` e \`skills\` diretamente nesta ferramenta.
|
|
345
338
|
`.trim()
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
};
|
|
339
|
+
}
|
|
340
|
+
],
|
|
341
|
+
structuredData: {
|
|
342
|
+
status: "missing_profile",
|
|
343
|
+
message: "Nenhum perfil fornecido e nenhum job pendente no Bridge HTTP.",
|
|
344
|
+
actionRequired: "provide_input_or_open_web"
|
|
354
345
|
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
346
|
+
};
|
|
357
347
|
}
|
|
358
348
|
if (input.profileText && !headline && rawExperiences.length === 0) {
|
|
359
349
|
const lines = input.profileText.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
@@ -1026,8 +1016,8 @@ var simulateRecruiterSearchInputSchema = z8.object({
|
|
|
1026
1016
|
});
|
|
1027
1017
|
async function handleSimulateRecruiterSearch(input) {
|
|
1028
1018
|
const defaultKeywords = input.requiredKeywords?.length ? input.requiredKeywords : [input.targetRole, "Senior", "Remote", "Architecture", "Scale"];
|
|
1029
|
-
const highPriorityText = `${input.headline} ${input.skills.join(" ")}`;
|
|
1030
|
-
const bodyText = `${input.summary} ${input.experienceBullets.join(" ")}`;
|
|
1019
|
+
const highPriorityText = `${input.headline || ""} ${(input.skills || []).join(" ")}`;
|
|
1020
|
+
const bodyText = `${input.summary || ""} ${(input.experienceBullets || []).join(" ")}`;
|
|
1031
1021
|
const evaluations = defaultKeywords.map((term) => {
|
|
1032
1022
|
const inHighPriority = termMatchesText(highPriorityText, term);
|
|
1033
1023
|
const inBody = termMatchesText(bodyText, term);
|
|
@@ -1200,12 +1190,13 @@ var generateHeadlineInputSchema = z10.object({
|
|
|
1200
1190
|
seniorityOrScope: z10.string().default("US Remote").describe("Senioridade ou disponibilidade (ex: US Remote, Global Teams, Staff)")
|
|
1201
1191
|
});
|
|
1202
1192
|
async function handleGenerateHeadline(input) {
|
|
1203
|
-
const
|
|
1193
|
+
const coreTechs = Array.isArray(input.coreTechnologies) ? input.coreTechnologies : ["TypeScript", "React", "Node.js"];
|
|
1194
|
+
const techsStr = coreTechs.slice(0, 4).join(" \u2022 ");
|
|
1204
1195
|
const diffStr = input.keyDifferentiator || "Distributed Systems";
|
|
1205
1196
|
const scopeStr = input.seniorityOrScope || "US Remote";
|
|
1206
1197
|
const option1 = `${input.targetRole} | ${techsStr} | ${diffStr} | ${scopeStr}`;
|
|
1207
1198
|
const option2 = `${input.targetRole} | Scaling ${diffStr} with ${techsStr} | ${scopeStr}`;
|
|
1208
|
-
const option3 = `${input.targetRole} | ${
|
|
1199
|
+
const option3 = `${input.targetRole} | ${coreTechs.slice(0, 3).join(", ")} Specialist | ${scopeStr}`;
|
|
1209
1200
|
const proposals = [
|
|
1210
1201
|
{
|
|
1211
1202
|
type: "Niche Specialist (Recomendada)",
|
|
@@ -1255,79 +1246,148 @@ ${proposals.map(
|
|
|
1255
1246
|
};
|
|
1256
1247
|
}
|
|
1257
1248
|
|
|
1258
|
-
// src/tools/
|
|
1249
|
+
// src/tools/get-pending-job.ts
|
|
1259
1250
|
import { z as z11 } from "zod";
|
|
1260
|
-
var
|
|
1261
|
-
|
|
1262
|
-
host: z11.string().default("127.0.0.1").describe("Host do Chrome (padr\xE3o 127.0.0.1)"),
|
|
1263
|
-
timeoutMs: z11.number().default(5e3).describe("Tempo limite em milissegundos para a conex\xE3o")
|
|
1251
|
+
var getPendingJobInputSchema = z11.object({
|
|
1252
|
+
jobId: z11.string().optional().describe("ID espec\xEDfico do job a ser buscado (opcional. Se omitido, pega o pr\xF3ximo da fila)")
|
|
1264
1253
|
});
|
|
1265
|
-
async function
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
`;
|
|
1281
|
-
} else {
|
|
1282
|
-
sessionSection = `
|
|
1283
|
-
### \u{1F464} Sess\xE3o do LinkeGringo: \u{1F7E1} Aguardando Upload do PDF
|
|
1284
|
-
- **Passo Atual**: \`upload\` (Tela inicial)
|
|
1285
|
-
- **Status do Arquivo**: Nenhum PDF do LinkedIn foi carregado ainda pelo usu\xE1rio.
|
|
1286
|
-
|
|
1287
|
-
> \u{1F4A1} **Instru\xE7\xE3o para a IA**: O usu\xE1rio est\xE1 com o LinkeGringo aberto, mas ainda n\xE3o subiu o PDF. **Pe\xE7a educadamente para o usu\xE1rio arrastar ou selecionar o PDF do seu perfil do LinkedIn no dropzone da aplica\xE7\xE3o web** (${status.linkeGringoTabUrl || "http://localhost:5173"}). Assim que o usu\xE1rio subir o PDF, voc\xEA ter\xE1 acesso instant\xE2neo aos dados para auditar e otimizar!
|
|
1288
|
-
`;
|
|
1289
|
-
}
|
|
1290
|
-
} else {
|
|
1291
|
-
sessionSection = `
|
|
1292
|
-
### \u{1F310} Sess\xE3o do LinkeGringo: \u26A0\uFE0F N\xE3o Detectada
|
|
1293
|
-
> O LinkeGringo n\xE3o foi detectado em nenhuma aba aberta. Pe\xE7a ao usu\xE1rio para abrir \`http://localhost:5173\` no navegador ou fornecer o texto do perfil diretamente.
|
|
1294
|
-
`;
|
|
1254
|
+
async function handleGetPendingJob(input = {}) {
|
|
1255
|
+
const job = await getRemoteOrLocalPendingJob(input.jobId);
|
|
1256
|
+
if (!job) {
|
|
1257
|
+
return {
|
|
1258
|
+
content: [
|
|
1259
|
+
{
|
|
1260
|
+
type: "text",
|
|
1261
|
+
text: "Nenhum job pendente no momento na interface web do LinkeGringo."
|
|
1262
|
+
}
|
|
1263
|
+
],
|
|
1264
|
+
structuredData: {
|
|
1265
|
+
jobFound: false,
|
|
1266
|
+
pendingCount: bridgeJobStore.getPendingCount()
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1295
1269
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
${
|
|
1303
|
-
- **
|
|
1304
|
-
- **
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
- **
|
|
1309
|
-
|
|
1310
|
-
${
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
> 3. Se estiver usando o servidor oficial DevTools MCP, configure com \`--autoConnect\`.
|
|
1318
|
-
`}
|
|
1270
|
+
bridgeJobStore.markProcessing(job.id);
|
|
1271
|
+
let details = "";
|
|
1272
|
+
if (job.type === "parse_and_diagnose") {
|
|
1273
|
+
const p = job.payload || {};
|
|
1274
|
+
details = `
|
|
1275
|
+
- **Tipo de A\xE7\xE3o**: Extra\xE7\xE3o Multimodal e Diagn\xF3stico de Perfil (parseAndDiagnose)
|
|
1276
|
+
- **Cargo Almejado**: ${p.targetRole || "N\xE3o especificado"}
|
|
1277
|
+
- **Possui PDF Base64**: ${Boolean(p.pdfBase64)} (${p.pdfBase64 ? Math.round(p.pdfBase64.length / 1024) + " KB base64" : "N/A"})
|
|
1278
|
+
- **Possui Texto**: ${Boolean(p.pdfText)}
|
|
1279
|
+
`.trim();
|
|
1280
|
+
} else if (job.type === "generate_interview") {
|
|
1281
|
+
details = `
|
|
1282
|
+
- **Tipo de A\xE7\xE3o**: Gera\xE7\xE3o de Perguntas de Entrevista T\xE9cnica
|
|
1283
|
+
- **Candidato**: ${job.payload?.profile?.name || "N\xE3o informado"}
|
|
1284
|
+
- **Cargo Almejado**: ${job.payload?.objective?.primaryRole || "N\xE3o informado"}
|
|
1285
|
+
`.trim();
|
|
1286
|
+
} else if (job.type === "generate_rewritten_profile") {
|
|
1287
|
+
details = `
|
|
1288
|
+
- **Tipo de A\xE7\xE3o**: Reescrita do Perfil com Bullets Google XYZ
|
|
1289
|
+
- **Candidato**: ${job.payload?.profile?.name || "N\xE3o informado"}
|
|
1290
|
+
- **Fatos Confirmados**: ${job.payload?.confirmedFacts?.length || 0} fatos
|
|
1319
1291
|
`.trim();
|
|
1292
|
+
}
|
|
1320
1293
|
return {
|
|
1321
1294
|
content: [
|
|
1322
1295
|
{
|
|
1323
1296
|
type: "text",
|
|
1324
|
-
text:
|
|
1297
|
+
text: `
|
|
1298
|
+
# \u{1F4E5} Job da Interface Web Encontrado!
|
|
1299
|
+
|
|
1300
|
+
- **Job ID**: \`${job.id}\`
|
|
1301
|
+
- **Tipo**: \`${job.type}\`
|
|
1302
|
+
- **Criado em**: ${new Date(job.createdAt).toLocaleTimeString()}
|
|
1303
|
+
|
|
1304
|
+
${details}
|
|
1305
|
+
|
|
1306
|
+
---
|
|
1307
|
+
\u{1F4A1} **Instru\xE7\xF5es para o Agente de IA**:
|
|
1308
|
+
1. Processe a intelig\xEAncia requerida para este job com base nas diretrizes do LinkeGringo.
|
|
1309
|
+
2. Ao concluir, chame a ferramenta \`submit_job_result\` passando \`jobId: "${job.id}"\` e o JSON estruturado no campo \`result\`.
|
|
1310
|
+
3. A interface web em \`localhost:5173\` atualizar\xE1 a tela no mesmo instante!
|
|
1311
|
+
`.trim()
|
|
1325
1312
|
}
|
|
1326
1313
|
],
|
|
1327
|
-
structuredData:
|
|
1314
|
+
structuredData: {
|
|
1315
|
+
jobFound: true,
|
|
1316
|
+
job: {
|
|
1317
|
+
id: job.id,
|
|
1318
|
+
type: job.type,
|
|
1319
|
+
payload: job.payload,
|
|
1320
|
+
createdAt: job.createdAt
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1328
1323
|
};
|
|
1329
1324
|
}
|
|
1330
1325
|
|
|
1326
|
+
// src/tools/submit-job-result.ts
|
|
1327
|
+
import { z as z12 } from "zod";
|
|
1328
|
+
var submitJobResultInputSchema = z12.object({
|
|
1329
|
+
jobId: z12.string().describe("ID do job a ser conclu\xEDdo"),
|
|
1330
|
+
result: z12.any().describe("JSON com os dados estruturados exigidos pelo front-end (ex: ParseAndDiagnoseResult, InterviewPlan, ProfileAnalysis)"),
|
|
1331
|
+
status: z12.enum(["completed", "failed"]).default("completed").describe("Status final do job"),
|
|
1332
|
+
error: z12.string().optional().describe('Mensagem de erro caso o status seja "failed"')
|
|
1333
|
+
});
|
|
1334
|
+
async function handleSubmitJobResult(input) {
|
|
1335
|
+
try {
|
|
1336
|
+
if (input.status === "failed") {
|
|
1337
|
+
const job2 = await failRemoteOrLocalJob(input.jobId, input.error || "Falha no processamento pelo agente");
|
|
1338
|
+
return {
|
|
1339
|
+
content: [
|
|
1340
|
+
{
|
|
1341
|
+
type: "text",
|
|
1342
|
+
text: `\u26A0\uFE0F Job ${job2.id} marcado como com falha. A interface web exibir\xE1 a mensagem de erro.`
|
|
1343
|
+
}
|
|
1344
|
+
],
|
|
1345
|
+
structuredData: {
|
|
1346
|
+
success: false,
|
|
1347
|
+
jobId: job2.id,
|
|
1348
|
+
status: "failed"
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
const job = await completeRemoteOrLocalJob(input.jobId, input.result);
|
|
1353
|
+
return {
|
|
1354
|
+
content: [
|
|
1355
|
+
{
|
|
1356
|
+
type: "text",
|
|
1357
|
+
text: `
|
|
1358
|
+
\u2705 **Resultado Enviado com Sucesso para a Interface Web!**
|
|
1359
|
+
|
|
1360
|
+
- **Job ID**: \`${job.id}\`
|
|
1361
|
+
- **Tipo**: \`${job.type}\`
|
|
1362
|
+
- **Status**: \`completed\`
|
|
1363
|
+
|
|
1364
|
+
O front-end em \`localhost:5173\` acabou de receber a resposta formatada e atualizou a tela automaticamente!
|
|
1365
|
+
`.trim()
|
|
1366
|
+
}
|
|
1367
|
+
],
|
|
1368
|
+
structuredData: {
|
|
1369
|
+
success: true,
|
|
1370
|
+
jobId: job.id,
|
|
1371
|
+
status: "completed",
|
|
1372
|
+
type: job.type
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
} catch (err) {
|
|
1376
|
+
return {
|
|
1377
|
+
content: [
|
|
1378
|
+
{
|
|
1379
|
+
type: "text",
|
|
1380
|
+
text: `\u274C Erro ao submeter resultado para o job "${input.jobId}": ${err.message}`
|
|
1381
|
+
}
|
|
1382
|
+
],
|
|
1383
|
+
structuredData: {
|
|
1384
|
+
success: false,
|
|
1385
|
+
error: err.message
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1331
1391
|
// src/server.ts
|
|
1332
1392
|
function createLinkeGringoMcpServer() {
|
|
1333
1393
|
const server = new McpServer({
|
|
@@ -1375,13 +1435,23 @@ function createLinkeGringoMcpServer() {
|
|
|
1375
1435
|
}
|
|
1376
1436
|
);
|
|
1377
1437
|
server.registerTool(
|
|
1378
|
-
"
|
|
1438
|
+
"get_pending_job",
|
|
1379
1439
|
{
|
|
1380
|
-
description: "
|
|
1381
|
-
inputSchema:
|
|
1440
|
+
description: "Busca o pr\xF3ximo job pendente enviado pela interface gr\xE1fica web do LinkeGringo (ex: upload de perfil para diagn\xF3stico, gera\xE7\xE3o de entrevista, reescrita de perfil).",
|
|
1441
|
+
inputSchema: getPendingJobInputSchema.shape
|
|
1382
1442
|
},
|
|
1383
1443
|
async (args) => {
|
|
1384
|
-
return await
|
|
1444
|
+
return await handleGetPendingJob(args);
|
|
1445
|
+
}
|
|
1446
|
+
);
|
|
1447
|
+
server.registerTool(
|
|
1448
|
+
"submit_job_result",
|
|
1449
|
+
{
|
|
1450
|
+
description: "Envia o resultado processado pelo agente de IA de volta para a interface gr\xE1fica web do LinkeGringo, desbloqueando o front-end e atualizando a tela imediatamente.",
|
|
1451
|
+
inputSchema: submitJobResultInputSchema.shape
|
|
1452
|
+
},
|
|
1453
|
+
async (args) => {
|
|
1454
|
+
return await handleSubmitJobResult(args);
|
|
1385
1455
|
}
|
|
1386
1456
|
);
|
|
1387
1457
|
server.registerResource(
|
|
@@ -1422,49 +1492,49 @@ function createLinkeGringoMcpServer() {
|
|
|
1422
1492
|
}
|
|
1423
1493
|
|
|
1424
1494
|
// src/cli/installer.ts
|
|
1425
|
-
import
|
|
1426
|
-
import
|
|
1427
|
-
import
|
|
1495
|
+
import fs from "fs";
|
|
1496
|
+
import path from "path";
|
|
1497
|
+
import os from "os";
|
|
1428
1498
|
function getMcpConfigsForSystem() {
|
|
1429
|
-
const home =
|
|
1430
|
-
const platform =
|
|
1499
|
+
const home = os.homedir();
|
|
1500
|
+
const platform = os.platform();
|
|
1431
1501
|
const configs = [];
|
|
1432
|
-
const antigravityPath =
|
|
1502
|
+
const antigravityPath = path.join(home, ".gemini", "config", "mcp_config.json");
|
|
1433
1503
|
configs.push({
|
|
1434
1504
|
id: "antigravity",
|
|
1435
1505
|
client: "Google Antigravity",
|
|
1436
1506
|
configPath: antigravityPath,
|
|
1437
|
-
detected:
|
|
1507
|
+
detected: fs.existsSync(path.join(home, ".gemini")) || fs.existsSync(antigravityPath)
|
|
1438
1508
|
});
|
|
1439
1509
|
let claudePath;
|
|
1440
1510
|
let claudeDir;
|
|
1441
1511
|
if (platform === "darwin") {
|
|
1442
|
-
claudeDir =
|
|
1443
|
-
claudePath =
|
|
1512
|
+
claudeDir = path.join(home, "Library", "Application Support", "Claude");
|
|
1513
|
+
claudePath = path.join(claudeDir, "claude_desktop_config.json");
|
|
1444
1514
|
} else if (platform === "win32") {
|
|
1445
|
-
claudeDir =
|
|
1446
|
-
claudePath =
|
|
1515
|
+
claudeDir = path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "Claude");
|
|
1516
|
+
claudePath = path.join(claudeDir, "claude_desktop_config.json");
|
|
1447
1517
|
} else {
|
|
1448
|
-
claudeDir =
|
|
1449
|
-
claudePath =
|
|
1518
|
+
claudeDir = path.join(home, ".config", "Claude");
|
|
1519
|
+
claudePath = path.join(claudeDir, "claude_desktop_config.json");
|
|
1450
1520
|
}
|
|
1451
1521
|
configs.push({
|
|
1452
1522
|
id: "claude",
|
|
1453
1523
|
client: `Claude Desktop (${platform === "darwin" ? "macOS" : platform === "win32" ? "Windows" : "Linux"})`,
|
|
1454
1524
|
configPath: claudePath,
|
|
1455
|
-
detected:
|
|
1525
|
+
detected: fs.existsSync(claudeDir) || fs.existsSync(claudePath)
|
|
1456
1526
|
});
|
|
1457
|
-
const cursorDir =
|
|
1458
|
-
const cursorPath =
|
|
1527
|
+
const cursorDir = path.join(home, ".cursor");
|
|
1528
|
+
const cursorPath = path.join(cursorDir, "mcp.json");
|
|
1459
1529
|
configs.push({
|
|
1460
1530
|
id: "cursor",
|
|
1461
1531
|
client: "Cursor AI",
|
|
1462
1532
|
configPath: cursorPath,
|
|
1463
|
-
detected:
|
|
1533
|
+
detected: fs.existsSync(cursorDir) || fs.existsSync(cursorPath)
|
|
1464
1534
|
});
|
|
1465
|
-
const windsurfDir =
|
|
1466
|
-
const windsurfPath =
|
|
1467
|
-
if (
|
|
1535
|
+
const windsurfDir = path.join(home, ".codeium", "windsurf");
|
|
1536
|
+
const windsurfPath = path.join(windsurfDir, "mcp_config.json");
|
|
1537
|
+
if (fs.existsSync(windsurfDir) || fs.existsSync(windsurfPath)) {
|
|
1468
1538
|
configs.push({
|
|
1469
1539
|
id: "windsurf",
|
|
1470
1540
|
client: "Windsurf",
|
|
@@ -1475,15 +1545,15 @@ function getMcpConfigsForSystem() {
|
|
|
1475
1545
|
return configs;
|
|
1476
1546
|
}
|
|
1477
1547
|
function installMcpServerConfig(configPath) {
|
|
1478
|
-
const dir =
|
|
1479
|
-
if (!
|
|
1480
|
-
|
|
1548
|
+
const dir = path.dirname(configPath);
|
|
1549
|
+
if (!fs.existsSync(dir)) {
|
|
1550
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1481
1551
|
}
|
|
1482
1552
|
let configData = { mcpServers: {} };
|
|
1483
1553
|
let isNew = true;
|
|
1484
|
-
if (
|
|
1554
|
+
if (fs.existsSync(configPath)) {
|
|
1485
1555
|
try {
|
|
1486
|
-
const raw =
|
|
1556
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
1487
1557
|
if (raw.trim()) {
|
|
1488
1558
|
configData = JSON.parse(raw);
|
|
1489
1559
|
isNew = false;
|
|
@@ -1499,11 +1569,7 @@ function installMcpServerConfig(configPath) {
|
|
|
1499
1569
|
command: "npx",
|
|
1500
1570
|
args: ["-y", "@linkegringo/mcp"]
|
|
1501
1571
|
};
|
|
1502
|
-
configData
|
|
1503
|
-
command: "npx",
|
|
1504
|
-
args: ["-y", "chrome-devtools-mcp@latest", "--autoConnect"]
|
|
1505
|
-
};
|
|
1506
|
-
fs2.writeFileSync(configPath, JSON.stringify(configData, null, 2) + "\n", "utf8");
|
|
1572
|
+
fs.writeFileSync(configPath, JSON.stringify(configData, null, 2) + "\n", "utf8");
|
|
1507
1573
|
return {
|
|
1508
1574
|
status: isNew ? "created" : "updated",
|
|
1509
1575
|
path: configPath
|
|
@@ -1531,8 +1597,8 @@ function runInstaller(args = process.argv) {
|
|
|
1531
1597
|
const results = [];
|
|
1532
1598
|
if (options.local) {
|
|
1533
1599
|
const cwd = process.cwd();
|
|
1534
|
-
const localCursorDir =
|
|
1535
|
-
const localPath =
|
|
1600
|
+
const localCursorDir = path.join(cwd, ".cursor");
|
|
1601
|
+
const localPath = path.join(localCursorDir, "mcp.json");
|
|
1536
1602
|
try {
|
|
1537
1603
|
const res = installMcpServerConfig(localPath);
|
|
1538
1604
|
results.push({
|
|
@@ -1604,14 +1670,393 @@ function runInstaller(args = process.argv) {
|
|
|
1604
1670
|
return results;
|
|
1605
1671
|
}
|
|
1606
1672
|
|
|
1673
|
+
// src/bridge/server.ts
|
|
1674
|
+
import http from "http";
|
|
1675
|
+
function setCorsHeaders(res) {
|
|
1676
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1677
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1678
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
1679
|
+
res.setHeader("Access-Control-Max-Age", "86400");
|
|
1680
|
+
}
|
|
1681
|
+
function sendJson(res, statusCode, data) {
|
|
1682
|
+
setCorsHeaders(res);
|
|
1683
|
+
res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
|
|
1684
|
+
res.end(JSON.stringify(data));
|
|
1685
|
+
}
|
|
1686
|
+
async function parseBody(req) {
|
|
1687
|
+
return new Promise((resolve, reject) => {
|
|
1688
|
+
let body = "";
|
|
1689
|
+
req.on("data", (chunk) => {
|
|
1690
|
+
body += chunk;
|
|
1691
|
+
if (body.length > 50 * 1024 * 1024) {
|
|
1692
|
+
req.destroy();
|
|
1693
|
+
reject(new Error("Payload too large"));
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
req.on("end", () => {
|
|
1697
|
+
try {
|
|
1698
|
+
resolve(body ? JSON.parse(body) : {});
|
|
1699
|
+
} catch (err) {
|
|
1700
|
+
reject(err);
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
req.on("error", reject);
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
function createBridgeHttpServer(options = {}) {
|
|
1707
|
+
const server = http.createServer(async (req, res) => {
|
|
1708
|
+
setCorsHeaders(res);
|
|
1709
|
+
if (req.method === "OPTIONS") {
|
|
1710
|
+
res.writeHead(204);
|
|
1711
|
+
res.end();
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
const parsedUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
1715
|
+
const pathname = parsedUrl.pathname;
|
|
1716
|
+
try {
|
|
1717
|
+
if (req.method === "GET" && (pathname === "/health" || pathname === "/api/health")) {
|
|
1718
|
+
sendJson(res, 200, {
|
|
1719
|
+
status: "ok",
|
|
1720
|
+
server: "linkegringo-mcp-bridge",
|
|
1721
|
+
watcherConnected: bridgeJobStore.hasActiveWatcher(),
|
|
1722
|
+
watcherCount: bridgeJobStore.getWatcherCount(),
|
|
1723
|
+
pendingCount: bridgeJobStore.getPendingCount(),
|
|
1724
|
+
totalJobs: bridgeJobStore.getAllJobs().length
|
|
1725
|
+
});
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
if (req.method === "GET" && pathname === "/api/bridge/status") {
|
|
1729
|
+
const pendingJob = bridgeJobStore.getPendingJob();
|
|
1730
|
+
sendJson(res, 200, {
|
|
1731
|
+
ok: true,
|
|
1732
|
+
status: "ready",
|
|
1733
|
+
server: "linkegringo-mcp-bridge",
|
|
1734
|
+
watcherConnected: bridgeJobStore.hasActiveWatcher(),
|
|
1735
|
+
watcherCount: bridgeJobStore.getWatcherCount(),
|
|
1736
|
+
pendingCount: bridgeJobStore.getPendingCount(),
|
|
1737
|
+
activeJobId: pendingJob ? pendingJob.id : null,
|
|
1738
|
+
totalJobs: bridgeJobStore.getAllJobs().length
|
|
1739
|
+
});
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
if (req.method === "POST" && pathname === "/api/bridge/disconnect") {
|
|
1743
|
+
const canceledCount = bridgeJobStore.cancelAllPending("Cancelado pelo usu\xE1rio na interface web.");
|
|
1744
|
+
sendJson(res, 200, {
|
|
1745
|
+
ok: true,
|
|
1746
|
+
message: "Conex\xE3o cancelada e fila de jobs limpa com sucesso.",
|
|
1747
|
+
canceledCount
|
|
1748
|
+
});
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
if (req.method === "GET" && pathname === "/api/jobs/stream") {
|
|
1752
|
+
res.writeHead(200, {
|
|
1753
|
+
"Content-Type": "text/event-stream",
|
|
1754
|
+
"Cache-Control": "no-cache, no-transform",
|
|
1755
|
+
"Connection": "keep-alive",
|
|
1756
|
+
"Access-Control-Allow-Origin": "*"
|
|
1757
|
+
});
|
|
1758
|
+
res.flushHeaders?.();
|
|
1759
|
+
const watcherId = `watcher_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
1760
|
+
bridgeJobStore.registerWatcher(watcherId);
|
|
1761
|
+
res.write(`data: ${JSON.stringify({ type: "connected", watcherId, pendingCount: bridgeJobStore.getPendingCount() })}
|
|
1762
|
+
|
|
1763
|
+
`);
|
|
1764
|
+
const onJobCreated = (job) => {
|
|
1765
|
+
res.write(`data: ${JSON.stringify({ type: "job_created", job: { id: job.id, type: job.type, createdAt: job.createdAt } })}
|
|
1766
|
+
|
|
1767
|
+
`);
|
|
1768
|
+
};
|
|
1769
|
+
const onJobCompleted = (job) => {
|
|
1770
|
+
res.write(`data: ${JSON.stringify({ type: "job_completed", job: { id: job.id, type: job.type } })}
|
|
1771
|
+
|
|
1772
|
+
`);
|
|
1773
|
+
};
|
|
1774
|
+
const onJobFailed = (job) => {
|
|
1775
|
+
res.write(`data: ${JSON.stringify({ type: "job_failed", job: { id: job.id, type: job.type, error: job.error } })}
|
|
1776
|
+
|
|
1777
|
+
`);
|
|
1778
|
+
};
|
|
1779
|
+
bridgeJobStore.on("job:created", onJobCreated);
|
|
1780
|
+
bridgeJobStore.on("job:completed", onJobCompleted);
|
|
1781
|
+
bridgeJobStore.on("job:failed", onJobFailed);
|
|
1782
|
+
const heartbeat = setInterval(() => {
|
|
1783
|
+
res.write(`: heartbeat
|
|
1784
|
+
|
|
1785
|
+
`);
|
|
1786
|
+
}, 15e3);
|
|
1787
|
+
req.on("close", () => {
|
|
1788
|
+
clearInterval(heartbeat);
|
|
1789
|
+
bridgeJobStore.unregisterWatcher(watcherId);
|
|
1790
|
+
bridgeJobStore.off("job:created", onJobCreated);
|
|
1791
|
+
bridgeJobStore.off("job:completed", onJobCompleted);
|
|
1792
|
+
bridgeJobStore.off("job:failed", onJobFailed);
|
|
1793
|
+
});
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
if (req.method === "POST" && pathname === "/api/jobs") {
|
|
1797
|
+
const body = await parseBody(req);
|
|
1798
|
+
if (!body.type) {
|
|
1799
|
+
sendJson(res, 400, { ok: false, error: 'Campo "type" \xE9 obrigat\xF3rio.' });
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
const job = bridgeJobStore.createJob(body.type, body.payload || {});
|
|
1803
|
+
options.onJobCreated?.(job);
|
|
1804
|
+
sendJson(res, 201, { ok: true, job });
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
if (req.method === "GET" && pathname === "/api/jobs") {
|
|
1808
|
+
sendJson(res, 200, { ok: true, jobs: bridgeJobStore.getAllJobs() });
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
if (req.method === "GET" && pathname === "/api/jobs/pending") {
|
|
1812
|
+
const id = parsedUrl.searchParams.get("id") || void 0;
|
|
1813
|
+
const job = bridgeJobStore.getPendingJob(id);
|
|
1814
|
+
sendJson(res, 200, { ok: true, job: job || null });
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
const jobMatch = pathname.match(/^\/api\/jobs\/([a-zA-Z0-9_-]+)$/);
|
|
1818
|
+
if (req.method === "GET" && jobMatch) {
|
|
1819
|
+
const jobId = jobMatch[1];
|
|
1820
|
+
const wait = parsedUrl.searchParams.get("wait") === "true";
|
|
1821
|
+
const timeout = parseInt(parsedUrl.searchParams.get("timeout") || "30000", 10);
|
|
1822
|
+
let job = bridgeJobStore.getJob(jobId);
|
|
1823
|
+
if (!job) {
|
|
1824
|
+
sendJson(res, 404, { ok: false, error: `Job n\xE3o encontrado: ${jobId}` });
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
if (wait && (job.status === "pending" || job.status === "processing")) {
|
|
1828
|
+
try {
|
|
1829
|
+
job = await bridgeJobStore.waitForJob(jobId, timeout);
|
|
1830
|
+
} catch {
|
|
1831
|
+
job = bridgeJobStore.getJob(jobId) || job;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
sendJson(res, 200, { ok: true, job });
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
const completeMatch = pathname.match(/^\/api\/jobs\/([a-zA-Z0-9_-]+)\/complete$/);
|
|
1838
|
+
if (req.method === "POST" && completeMatch) {
|
|
1839
|
+
const jobId = completeMatch[1];
|
|
1840
|
+
const body = await parseBody(req);
|
|
1841
|
+
try {
|
|
1842
|
+
const job = bridgeJobStore.completeJob(jobId, body.result);
|
|
1843
|
+
sendJson(res, 200, { ok: true, job });
|
|
1844
|
+
} catch (err) {
|
|
1845
|
+
sendJson(res, 404, { ok: false, error: err.message });
|
|
1846
|
+
}
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
const failMatch = pathname.match(/^\/api\/jobs\/([a-zA-Z0-9_-]+)\/fail$/);
|
|
1850
|
+
if (req.method === "POST" && failMatch) {
|
|
1851
|
+
const jobId = failMatch[1];
|
|
1852
|
+
const body = await parseBody(req);
|
|
1853
|
+
try {
|
|
1854
|
+
const job = bridgeJobStore.failJob(jobId, body.error || "Erro desconhecido");
|
|
1855
|
+
sendJson(res, 200, { ok: true, job });
|
|
1856
|
+
} catch (err) {
|
|
1857
|
+
sendJson(res, 404, { ok: false, error: err.message });
|
|
1858
|
+
}
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
if (req.method === "POST" && pathname === "/api/jobs/clear") {
|
|
1862
|
+
bridgeJobStore.clear();
|
|
1863
|
+
sendJson(res, 200, { ok: true, message: "Fila de jobs limpa com sucesso." });
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
sendJson(res, 404, { ok: false, error: "Rota n\xE3o encontrada" });
|
|
1867
|
+
} catch (err) {
|
|
1868
|
+
sendJson(res, 500, { ok: false, error: err?.message || "Erro interno no servidor bridge" });
|
|
1869
|
+
}
|
|
1870
|
+
});
|
|
1871
|
+
return server;
|
|
1872
|
+
}
|
|
1873
|
+
var activeBridgeServer = null;
|
|
1874
|
+
var activePort = 5174;
|
|
1875
|
+
async function startBridgeServer(options = {}) {
|
|
1876
|
+
if (activeBridgeServer) {
|
|
1877
|
+
return {
|
|
1878
|
+
server: activeBridgeServer,
|
|
1879
|
+
port: activePort,
|
|
1880
|
+
close: stopBridgeServer
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
const port = options.port || parseInt(process.env.LINKEGRINGO_BRIDGE_PORT || "5174", 10);
|
|
1884
|
+
const host = options.host || "127.0.0.1";
|
|
1885
|
+
const server = createBridgeHttpServer(options);
|
|
1886
|
+
return new Promise((resolve, reject) => {
|
|
1887
|
+
server.on("error", (err) => {
|
|
1888
|
+
if (err.code === "EADDRINUSE") {
|
|
1889
|
+
console.error(`[LinkeGringo Bridge] Porta ${port} j\xE1 est\xE1 em uso. Reutilizando porta existente.`);
|
|
1890
|
+
resolve({
|
|
1891
|
+
server,
|
|
1892
|
+
port,
|
|
1893
|
+
close: stopBridgeServer
|
|
1894
|
+
});
|
|
1895
|
+
} else {
|
|
1896
|
+
reject(err);
|
|
1897
|
+
}
|
|
1898
|
+
});
|
|
1899
|
+
server.listen(port, host, () => {
|
|
1900
|
+
activeBridgeServer = server;
|
|
1901
|
+
activePort = port;
|
|
1902
|
+
console.error(`[LinkeGringo Bridge] Servidor HTTP local ativo em http://${host}:${port}`);
|
|
1903
|
+
resolve({
|
|
1904
|
+
server,
|
|
1905
|
+
port,
|
|
1906
|
+
close: stopBridgeServer
|
|
1907
|
+
});
|
|
1908
|
+
});
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
async function stopBridgeServer() {
|
|
1912
|
+
if (!activeBridgeServer) return;
|
|
1913
|
+
return new Promise((resolve) => {
|
|
1914
|
+
activeBridgeServer?.close(() => {
|
|
1915
|
+
activeBridgeServer = null;
|
|
1916
|
+
resolve();
|
|
1917
|
+
});
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
// src/bridge/watcher.ts
|
|
1922
|
+
import http2 from "http";
|
|
1923
|
+
function startBridgeWatcher(options = {}) {
|
|
1924
|
+
const bridgeUrl = options.bridgeUrl || process.env.LINKEGRINGO_BRIDGE_URL || "http://127.0.0.1:5174";
|
|
1925
|
+
let isClosed = false;
|
|
1926
|
+
let currentReq = null;
|
|
1927
|
+
let retryTimer = null;
|
|
1928
|
+
let retryDelay = 1e3;
|
|
1929
|
+
function connect() {
|
|
1930
|
+
if (isClosed) return;
|
|
1931
|
+
try {
|
|
1932
|
+
const url = new URL("/api/jobs/stream", bridgeUrl);
|
|
1933
|
+
const req = http2.get(
|
|
1934
|
+
url.toString(),
|
|
1935
|
+
{
|
|
1936
|
+
headers: {
|
|
1937
|
+
Accept: "text/event-stream",
|
|
1938
|
+
"Cache-Control": "no-cache"
|
|
1939
|
+
}
|
|
1940
|
+
},
|
|
1941
|
+
(res) => {
|
|
1942
|
+
if (res.statusCode !== 200) {
|
|
1943
|
+
options.onError?.(new Error(`Falha ao conectar no stream SSE: HTTP ${res.statusCode}`));
|
|
1944
|
+
scheduleRetry();
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
retryDelay = 1e3;
|
|
1948
|
+
let buffer = "";
|
|
1949
|
+
res.on("data", (chunk) => {
|
|
1950
|
+
buffer += chunk.toString("utf-8");
|
|
1951
|
+
const lines = buffer.split("\n\n");
|
|
1952
|
+
buffer = lines.pop() || "";
|
|
1953
|
+
for (const block of lines) {
|
|
1954
|
+
for (const line of block.split("\n")) {
|
|
1955
|
+
if (line.startsWith("data: ")) {
|
|
1956
|
+
try {
|
|
1957
|
+
const payload = JSON.parse(line.slice(6));
|
|
1958
|
+
if (payload.type === "connected") {
|
|
1959
|
+
options.onConnect?.(payload);
|
|
1960
|
+
console.log(
|
|
1961
|
+
`[LinkeGringo Watcher] \u{1F7E2} Conectado ao bridge (${bridgeUrl}). Watcher ativo: ${payload.watcherId} (Jobs pendentes: ${payload.pendingCount})`
|
|
1962
|
+
);
|
|
1963
|
+
if (options.once && payload.pendingCount > 0) {
|
|
1964
|
+
console.log(
|
|
1965
|
+
`[LinkeGringo Watcher] \u{1F680} JOB DETECTADO NA FILA (${payload.pendingCount} pendente(s)). Finalizando para ativar agente...`
|
|
1966
|
+
);
|
|
1967
|
+
setTimeout(() => process.exit(0), 50);
|
|
1968
|
+
}
|
|
1969
|
+
} else if (payload.type === "job_created") {
|
|
1970
|
+
console.log(
|
|
1971
|
+
`
|
|
1972
|
+
[LinkeGringo Watcher] \u{1F680} NOVO JOB DETECTADO: ${payload.job.id} | Tipo: ${payload.job.type}`
|
|
1973
|
+
);
|
|
1974
|
+
options.onJob?.(payload.job);
|
|
1975
|
+
if (options.once) {
|
|
1976
|
+
setTimeout(() => process.exit(0), 50);
|
|
1977
|
+
}
|
|
1978
|
+
} else if (payload.type === "job_completed") {
|
|
1979
|
+
console.log(`[LinkeGringo Watcher] \u2705 Job finalizado: ${payload.job.id}`);
|
|
1980
|
+
} else if (payload.type === "job_failed") {
|
|
1981
|
+
console.log(
|
|
1982
|
+
`[LinkeGringo Watcher] \u26A0\uFE0F Job cancelado ou com erro: ${payload.job.id} (${payload.job.error})`
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
} catch {
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
});
|
|
1991
|
+
res.on("end", () => {
|
|
1992
|
+
if (!isClosed) {
|
|
1993
|
+
scheduleRetry();
|
|
1994
|
+
}
|
|
1995
|
+
});
|
|
1996
|
+
res.on("error", (err) => {
|
|
1997
|
+
options.onError?.(err);
|
|
1998
|
+
if (!isClosed) {
|
|
1999
|
+
scheduleRetry();
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
);
|
|
2004
|
+
req.on("error", (err) => {
|
|
2005
|
+
options.onError?.(err);
|
|
2006
|
+
if (!isClosed) {
|
|
2007
|
+
scheduleRetry();
|
|
2008
|
+
}
|
|
2009
|
+
});
|
|
2010
|
+
currentReq = req;
|
|
2011
|
+
} catch (err) {
|
|
2012
|
+
options.onError?.(err);
|
|
2013
|
+
scheduleRetry();
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
function scheduleRetry() {
|
|
2017
|
+
if (isClosed || retryTimer) return;
|
|
2018
|
+
retryTimer = setTimeout(() => {
|
|
2019
|
+
retryTimer = null;
|
|
2020
|
+
retryDelay = Math.min(retryDelay * 1.5, 1e4);
|
|
2021
|
+
connect();
|
|
2022
|
+
}, retryDelay);
|
|
2023
|
+
}
|
|
2024
|
+
connect();
|
|
2025
|
+
return {
|
|
2026
|
+
close() {
|
|
2027
|
+
isClosed = true;
|
|
2028
|
+
if (retryTimer) {
|
|
2029
|
+
clearTimeout(retryTimer);
|
|
2030
|
+
retryTimer = null;
|
|
2031
|
+
}
|
|
2032
|
+
if (currentReq) {
|
|
2033
|
+
currentReq.destroy();
|
|
2034
|
+
currentReq = null;
|
|
2035
|
+
}
|
|
2036
|
+
console.log("[LinkeGringo Watcher] Modo watch desconectado.");
|
|
2037
|
+
}
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
|
|
1607
2041
|
// src/index.ts
|
|
1608
|
-
import
|
|
2042
|
+
import fs2 from "fs";
|
|
1609
2043
|
import { fileURLToPath } from "url";
|
|
1610
2044
|
async function main() {
|
|
1611
2045
|
if (process.argv.includes("install") || process.argv.includes("setup") || process.argv.includes("--install")) {
|
|
1612
2046
|
runInstaller(process.argv);
|
|
1613
2047
|
return;
|
|
1614
2048
|
}
|
|
2049
|
+
if (process.argv.includes("watch") || process.argv.includes("--watch") || process.argv.includes("listen")) {
|
|
2050
|
+
const once = process.argv.includes("--once") || process.argv.includes("-1");
|
|
2051
|
+
console.log(`[LinkeGringo Watcher] Iniciando modo escuta ultraleve${once ? " (once)" : ""}...`);
|
|
2052
|
+
startBridgeWatcher({ once });
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
try {
|
|
2056
|
+
await startBridgeServer();
|
|
2057
|
+
} catch (err) {
|
|
2058
|
+
console.error("[LinkeGringo Bridge] N\xE3o foi poss\xEDvel iniciar bridge HTTP:", err);
|
|
2059
|
+
}
|
|
1615
2060
|
const server = createLinkeGringoMcpServer();
|
|
1616
2061
|
const transport = new StdioServerTransport();
|
|
1617
2062
|
await server.connect(transport);
|
|
@@ -1621,7 +2066,7 @@ function isDirectExecution() {
|
|
|
1621
2066
|
if (!process.argv[1]) return false;
|
|
1622
2067
|
try {
|
|
1623
2068
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
1624
|
-
const scriptPath =
|
|
2069
|
+
const scriptPath = fs2.existsSync(process.argv[1]) ? fs2.realpathSync(process.argv[1]) : process.argv[1];
|
|
1625
2070
|
return currentFilePath === scriptPath || process.argv[1].endsWith("index.js") || process.argv[1].endsWith("linkegringo-mcp") || process.argv[1].endsWith("mcp") || process.argv[1].endsWith("linkegringo");
|
|
1626
2071
|
} catch {
|
|
1627
2072
|
return true;
|
|
@@ -1635,25 +2080,31 @@ if (isDirectExecution()) {
|
|
|
1635
2080
|
}
|
|
1636
2081
|
export {
|
|
1637
2082
|
auditProfileInputSchema,
|
|
1638
|
-
|
|
1639
|
-
|
|
2083
|
+
bridgeJobStore,
|
|
2084
|
+
completeRemoteOrLocalJob,
|
|
1640
2085
|
convertToXyzBulletInputSchema,
|
|
2086
|
+
createBridgeHttpServer,
|
|
1641
2087
|
createLinkeGringoMcpServer,
|
|
1642
2088
|
detectSparseExperiences,
|
|
1643
|
-
|
|
2089
|
+
failRemoteOrLocalJob,
|
|
1644
2090
|
formatGoogleXyzBullet,
|
|
1645
2091
|
generateHeadlineInputSchema,
|
|
1646
2092
|
getExperienceBulletCount,
|
|
1647
2093
|
getMcpConfigsForSystem,
|
|
2094
|
+
getPendingJobInputSchema,
|
|
2095
|
+
getRemoteOrLocalPendingJob,
|
|
1648
2096
|
handleAuditProfile,
|
|
1649
|
-
handleCheckChromeCdp,
|
|
1650
2097
|
handleConvertToXyzBullet,
|
|
1651
2098
|
handleGenerateHeadline,
|
|
2099
|
+
handleGetPendingJob,
|
|
1652
2100
|
handleSimulateRecruiterSearch,
|
|
2101
|
+
handleSubmitJobResult,
|
|
1653
2102
|
installMcpServerConfig,
|
|
1654
|
-
isLinkeGringoUrl,
|
|
1655
2103
|
parseArgs,
|
|
1656
|
-
probeViaWebSocket,
|
|
1657
2104
|
runInstaller,
|
|
1658
|
-
simulateRecruiterSearchInputSchema
|
|
2105
|
+
simulateRecruiterSearchInputSchema,
|
|
2106
|
+
startBridgeServer,
|
|
2107
|
+
startBridgeWatcher,
|
|
2108
|
+
stopBridgeServer,
|
|
2109
|
+
submitJobResultInputSchema
|
|
1659
2110
|
};
|