@linkegringo/mcp 1.0.6 → 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/dist/index.d.ts CHANGED
@@ -72,6 +72,24 @@ declare function completeRemoteOrLocalJob(jobId: string, result: any, bridgeUrl?
72
72
  */
73
73
  declare function failRemoteOrLocalJob(jobId: string, error: string, bridgeUrl?: string): Promise<BridgeJob>;
74
74
 
75
+ interface WatcherOptions {
76
+ bridgeUrl?: string;
77
+ once?: boolean;
78
+ onJob?: (job: {
79
+ id: string;
80
+ type: string;
81
+ createdAt: number;
82
+ }) => void;
83
+ onConnect?: (info: {
84
+ watcherId: string;
85
+ pendingCount: number;
86
+ }) => void;
87
+ onError?: (err: Error) => void;
88
+ }
89
+ declare function startBridgeWatcher(options?: WatcherOptions): {
90
+ close: () => void;
91
+ };
92
+
75
93
  declare class BridgeJobStore extends EventEmitter {
76
94
  private jobs;
77
95
  private pendingQueue;
@@ -84,6 +102,12 @@ declare class BridgeJobStore extends EventEmitter {
84
102
  waitForJob(id: string, timeoutMs?: number): Promise<BridgeJob>;
85
103
  getAllJobs(): BridgeJob[];
86
104
  getPendingCount(): number;
105
+ private activeWatchers;
106
+ registerWatcher(id: string): void;
107
+ unregisterWatcher(id: string): void;
108
+ getWatcherCount(): number;
109
+ hasActiveWatcher(): boolean;
110
+ cancelAllPending(reason?: string): number;
87
111
  clear(): void;
88
112
  }
89
113
  declare const bridgeJobStore: BridgeJobStore;
@@ -439,4 +463,4 @@ declare function handleSubmitJobResult(input: SubmitJobResultInput): Promise<{
439
463
  };
440
464
  }>;
441
465
 
442
- export { type AuditIssue, type AuditProfileInput, type BridgeJob, type BridgeServerOptions, type ConvertToXyzBulletInput, type GenerateHeadlineInput, type GetPendingJobInput, type InstallResult, type InstallerOptions, type JobStatus, type JobType, type McpTarget, type SimulateRecruiterSearchInput, type SparseExperience, type SubmitJobResultInput, auditProfileInputSchema, bridgeJobStore, completeRemoteOrLocalJob, convertToXyzBulletInputSchema, createBridgeHttpServer, createLinkeGringoMcpServer, detectSparseExperiences, failRemoteOrLocalJob, formatGoogleXyzBullet, generateHeadlineInputSchema, getExperienceBulletCount, getMcpConfigsForSystem, getPendingJobInputSchema, getRemoteOrLocalPendingJob, handleAuditProfile, handleConvertToXyzBullet, handleGenerateHeadline, handleGetPendingJob, handleSimulateRecruiterSearch, handleSubmitJobResult, installMcpServerConfig, parseArgs, runInstaller, simulateRecruiterSearchInputSchema, startBridgeServer, stopBridgeServer, submitJobResultInputSchema };
466
+ export { type AuditIssue, type AuditProfileInput, type BridgeJob, type BridgeServerOptions, type ConvertToXyzBulletInput, type GenerateHeadlineInput, type GetPendingJobInput, type InstallResult, type InstallerOptions, type JobStatus, type JobType, type McpTarget, type SimulateRecruiterSearchInput, type SparseExperience, type SubmitJobResultInput, type WatcherOptions, auditProfileInputSchema, bridgeJobStore, completeRemoteOrLocalJob, convertToXyzBulletInputSchema, createBridgeHttpServer, createLinkeGringoMcpServer, detectSparseExperiences, failRemoteOrLocalJob, formatGoogleXyzBullet, generateHeadlineInputSchema, getExperienceBulletCount, getMcpConfigsForSystem, getPendingJobInputSchema, getRemoteOrLocalPendingJob, handleAuditProfile, handleConvertToXyzBullet, handleGenerateHeadline, handleGetPendingJob, handleSimulateRecruiterSearch, handleSubmitJobResult, installMcpServerConfig, parseArgs, runInstaller, simulateRecruiterSearchInputSchema, startBridgeServer, startBridgeWatcher, stopBridgeServer, submitJobResultInputSchema };
package/dist/index.js CHANGED
@@ -117,9 +117,43 @@ var BridgeJobStore = class extends EventEmitter {
117
117
  getPendingCount() {
118
118
  return this.jobs.size > 0 ? Array.from(this.jobs.values()).filter((j) => j.status === "pending" || j.status === "processing").length : 0;
119
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);
148
+ }
149
+ }
150
+ this.emit("queue:cleared", count);
151
+ return count;
152
+ }
120
153
  clear() {
121
154
  this.jobs.clear();
122
155
  this.pendingQueue = [];
156
+ this.activeWatchers.clear();
123
157
  this.removeAllListeners();
124
158
  }
125
159
  };
@@ -1684,11 +1718,81 @@ function createBridgeHttpServer(options = {}) {
1684
1718
  sendJson(res, 200, {
1685
1719
  status: "ok",
1686
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(),
1687
1736
  pendingCount: bridgeJobStore.getPendingCount(),
1737
+ activeJobId: pendingJob ? pendingJob.id : null,
1688
1738
  totalJobs: bridgeJobStore.getAllJobs().length
1689
1739
  });
1690
1740
  return;
1691
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
+ }
1692
1796
  if (req.method === "POST" && pathname === "/api/jobs") {
1693
1797
  const body = await parseBody(req);
1694
1798
  if (!body.type) {
@@ -1700,6 +1804,10 @@ function createBridgeHttpServer(options = {}) {
1700
1804
  sendJson(res, 201, { ok: true, job });
1701
1805
  return;
1702
1806
  }
1807
+ if (req.method === "GET" && pathname === "/api/jobs") {
1808
+ sendJson(res, 200, { ok: true, jobs: bridgeJobStore.getAllJobs() });
1809
+ return;
1810
+ }
1703
1811
  if (req.method === "GET" && pathname === "/api/jobs/pending") {
1704
1812
  const id = parsedUrl.searchParams.get("id") || void 0;
1705
1813
  const job = bridgeJobStore.getPendingJob(id);
@@ -1810,6 +1918,126 @@ async function stopBridgeServer() {
1810
1918
  });
1811
1919
  }
1812
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
+
1813
2041
  // src/index.ts
1814
2042
  import fs2 from "fs";
1815
2043
  import { fileURLToPath } from "url";
@@ -1818,6 +2046,12 @@ async function main() {
1818
2046
  runInstaller(process.argv);
1819
2047
  return;
1820
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
+ }
1821
2055
  try {
1822
2056
  await startBridgeServer();
1823
2057
  } catch (err) {
@@ -1870,6 +2104,7 @@ export {
1870
2104
  runInstaller,
1871
2105
  simulateRecruiterSearchInputSchema,
1872
2106
  startBridgeServer,
2107
+ startBridgeWatcher,
1873
2108
  stopBridgeServer,
1874
2109
  submitJobResultInputSchema
1875
2110
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkegringo/mcp",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Servidor MCP oficial do LinkeGringo para auditoria e otimização de perfis para o mercado internacional",
5
5
  "type": "module",
6
6
  "bin": {