@linkegringo/mcp 1.0.2 → 1.0.4
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/index.d.ts +327 -12
- package/dist/index.js +462 -112
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { Experience } from '@linkegringo/core';
|
|
2
4
|
|
|
3
5
|
declare function createLinkeGringoMcpServer(): McpServer;
|
|
4
6
|
|
|
@@ -19,39 +21,352 @@ interface ChromeTabInfo {
|
|
|
19
21
|
webSocketDebuggerUrl?: string;
|
|
20
22
|
devtoolsFrontendUrl?: string;
|
|
21
23
|
}
|
|
24
|
+
interface LinkeGringoTabInfo {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
url: string;
|
|
28
|
+
webSocketDebuggerUrl?: string;
|
|
29
|
+
}
|
|
30
|
+
interface LinkeGringoSessionState {
|
|
31
|
+
hasUploadedProfile: boolean;
|
|
32
|
+
candidateName?: string;
|
|
33
|
+
targetRole?: string;
|
|
34
|
+
step?: string;
|
|
35
|
+
inboundScore?: number;
|
|
36
|
+
profile?: any;
|
|
37
|
+
review?: any;
|
|
38
|
+
}
|
|
22
39
|
interface CdpStatus {
|
|
23
40
|
isRunning: boolean;
|
|
24
41
|
port: number;
|
|
25
42
|
host: string;
|
|
26
43
|
browser?: string;
|
|
27
44
|
protocolVersion?: string;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Abas ativas restritas estritamente ao escopo do LinkeGringo (Privacy Shield).
|
|
47
|
+
* Abas pessoais (e-mails, mensageiros, bancos) são completamente omitidas.
|
|
48
|
+
*/
|
|
49
|
+
activeTabs: LinkeGringoTabInfo[];
|
|
50
|
+
linkeGringoTabs: LinkeGringoTabInfo[];
|
|
51
|
+
otherTabsCount: number;
|
|
34
52
|
linkeGringoTabFound: boolean;
|
|
35
53
|
linkeGringoTabUrl?: string;
|
|
54
|
+
sessionState?: LinkeGringoSessionState | null;
|
|
36
55
|
error?: string;
|
|
37
56
|
}
|
|
38
57
|
|
|
58
|
+
declare function isLinkeGringoUrl(url?: string): boolean;
|
|
59
|
+
declare function findDevToolsActivePort(): {
|
|
60
|
+
port: number;
|
|
61
|
+
wsPath: string;
|
|
62
|
+
} | null;
|
|
63
|
+
declare function probeViaWebSocket(port: number, wsPath: string, timeoutMs?: number): Promise<{
|
|
64
|
+
linkeGringoTabs: LinkeGringoTabInfo[];
|
|
65
|
+
otherTabsCount: number;
|
|
66
|
+
sessionState: LinkeGringoSessionState | null;
|
|
67
|
+
} | null>;
|
|
39
68
|
declare function checkChromeCdp(port?: number, host?: string, timeoutMs?: number): Promise<CdpStatus>;
|
|
40
69
|
|
|
70
|
+
interface McpTarget {
|
|
71
|
+
id: string;
|
|
72
|
+
client: string;
|
|
73
|
+
configPath: string;
|
|
74
|
+
detected: boolean;
|
|
75
|
+
}
|
|
41
76
|
interface InstallResult {
|
|
42
77
|
client: string;
|
|
43
78
|
configPath: string;
|
|
44
79
|
status: 'created' | 'updated' | 'skipped' | 'error';
|
|
45
80
|
message?: string;
|
|
46
81
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
82
|
+
interface InstallerOptions {
|
|
83
|
+
all?: boolean;
|
|
84
|
+
local?: boolean;
|
|
85
|
+
client?: string;
|
|
86
|
+
}
|
|
87
|
+
declare function getMcpConfigsForSystem(): McpTarget[];
|
|
51
88
|
declare function installMcpServerConfig(configPath: string): {
|
|
52
89
|
status: 'created' | 'updated';
|
|
53
90
|
path: string;
|
|
54
91
|
};
|
|
55
|
-
declare function
|
|
92
|
+
declare function parseArgs(args?: string[]): InstallerOptions;
|
|
93
|
+
declare function runInstaller(args?: string[]): InstallResult[];
|
|
94
|
+
|
|
95
|
+
interface SparseExperience {
|
|
96
|
+
company: string;
|
|
97
|
+
title: string;
|
|
98
|
+
estimatedBullets: number;
|
|
99
|
+
}
|
|
100
|
+
declare function getExperienceBulletCount(exp: {
|
|
101
|
+
description?: string;
|
|
102
|
+
bullets?: string[];
|
|
103
|
+
}): number;
|
|
104
|
+
declare function detectSparseExperiences(experiences?: Experience[]): SparseExperience[];
|
|
105
|
+
declare const auditProfileInputSchema: z.ZodObject<{
|
|
106
|
+
profileText: z.ZodOptional<z.ZodString>;
|
|
107
|
+
headline: z.ZodOptional<z.ZodString>;
|
|
108
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
109
|
+
experiences: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
110
|
+
company: z.ZodString;
|
|
111
|
+
title: z.ZodString;
|
|
112
|
+
bullets: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
113
|
+
description: z.ZodOptional<z.ZodString>;
|
|
114
|
+
}, "strip", z.ZodTypeAny, {
|
|
115
|
+
title: string;
|
|
116
|
+
company: string;
|
|
117
|
+
description?: string | undefined;
|
|
118
|
+
bullets?: string[] | undefined;
|
|
119
|
+
}, {
|
|
120
|
+
title: string;
|
|
121
|
+
company: string;
|
|
122
|
+
description?: string | undefined;
|
|
123
|
+
bullets?: string[] | undefined;
|
|
124
|
+
}>, "many">>;
|
|
125
|
+
skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
126
|
+
targetRole: z.ZodDefault<z.ZodString>;
|
|
127
|
+
targetMarket: z.ZodDefault<z.ZodString>;
|
|
128
|
+
}, "strip", z.ZodTypeAny, {
|
|
129
|
+
targetMarket: string;
|
|
130
|
+
targetRole: string;
|
|
131
|
+
headline?: string | undefined;
|
|
132
|
+
summary?: string | undefined;
|
|
133
|
+
experiences?: {
|
|
134
|
+
title: string;
|
|
135
|
+
company: string;
|
|
136
|
+
description?: string | undefined;
|
|
137
|
+
bullets?: string[] | undefined;
|
|
138
|
+
}[] | undefined;
|
|
139
|
+
skills?: string[] | undefined;
|
|
140
|
+
profileText?: string | undefined;
|
|
141
|
+
}, {
|
|
142
|
+
headline?: string | undefined;
|
|
143
|
+
summary?: string | undefined;
|
|
144
|
+
experiences?: {
|
|
145
|
+
title: string;
|
|
146
|
+
company: string;
|
|
147
|
+
description?: string | undefined;
|
|
148
|
+
bullets?: string[] | undefined;
|
|
149
|
+
}[] | undefined;
|
|
150
|
+
skills?: string[] | undefined;
|
|
151
|
+
targetMarket?: string | undefined;
|
|
152
|
+
targetRole?: string | undefined;
|
|
153
|
+
profileText?: string | undefined;
|
|
154
|
+
}>;
|
|
155
|
+
type AuditProfileInput = z.infer<typeof auditProfileInputSchema>;
|
|
156
|
+
interface AuditIssue {
|
|
157
|
+
severity: 'critical' | 'warning' | 'info';
|
|
158
|
+
category: 'headline' | 'experience' | 'about' | 'skills';
|
|
159
|
+
message: string;
|
|
160
|
+
recommendation: string;
|
|
161
|
+
}
|
|
162
|
+
declare function handleAuditProfile(input: AuditProfileInput): Promise<{
|
|
163
|
+
content: {
|
|
164
|
+
type: "text";
|
|
165
|
+
text: string;
|
|
166
|
+
}[];
|
|
167
|
+
structuredData: {
|
|
168
|
+
status: string;
|
|
169
|
+
linkeGringoTabUrl: string | undefined;
|
|
170
|
+
message: string;
|
|
171
|
+
actionRequired: string;
|
|
172
|
+
score?: undefined;
|
|
173
|
+
targetRole?: undefined;
|
|
174
|
+
headline?: undefined;
|
|
175
|
+
totalBullets?: undefined;
|
|
176
|
+
bulletsWithMetrics?: undefined;
|
|
177
|
+
sparseExperiences?: undefined;
|
|
178
|
+
triageBottlenecks?: undefined;
|
|
179
|
+
issues?: undefined;
|
|
180
|
+
};
|
|
181
|
+
} | {
|
|
182
|
+
content: {
|
|
183
|
+
type: "text";
|
|
184
|
+
text: string;
|
|
185
|
+
}[];
|
|
186
|
+
structuredData: {
|
|
187
|
+
status: string;
|
|
188
|
+
message: string;
|
|
189
|
+
actionRequired: string;
|
|
190
|
+
linkeGringoTabUrl?: undefined;
|
|
191
|
+
score?: undefined;
|
|
192
|
+
targetRole?: undefined;
|
|
193
|
+
headline?: undefined;
|
|
194
|
+
totalBullets?: undefined;
|
|
195
|
+
bulletsWithMetrics?: undefined;
|
|
196
|
+
sparseExperiences?: undefined;
|
|
197
|
+
triageBottlenecks?: undefined;
|
|
198
|
+
issues?: undefined;
|
|
199
|
+
};
|
|
200
|
+
} | {
|
|
201
|
+
content: {
|
|
202
|
+
type: "text";
|
|
203
|
+
text: string;
|
|
204
|
+
}[];
|
|
205
|
+
structuredData: {
|
|
206
|
+
score: number;
|
|
207
|
+
targetRole: string;
|
|
208
|
+
headline: string;
|
|
209
|
+
totalBullets: number;
|
|
210
|
+
bulletsWithMetrics: number;
|
|
211
|
+
sparseExperiences: SparseExperience[];
|
|
212
|
+
triageBottlenecks: string[];
|
|
213
|
+
issues: AuditIssue[];
|
|
214
|
+
status?: undefined;
|
|
215
|
+
linkeGringoTabUrl?: undefined;
|
|
216
|
+
message?: undefined;
|
|
217
|
+
actionRequired?: undefined;
|
|
218
|
+
};
|
|
219
|
+
}>;
|
|
220
|
+
|
|
221
|
+
declare const simulateRecruiterSearchInputSchema: z.ZodObject<{
|
|
222
|
+
headline: z.ZodString;
|
|
223
|
+
summary: z.ZodDefault<z.ZodString>;
|
|
224
|
+
skills: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
225
|
+
experienceBullets: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
226
|
+
targetRole: z.ZodDefault<z.ZodString>;
|
|
227
|
+
requiredKeywords: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
228
|
+
}, "strip", z.ZodTypeAny, {
|
|
229
|
+
headline: string;
|
|
230
|
+
summary: string;
|
|
231
|
+
skills: string[];
|
|
232
|
+
targetRole: string;
|
|
233
|
+
experienceBullets: string[];
|
|
234
|
+
requiredKeywords?: string[] | undefined;
|
|
235
|
+
}, {
|
|
236
|
+
headline: string;
|
|
237
|
+
summary?: string | undefined;
|
|
238
|
+
skills?: string[] | undefined;
|
|
239
|
+
targetRole?: string | undefined;
|
|
240
|
+
experienceBullets?: string[] | undefined;
|
|
241
|
+
requiredKeywords?: string[] | undefined;
|
|
242
|
+
}>;
|
|
243
|
+
type SimulateRecruiterSearchInput = z.infer<typeof simulateRecruiterSearchInputSchema>;
|
|
244
|
+
declare function handleSimulateRecruiterSearch(input: SimulateRecruiterSearchInput): Promise<{
|
|
245
|
+
content: {
|
|
246
|
+
type: "text";
|
|
247
|
+
text: string;
|
|
248
|
+
}[];
|
|
249
|
+
structuredData: {
|
|
250
|
+
overallStatus: "weak" | "missing" | "match";
|
|
251
|
+
matchPercentage: number;
|
|
252
|
+
evaluations: {
|
|
253
|
+
term: string;
|
|
254
|
+
status: "weak" | "missing" | "match";
|
|
255
|
+
detail: string;
|
|
256
|
+
}[];
|
|
257
|
+
matchCount: number;
|
|
258
|
+
weakCount: number;
|
|
259
|
+
missingCount: number;
|
|
260
|
+
};
|
|
261
|
+
}>;
|
|
262
|
+
|
|
263
|
+
declare const convertToXyzBulletInputSchema: z.ZodObject<{
|
|
264
|
+
rawBullet: z.ZodString;
|
|
265
|
+
roleContext: z.ZodDefault<z.ZodString>;
|
|
266
|
+
action: z.ZodOptional<z.ZodString>;
|
|
267
|
+
metric: z.ZodOptional<z.ZodString>;
|
|
268
|
+
method: z.ZodOptional<z.ZodString>;
|
|
269
|
+
}, "strip", z.ZodTypeAny, {
|
|
270
|
+
rawBullet: string;
|
|
271
|
+
roleContext: string;
|
|
272
|
+
method?: string | undefined;
|
|
273
|
+
metric?: string | undefined;
|
|
274
|
+
action?: string | undefined;
|
|
275
|
+
}, {
|
|
276
|
+
rawBullet: string;
|
|
277
|
+
method?: string | undefined;
|
|
278
|
+
metric?: string | undefined;
|
|
279
|
+
roleContext?: string | undefined;
|
|
280
|
+
action?: string | undefined;
|
|
281
|
+
}>;
|
|
282
|
+
type ConvertToXyzBulletInput = z.infer<typeof convertToXyzBulletInputSchema>;
|
|
283
|
+
declare function formatGoogleXyzBullet(parts: {
|
|
284
|
+
action: string;
|
|
285
|
+
metric: string;
|
|
286
|
+
method: string;
|
|
287
|
+
}): string;
|
|
288
|
+
declare function handleConvertToXyzBullet(input: ConvertToXyzBulletInput): Promise<{
|
|
289
|
+
content: {
|
|
290
|
+
type: "text";
|
|
291
|
+
text: string;
|
|
292
|
+
}[];
|
|
293
|
+
structuredData: {
|
|
294
|
+
formattedBullet: string;
|
|
295
|
+
parts: {
|
|
296
|
+
action: string;
|
|
297
|
+
metric: string;
|
|
298
|
+
method: string;
|
|
299
|
+
};
|
|
300
|
+
hasMetrics?: undefined;
|
|
301
|
+
rawBullet?: undefined;
|
|
302
|
+
proposals?: undefined;
|
|
303
|
+
};
|
|
304
|
+
} | {
|
|
305
|
+
content: {
|
|
306
|
+
type: "text";
|
|
307
|
+
text: string;
|
|
308
|
+
}[];
|
|
309
|
+
structuredData: {
|
|
310
|
+
hasMetrics: boolean;
|
|
311
|
+
rawBullet: string;
|
|
312
|
+
proposals: string[];
|
|
313
|
+
formattedBullet?: undefined;
|
|
314
|
+
parts?: undefined;
|
|
315
|
+
};
|
|
316
|
+
}>;
|
|
317
|
+
|
|
318
|
+
declare const generateHeadlineInputSchema: z.ZodObject<{
|
|
319
|
+
targetRole: z.ZodDefault<z.ZodString>;
|
|
320
|
+
coreTechnologies: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
321
|
+
keyDifferentiator: z.ZodOptional<z.ZodString>;
|
|
322
|
+
seniorityOrScope: z.ZodDefault<z.ZodString>;
|
|
323
|
+
}, "strip", z.ZodTypeAny, {
|
|
324
|
+
targetRole: string;
|
|
325
|
+
coreTechnologies: string[];
|
|
326
|
+
seniorityOrScope: string;
|
|
327
|
+
keyDifferentiator?: string | undefined;
|
|
328
|
+
}, {
|
|
329
|
+
targetRole?: string | undefined;
|
|
330
|
+
coreTechnologies?: string[] | undefined;
|
|
331
|
+
keyDifferentiator?: string | undefined;
|
|
332
|
+
seniorityOrScope?: string | undefined;
|
|
333
|
+
}>;
|
|
334
|
+
type GenerateHeadlineInput = z.infer<typeof generateHeadlineInputSchema>;
|
|
335
|
+
declare function handleGenerateHeadline(input: GenerateHeadlineInput): Promise<{
|
|
336
|
+
content: {
|
|
337
|
+
type: "text";
|
|
338
|
+
text: string;
|
|
339
|
+
}[];
|
|
340
|
+
structuredData: {
|
|
341
|
+
proposals: {
|
|
342
|
+
type: string;
|
|
343
|
+
headline: string;
|
|
344
|
+
charCount: number;
|
|
345
|
+
focus: string;
|
|
346
|
+
}[];
|
|
347
|
+
};
|
|
348
|
+
}>;
|
|
349
|
+
|
|
350
|
+
declare const checkChromeCdpInputSchema: z.ZodObject<{
|
|
351
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
352
|
+
host: z.ZodDefault<z.ZodString>;
|
|
353
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
354
|
+
}, "strip", z.ZodTypeAny, {
|
|
355
|
+
port: number;
|
|
356
|
+
host: string;
|
|
357
|
+
timeoutMs: number;
|
|
358
|
+
}, {
|
|
359
|
+
port?: number | undefined;
|
|
360
|
+
host?: string | undefined;
|
|
361
|
+
timeoutMs?: number | undefined;
|
|
362
|
+
}>;
|
|
363
|
+
type CheckChromeCdpInput = z.infer<typeof checkChromeCdpInputSchema>;
|
|
364
|
+
declare function handleCheckChromeCdp(input: CheckChromeCdpInput): Promise<{
|
|
365
|
+
content: {
|
|
366
|
+
type: "text";
|
|
367
|
+
text: string;
|
|
368
|
+
}[];
|
|
369
|
+
structuredData: CdpStatus;
|
|
370
|
+
}>;
|
|
56
371
|
|
|
57
|
-
export { type CdpStatus, type ChromeTabInfo, type ChromeVersionResponse, type InstallResult, checkChromeCdp, createLinkeGringoMcpServer, getMcpConfigsForSystem, installMcpServerConfig, runInstaller };
|
|
372
|
+
export { type AuditIssue, type AuditProfileInput, type CdpStatus, type CheckChromeCdpInput, type ChromeTabInfo, type ChromeVersionResponse, type ConvertToXyzBulletInput, type GenerateHeadlineInput, type InstallResult, type InstallerOptions, type LinkeGringoSessionState, type LinkeGringoTabInfo, type McpTarget, type SimulateRecruiterSearchInput, type SparseExperience, auditProfileInputSchema, checkChromeCdp, checkChromeCdpInputSchema, convertToXyzBulletInputSchema, createLinkeGringoMcpServer, detectSparseExperiences, findDevToolsActivePort, formatGoogleXyzBullet, generateHeadlineInputSchema, getExperienceBulletCount, getMcpConfigsForSystem, handleAuditProfile, handleCheckChromeCdp, handleConvertToXyzBullet, handleGenerateHeadline, handleSimulateRecruiterSearch, installMcpServerConfig, isLinkeGringoUrl, parseArgs, probeViaWebSocket, runInstaller, simulateRecruiterSearchInputSchema };
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,233 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
8
8
|
|
|
9
9
|
// src/tools/audit-profile.ts
|
|
10
10
|
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
// src/cdp/probe.ts
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import os from "os";
|
|
15
|
+
import path from "path";
|
|
16
|
+
function isLinkeGringoUrl(url = "") {
|
|
17
|
+
const lower = url.toLowerCase();
|
|
18
|
+
return lower.includes("localhost:5173") || lower.includes("127.0.0.1:5173") || lower.includes("localhost:4173") || lower.includes("127.0.0.1:4173") || lower.includes("linkegringo") || lower.includes("muriel-gasparini.github.io");
|
|
19
|
+
}
|
|
20
|
+
function findDevToolsActivePort() {
|
|
21
|
+
const possiblePaths = [
|
|
22
|
+
// Linux
|
|
23
|
+
path.join(os.homedir(), ".config", "google-chrome", "DevToolsActivePort"),
|
|
24
|
+
path.join(os.homedir(), ".config", "chromium", "DevToolsActivePort"),
|
|
25
|
+
path.join(os.homedir(), ".config", "google-chrome-beta", "DevToolsActivePort"),
|
|
26
|
+
// macOS
|
|
27
|
+
path.join(os.homedir(), "Library", "Application Support", "Google", "Chrome", "DevToolsActivePort"),
|
|
28
|
+
// Windows
|
|
29
|
+
path.join(process.env.LOCALAPPDATA || "", "Google", "Chrome", "User Data", "DevToolsActivePort")
|
|
30
|
+
];
|
|
31
|
+
for (const p of possiblePaths) {
|
|
32
|
+
if (p && fs.existsSync(p)) {
|
|
33
|
+
try {
|
|
34
|
+
const lines = fs.readFileSync(p, "utf8").trim().split("\n");
|
|
35
|
+
if (lines.length >= 2) {
|
|
36
|
+
const port = parseInt(lines[0].trim(), 10);
|
|
37
|
+
const wsPath = lines[1].trim();
|
|
38
|
+
if (port > 0 && wsPath) {
|
|
39
|
+
return { port, wsPath };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
async function probeViaWebSocket(port, wsPath, timeoutMs = 2e3) {
|
|
49
|
+
const wsUrl = `ws://127.0.0.1:${port}${wsPath}`;
|
|
50
|
+
if (typeof globalThis.WebSocket !== "function") return null;
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
let ws;
|
|
53
|
+
const timer = setTimeout(() => {
|
|
54
|
+
try {
|
|
55
|
+
ws?.close();
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
resolve(null);
|
|
59
|
+
}, timeoutMs);
|
|
60
|
+
try {
|
|
61
|
+
ws = new globalThis.WebSocket(wsUrl);
|
|
62
|
+
} catch {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
resolve(null);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
let linkeTarget = null;
|
|
68
|
+
let linkeGringoTabs = [];
|
|
69
|
+
let otherTabsCount = 0;
|
|
70
|
+
ws.onopen = () => {
|
|
71
|
+
ws.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
|
|
72
|
+
};
|
|
73
|
+
ws.onmessage = (event) => {
|
|
74
|
+
try {
|
|
75
|
+
const msg = JSON.parse(String(event.data));
|
|
76
|
+
if (msg.id === 1) {
|
|
77
|
+
const targets = msg.result?.targetInfos || [];
|
|
78
|
+
const pageTargets = targets.filter((t) => t.type === "page");
|
|
79
|
+
const matched = pageTargets.filter((t) => isLinkeGringoUrl(t.url));
|
|
80
|
+
linkeGringoTabs = matched.map((t) => ({
|
|
81
|
+
id: t.targetId,
|
|
82
|
+
title: t.title,
|
|
83
|
+
url: t.url
|
|
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;
|
|
118
|
+
try {
|
|
119
|
+
if (raw) session = JSON.parse(raw);
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
const hasUploadedProfile = Boolean(session && session.profile);
|
|
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
|
+
});
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
try {
|
|
141
|
+
ws?.close();
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
resolve(null);
|
|
145
|
+
}
|
|
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 = 2e3) {
|
|
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
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const controller = new AbortController();
|
|
175
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
176
|
+
try {
|
|
177
|
+
const versionRes = await fetch(`http://${host}:${port}/json/version`, {
|
|
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 = [];
|
|
185
|
+
try {
|
|
186
|
+
const listRes = await fetch(`http://${host}:${port}/json/list`, {
|
|
187
|
+
signal: controller.signal
|
|
188
|
+
});
|
|
189
|
+
if (listRes.ok) {
|
|
190
|
+
const rawTabs = await listRes.json();
|
|
191
|
+
if (Array.isArray(rawTabs)) {
|
|
192
|
+
pageTabs = rawTabs.filter((t) => t.type === "page");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
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
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/tools/audit-profile.ts
|
|
11
238
|
function getExperienceBulletCount(exp) {
|
|
12
239
|
if (Array.isArray(exp.bullets) && exp.bullets.length > 0) {
|
|
13
240
|
return exp.bullets.length;
|
|
@@ -57,7 +284,77 @@ async function handleAuditProfile(input) {
|
|
|
57
284
|
let headline = input.headline || "";
|
|
58
285
|
let summary = input.summary || "";
|
|
59
286
|
let skills = input.skills || [];
|
|
60
|
-
const rawExperiences = input.experiences || [];
|
|
287
|
+
const rawExperiences = [...input.experiences || []];
|
|
288
|
+
if (!headline && !input.profileText && rawExperiences.length === 0) {
|
|
289
|
+
try {
|
|
290
|
+
const cdp = await checkChromeCdp();
|
|
291
|
+
if (cdp.sessionState?.hasUploadedProfile && cdp.sessionState.profile) {
|
|
292
|
+
const p = cdp.sessionState.profile;
|
|
293
|
+
headline = p.headline || "";
|
|
294
|
+
summary = p.summary || "";
|
|
295
|
+
skills = p.skills || [];
|
|
296
|
+
if (Array.isArray(p.experiences)) {
|
|
297
|
+
for (const exp of p.experiences) {
|
|
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
|
|
313
|
+
|
|
314
|
+
A aplica\xE7\xE3o **LinkeGringo est\xE1 aberta no seu navegador** (\`${cdp.linkeGringoTabUrl || "http://localhost:5173"}\`), mas o **upload do PDF do LinkedIn ainda n\xE3o foi realizado**.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
### \u{1F4A1} Pr\xF3ximo Passo
|
|
319
|
+
Por favor, acesse a aba do LinkeGringo no seu navegador e **arraste o PDF do seu perfil do LinkedIn para o dropzone** (ou clique para selecionar o arquivo).
|
|
320
|
+
|
|
321
|
+
Assim que o upload for processado pela aplica\xE7\xE3o web, chame o \`audit_profile\` novamente para auditar o perfil real automaticamente!
|
|
322
|
+
`.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
|
+
}
|
|
331
|
+
};
|
|
332
|
+
} else {
|
|
333
|
+
return {
|
|
334
|
+
content: [
|
|
335
|
+
{
|
|
336
|
+
type: "text",
|
|
337
|
+
text: `
|
|
338
|
+
# \u26A0\uFE0F Nenhum Perfil Fornecido para Auditoria
|
|
339
|
+
|
|
340
|
+
Nenhum dado de perfil foi informado e a aplica\xE7\xE3o LinkeGringo n\xE3o foi detectada no navegador.
|
|
341
|
+
|
|
342
|
+
### Como prosseguir:
|
|
343
|
+
1. **Pela Web**: Abra o LinkeGringo em \`http://localhost:5173\` no Google Chrome e fa\xE7a o upload do PDF do seu LinkedIn; OU
|
|
344
|
+
2. **Via Par\xE2metros**: Forne\xE7a o texto bruto do perfil no par\xE2metro \`profileText\` ou informe \`headline\`, \`experiences\` e \`skills\`.
|
|
345
|
+
`.trim()
|
|
346
|
+
}
|
|
347
|
+
],
|
|
348
|
+
structuredData: {
|
|
349
|
+
status: "missing_profile",
|
|
350
|
+
message: "Nenhum perfil fornecido e aplica\xE7\xE3o LinkeGringo n\xE3o detectada no navegador.",
|
|
351
|
+
actionRequired: "provide_input_or_open_web"
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
356
|
+
}
|
|
357
|
+
}
|
|
61
358
|
if (input.profileText && !headline && rawExperiences.length === 0) {
|
|
62
359
|
const lines = input.profileText.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
63
360
|
if (lines.length > 0) {
|
|
@@ -960,68 +1257,6 @@ ${proposals.map(
|
|
|
960
1257
|
|
|
961
1258
|
// src/tools/cdp-check.ts
|
|
962
1259
|
import { z as z11 } from "zod";
|
|
963
|
-
|
|
964
|
-
// src/cdp/probe.ts
|
|
965
|
-
async function checkChromeCdp(port = 9222, host = "127.0.0.1", timeoutMs = 2e3) {
|
|
966
|
-
const controller = new AbortController();
|
|
967
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
968
|
-
try {
|
|
969
|
-
const versionRes = await fetch(`http://${host}:${port}/json/version`, {
|
|
970
|
-
signal: controller.signal
|
|
971
|
-
});
|
|
972
|
-
if (!versionRes.ok) {
|
|
973
|
-
throw new Error(`HTTP ${versionRes.status}: ${versionRes.statusText}`);
|
|
974
|
-
}
|
|
975
|
-
const versionData = await versionRes.json();
|
|
976
|
-
let pageTabs = [];
|
|
977
|
-
try {
|
|
978
|
-
const listRes = await fetch(`http://${host}:${port}/json/list`, {
|
|
979
|
-
signal: controller.signal
|
|
980
|
-
});
|
|
981
|
-
if (listRes.ok) {
|
|
982
|
-
const rawTabs = await listRes.json();
|
|
983
|
-
if (Array.isArray(rawTabs)) {
|
|
984
|
-
pageTabs = rawTabs.filter((t) => t.type === "page");
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
} catch {
|
|
988
|
-
}
|
|
989
|
-
const linkeGringoTab = pageTabs.find((t) => {
|
|
990
|
-
const lowerUrl = (t.url || "").toLowerCase();
|
|
991
|
-
return lowerUrl.includes("linkegringo") || lowerUrl.includes("5173") || lowerUrl.includes("muriel-gasparini.github.io");
|
|
992
|
-
});
|
|
993
|
-
return {
|
|
994
|
-
isRunning: true,
|
|
995
|
-
port,
|
|
996
|
-
host,
|
|
997
|
-
browser: versionData.Browser,
|
|
998
|
-
protocolVersion: versionData["Protocol-Version"],
|
|
999
|
-
activeTabs: pageTabs.map((t) => ({
|
|
1000
|
-
id: t.id,
|
|
1001
|
-
title: t.title,
|
|
1002
|
-
url: t.url,
|
|
1003
|
-
webSocketDebuggerUrl: t.webSocketDebuggerUrl
|
|
1004
|
-
})),
|
|
1005
|
-
linkeGringoTabFound: Boolean(linkeGringoTab),
|
|
1006
|
-
linkeGringoTabUrl: linkeGringoTab?.url
|
|
1007
|
-
};
|
|
1008
|
-
} catch (err) {
|
|
1009
|
-
const error = err;
|
|
1010
|
-
const isTimeout = error.name === "AbortError" || error.name === "TimeoutError";
|
|
1011
|
-
return {
|
|
1012
|
-
isRunning: false,
|
|
1013
|
-
port,
|
|
1014
|
-
host,
|
|
1015
|
-
activeTabs: [],
|
|
1016
|
-
linkeGringoTabFound: false,
|
|
1017
|
-
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."
|
|
1018
|
-
};
|
|
1019
|
-
} finally {
|
|
1020
|
-
clearTimeout(timeoutId);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
// src/tools/cdp-check.ts
|
|
1025
1260
|
var checkChromeCdpInputSchema = z11.object({
|
|
1026
1261
|
port: z11.number().default(9222).describe("Porta do Chrome DevTools Protocol a ser testada (padr\xE3o 9222)"),
|
|
1027
1262
|
host: z11.string().default("127.0.0.1").describe("Host do Chrome (padr\xE3o 127.0.0.1)"),
|
|
@@ -1029,20 +1264,50 @@ var checkChromeCdpInputSchema = z11.object({
|
|
|
1029
1264
|
});
|
|
1030
1265
|
async function handleCheckChromeCdp(input) {
|
|
1031
1266
|
const status = await checkChromeCdp(input.port, input.host, input.timeoutMs);
|
|
1267
|
+
const session = status.sessionState;
|
|
1268
|
+
const isProfileLoaded = Boolean(session?.hasUploadedProfile);
|
|
1269
|
+
let sessionSection = "";
|
|
1270
|
+
if (status.linkeGringoTabFound) {
|
|
1271
|
+
if (isProfileLoaded) {
|
|
1272
|
+
sessionSection = `
|
|
1273
|
+
### \u{1F464} Sess\xE3o do LinkeGringo: \u{1F7E2} Perfil Carregado
|
|
1274
|
+
- **Candidato**: ${session?.candidateName || "Identificado"}
|
|
1275
|
+
- **Cargo Alvo**: ${session?.targetRole || "N\xE3o especificado"}
|
|
1276
|
+
- **Passo Atual**: \`${session?.step || "diagnostic"}\`
|
|
1277
|
+
- **Inbound Score**: ${session?.inboundScore !== void 0 ? `${session.inboundScore}/100` : "Pronto para c\xE1lculo"}
|
|
1278
|
+
|
|
1279
|
+
> \u{1F4A1} **Instru\xE7\xE3o para a IA**: O perfil do usu\xE1rio j\xE1 est\xE1 carregado. Voc\xEA pode utilizar diretamente as ferramentas \`audit_profile\`, \`simulate_recruiter_search\`, \`generate_headline_proposals\` e \`convert_to_xyz_bullet\` com os dados do candidato.
|
|
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
|
+
`;
|
|
1295
|
+
}
|
|
1032
1296
|
const markdownSummary = `
|
|
1033
|
-
# Status do Chrome Remote Debugging (CDP)
|
|
1297
|
+
# Status do Chrome Remote Debugging (CDP) & LinkeGringo
|
|
1034
1298
|
|
|
1035
|
-
**Porta
|
|
1036
|
-
**Status
|
|
1299
|
+
**Porta CDP**: ${status.host}:${status.port}
|
|
1300
|
+
**Status do Chrome**: ${status.isRunning ? "\u{1F7E2} Conectado e Ativo" : "\u{1F534} Desconectado"}
|
|
1037
1301
|
|
|
1038
1302
|
${status.isRunning ? `
|
|
1039
|
-
- **Vers\xE3o do Navegador**: ${status.browser || "
|
|
1040
|
-
- **
|
|
1041
|
-
- **Total de Abas Abertas**: ${status.activeTabs.length}
|
|
1042
|
-
- **Aba do LinkeGringo**: ${status.linkeGringoTabFound ? `\u2713 Detectada (${status.linkeGringoTabUrl})` : "\u26A0\uFE0F Nenhuma aba do LinkeGringo aberta no momento"}
|
|
1303
|
+
- **Vers\xE3o do Navegador**: ${status.browser || "Google Chrome"}
|
|
1304
|
+
- **Aba do LinkeGringo**: ${status.linkeGringoTabFound ? `\u2713 Detectada (\`${status.linkeGringoTabUrl}\`)` : "\u26A0\uFE0F Nenhuma aba do LinkeGringo aberta"}
|
|
1043
1305
|
|
|
1044
|
-
|
|
1045
|
-
${status.
|
|
1306
|
+
### \u{1F6E1}\uFE0F Privacy Shield Ativo
|
|
1307
|
+
- **Abas Pessoais Protegidas**: ${status.otherTabsCount} aba(s) abertas no navegador foram preservadas sem inspe\xE7\xE3o (e-mails, mensageiros, documentos).
|
|
1308
|
+
- **Escopo Restrito**: O LinkeGringo acessa exclusivamente abas pertencentes \xE0 pr\xF3pria aplica\xE7\xE3o LinkeGringo.
|
|
1309
|
+
|
|
1310
|
+
${sessionSection}
|
|
1046
1311
|
` : `
|
|
1047
1312
|
> \u274C **Motivo**: ${status.error || "Porta fechada."}
|
|
1048
1313
|
>
|
|
@@ -1157,56 +1422,68 @@ function createLinkeGringoMcpServer() {
|
|
|
1157
1422
|
}
|
|
1158
1423
|
|
|
1159
1424
|
// src/cli/installer.ts
|
|
1160
|
-
import
|
|
1161
|
-
import
|
|
1162
|
-
import
|
|
1425
|
+
import fs2 from "fs";
|
|
1426
|
+
import path2 from "path";
|
|
1427
|
+
import os2 from "os";
|
|
1163
1428
|
function getMcpConfigsForSystem() {
|
|
1164
|
-
const home =
|
|
1165
|
-
const platform =
|
|
1429
|
+
const home = os2.homedir();
|
|
1430
|
+
const platform = os2.platform();
|
|
1166
1431
|
const configs = [];
|
|
1432
|
+
const antigravityPath = path2.join(home, ".gemini", "config", "mcp_config.json");
|
|
1167
1433
|
configs.push({
|
|
1434
|
+
id: "antigravity",
|
|
1168
1435
|
client: "Google Antigravity",
|
|
1169
|
-
configPath:
|
|
1436
|
+
configPath: antigravityPath,
|
|
1437
|
+
detected: fs2.existsSync(path2.join(home, ".gemini")) || fs2.existsSync(antigravityPath)
|
|
1170
1438
|
});
|
|
1439
|
+
let claudePath;
|
|
1440
|
+
let claudeDir;
|
|
1171
1441
|
if (platform === "darwin") {
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
configPath: path.join(
|
|
1175
|
-
home,
|
|
1176
|
-
"Library",
|
|
1177
|
-
"Application Support",
|
|
1178
|
-
"Claude",
|
|
1179
|
-
"claude_desktop_config.json"
|
|
1180
|
-
)
|
|
1181
|
-
});
|
|
1442
|
+
claudeDir = path2.join(home, "Library", "Application Support", "Claude");
|
|
1443
|
+
claudePath = path2.join(claudeDir, "claude_desktop_config.json");
|
|
1182
1444
|
} else if (platform === "win32") {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
client: "Claude Desktop (Windows)",
|
|
1186
|
-
configPath: path.join(appData, "Claude", "claude_desktop_config.json")
|
|
1187
|
-
});
|
|
1445
|
+
claudeDir = path2.join(process.env.APPDATA || path2.join(home, "AppData", "Roaming"), "Claude");
|
|
1446
|
+
claudePath = path2.join(claudeDir, "claude_desktop_config.json");
|
|
1188
1447
|
} else {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
configPath: path.join(home, ".config", "Claude", "claude_desktop_config.json")
|
|
1192
|
-
});
|
|
1448
|
+
claudeDir = path2.join(home, ".config", "Claude");
|
|
1449
|
+
claudePath = path2.join(claudeDir, "claude_desktop_config.json");
|
|
1193
1450
|
}
|
|
1194
1451
|
configs.push({
|
|
1452
|
+
id: "claude",
|
|
1453
|
+
client: `Claude Desktop (${platform === "darwin" ? "macOS" : platform === "win32" ? "Windows" : "Linux"})`,
|
|
1454
|
+
configPath: claudePath,
|
|
1455
|
+
detected: fs2.existsSync(claudeDir) || fs2.existsSync(claudePath)
|
|
1456
|
+
});
|
|
1457
|
+
const cursorDir = path2.join(home, ".cursor");
|
|
1458
|
+
const cursorPath = path2.join(cursorDir, "mcp.json");
|
|
1459
|
+
configs.push({
|
|
1460
|
+
id: "cursor",
|
|
1195
1461
|
client: "Cursor AI",
|
|
1196
|
-
configPath:
|
|
1462
|
+
configPath: cursorPath,
|
|
1463
|
+
detected: fs2.existsSync(cursorDir) || fs2.existsSync(cursorPath)
|
|
1197
1464
|
});
|
|
1465
|
+
const windsurfDir = path2.join(home, ".codeium", "windsurf");
|
|
1466
|
+
const windsurfPath = path2.join(windsurfDir, "mcp_config.json");
|
|
1467
|
+
if (fs2.existsSync(windsurfDir) || fs2.existsSync(windsurfPath)) {
|
|
1468
|
+
configs.push({
|
|
1469
|
+
id: "windsurf",
|
|
1470
|
+
client: "Windsurf",
|
|
1471
|
+
configPath: windsurfPath,
|
|
1472
|
+
detected: true
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1198
1475
|
return configs;
|
|
1199
1476
|
}
|
|
1200
1477
|
function installMcpServerConfig(configPath) {
|
|
1201
|
-
const dir =
|
|
1202
|
-
if (!
|
|
1203
|
-
|
|
1478
|
+
const dir = path2.dirname(configPath);
|
|
1479
|
+
if (!fs2.existsSync(dir)) {
|
|
1480
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1204
1481
|
}
|
|
1205
1482
|
let configData = { mcpServers: {} };
|
|
1206
1483
|
let isNew = true;
|
|
1207
|
-
if (
|
|
1484
|
+
if (fs2.existsSync(configPath)) {
|
|
1208
1485
|
try {
|
|
1209
|
-
const raw =
|
|
1486
|
+
const raw = fs2.readFileSync(configPath, "utf8");
|
|
1210
1487
|
if (raw.trim()) {
|
|
1211
1488
|
configData = JSON.parse(raw);
|
|
1212
1489
|
isNew = false;
|
|
@@ -1226,19 +1503,74 @@ function installMcpServerConfig(configPath) {
|
|
|
1226
1503
|
command: "npx",
|
|
1227
1504
|
args: ["-y", "chrome-devtools-mcp@latest", "--autoConnect"]
|
|
1228
1505
|
};
|
|
1229
|
-
|
|
1506
|
+
fs2.writeFileSync(configPath, JSON.stringify(configData, null, 2) + "\n", "utf8");
|
|
1230
1507
|
return {
|
|
1231
1508
|
status: isNew ? "created" : "updated",
|
|
1232
1509
|
path: configPath
|
|
1233
1510
|
};
|
|
1234
1511
|
}
|
|
1235
|
-
function
|
|
1512
|
+
function parseArgs(args = []) {
|
|
1513
|
+
const options = {};
|
|
1514
|
+
for (const arg of args) {
|
|
1515
|
+
if (arg === "--all" || arg === "--force") {
|
|
1516
|
+
options.all = true;
|
|
1517
|
+
} else if (arg === "--local" || arg === "-l") {
|
|
1518
|
+
options.local = true;
|
|
1519
|
+
} else if (arg.startsWith("--client=")) {
|
|
1520
|
+
options.client = arg.split("=")[1]?.toLowerCase().trim();
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return options;
|
|
1524
|
+
}
|
|
1525
|
+
function runInstaller(args = process.argv) {
|
|
1236
1526
|
console.log("\n\u{1F680} LinkeGringo MCP - Instalador Autom\xE1tico");
|
|
1237
1527
|
console.log("================================================");
|
|
1238
|
-
console.log("
|
|
1528
|
+
console.log("\u2139\uFE0F Modo 100% Aut\xF4nomo: N\xE3o requer o c\xF3digo-fonte do LinkeGringo na m\xE1quina.\n");
|
|
1529
|
+
const options = parseArgs(args);
|
|
1239
1530
|
const targets = getMcpConfigsForSystem();
|
|
1240
1531
|
const results = [];
|
|
1532
|
+
if (options.local) {
|
|
1533
|
+
const cwd = process.cwd();
|
|
1534
|
+
const localCursorDir = path2.join(cwd, ".cursor");
|
|
1535
|
+
const localPath = path2.join(localCursorDir, "mcp.json");
|
|
1536
|
+
try {
|
|
1537
|
+
const res = installMcpServerConfig(localPath);
|
|
1538
|
+
results.push({
|
|
1539
|
+
client: "Workspace Local (Cursor)",
|
|
1540
|
+
configPath: localPath,
|
|
1541
|
+
status: res.status
|
|
1542
|
+
});
|
|
1543
|
+
console.log(`\u2705 [Workspace Local]`);
|
|
1544
|
+
console.log(` Arquivo: ${localPath} (${res.status === "created" ? "Criado" : "Atualizado"})
|
|
1545
|
+
`);
|
|
1546
|
+
} catch (err) {
|
|
1547
|
+
results.push({
|
|
1548
|
+
client: "Workspace Local",
|
|
1549
|
+
configPath: localPath,
|
|
1550
|
+
status: "error",
|
|
1551
|
+
message: err.message
|
|
1552
|
+
});
|
|
1553
|
+
console.warn(`\u26A0\uFE0F [Workspace Local] Erro ao configurar: ${err.message}
|
|
1554
|
+
`);
|
|
1555
|
+
}
|
|
1556
|
+
return results;
|
|
1557
|
+
}
|
|
1558
|
+
const detectedTargets = targets.filter((t) => t.detected);
|
|
1559
|
+
const shouldInstallAll = options.all || detectedTargets.length === 0;
|
|
1241
1560
|
for (const target of targets) {
|
|
1561
|
+
if (options.client && !target.id.includes(options.client)) {
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
if (!shouldInstallAll && !target.detected) {
|
|
1565
|
+
results.push({
|
|
1566
|
+
client: target.client,
|
|
1567
|
+
configPath: target.configPath,
|
|
1568
|
+
status: "skipped",
|
|
1569
|
+
message: "Cliente n\xE3o detectado nesta m\xE1quina (use --all para for\xE7ar)"
|
|
1570
|
+
});
|
|
1571
|
+
console.log(`\u23ED\uFE0F [${target.client}] N\xE3o detectado nesta m\xE1quina (pulado. Use --all para criar)`);
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1242
1574
|
try {
|
|
1243
1575
|
const res = installMcpServerConfig(target.configPath);
|
|
1244
1576
|
results.push({
|
|
@@ -1246,7 +1578,8 @@ function runInstaller() {
|
|
|
1246
1578
|
configPath: target.configPath,
|
|
1247
1579
|
status: res.status
|
|
1248
1580
|
});
|
|
1249
|
-
|
|
1581
|
+
const tag = target.detected ? "(Detectado)" : "(Padr\xE3o)";
|
|
1582
|
+
console.log(`\u2705 [${target.client}] ${tag}`);
|
|
1250
1583
|
console.log(` Arquivo: ${target.configPath} (${res.status === "created" ? "Criado" : "Atualizado"})
|
|
1251
1584
|
`);
|
|
1252
1585
|
} catch (err) {
|
|
@@ -1267,16 +1600,16 @@ function runInstaller() {
|
|
|
1267
1600
|
console.log(" \u2022 Claude Code CLI: claude mcp add linkegringo npx -y @linkegringo/mcp");
|
|
1268
1601
|
console.log(' \u2022 Goose CLI: goose configure --add-extension "npx -y @linkegringo/mcp"');
|
|
1269
1602
|
console.log("================================================");
|
|
1270
|
-
console.log("\u{1F389} Instala\xE7\xE3o conclu\xEDda! Reinicie o
|
|
1603
|
+
console.log("\u{1F389} Instala\xE7\xE3o conclu\xEDda! Reinicie o seu cliente de IA para ativar.\n");
|
|
1271
1604
|
return results;
|
|
1272
1605
|
}
|
|
1273
1606
|
|
|
1274
1607
|
// src/index.ts
|
|
1275
|
-
import
|
|
1608
|
+
import fs3 from "fs";
|
|
1276
1609
|
import { fileURLToPath } from "url";
|
|
1277
1610
|
async function main() {
|
|
1278
1611
|
if (process.argv.includes("install") || process.argv.includes("setup") || process.argv.includes("--install")) {
|
|
1279
|
-
runInstaller();
|
|
1612
|
+
runInstaller(process.argv);
|
|
1280
1613
|
return;
|
|
1281
1614
|
}
|
|
1282
1615
|
const server = createLinkeGringoMcpServer();
|
|
@@ -1288,7 +1621,7 @@ function isDirectExecution() {
|
|
|
1288
1621
|
if (!process.argv[1]) return false;
|
|
1289
1622
|
try {
|
|
1290
1623
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
1291
|
-
const scriptPath =
|
|
1624
|
+
const scriptPath = fs3.existsSync(process.argv[1]) ? fs3.realpathSync(process.argv[1]) : process.argv[1];
|
|
1292
1625
|
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");
|
|
1293
1626
|
} catch {
|
|
1294
1627
|
return true;
|
|
@@ -1301,9 +1634,26 @@ if (isDirectExecution()) {
|
|
|
1301
1634
|
});
|
|
1302
1635
|
}
|
|
1303
1636
|
export {
|
|
1637
|
+
auditProfileInputSchema,
|
|
1304
1638
|
checkChromeCdp,
|
|
1639
|
+
checkChromeCdpInputSchema,
|
|
1640
|
+
convertToXyzBulletInputSchema,
|
|
1305
1641
|
createLinkeGringoMcpServer,
|
|
1642
|
+
detectSparseExperiences,
|
|
1643
|
+
findDevToolsActivePort,
|
|
1644
|
+
formatGoogleXyzBullet,
|
|
1645
|
+
generateHeadlineInputSchema,
|
|
1646
|
+
getExperienceBulletCount,
|
|
1306
1647
|
getMcpConfigsForSystem,
|
|
1648
|
+
handleAuditProfile,
|
|
1649
|
+
handleCheckChromeCdp,
|
|
1650
|
+
handleConvertToXyzBullet,
|
|
1651
|
+
handleGenerateHeadline,
|
|
1652
|
+
handleSimulateRecruiterSearch,
|
|
1307
1653
|
installMcpServerConfig,
|
|
1308
|
-
|
|
1654
|
+
isLinkeGringoUrl,
|
|
1655
|
+
parseArgs,
|
|
1656
|
+
probeViaWebSocket,
|
|
1657
|
+
runInstaller,
|
|
1658
|
+
simulateRecruiterSearchInputSchema
|
|
1309
1659
|
};
|