@triflux/remote 10.48.1 → 10.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,219 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
-
3
- const VALID_MCP_ROUTES = new Set(["codex", "antigravity", "gemini", "claude"]);
4
-
5
- function logRuleError(message, error) {
6
- if (error) {
7
- console.error(`[triflux-keyword-rules] ${message}: ${error.message}`);
8
- return;
9
- }
10
- console.error(`[triflux-keyword-rules] ${message}`);
11
- }
12
-
13
- function normalizePattern(pattern) {
14
- if (!pattern || typeof pattern.source !== "string") return null;
15
- if (typeof pattern.flags !== "string") return null;
16
- return { source: pattern.source, flags: pattern.flags };
17
- }
18
-
19
- function normalizeState(state) {
20
- if (state == null) return null;
21
- if (typeof state !== "object") return null;
22
- if (typeof state.activate !== "boolean") return null;
23
- if (typeof state.name !== "string" || !state.name.trim()) return null;
24
- return { activate: state.activate, name: state.name.trim() };
25
- }
26
-
27
- function normalizeRule(rule) {
28
- if (!rule || typeof rule !== "object") return null;
29
- if (rule.disabled === true) return null;
30
- if (typeof rule.id !== "string" || !rule.id.trim()) return null;
31
- if (!Array.isArray(rule.patterns) || rule.patterns.length === 0) return null;
32
- if (typeof rule.priority !== "number" || !Number.isFinite(rule.priority))
33
- return null;
34
-
35
- const patterns = rule.patterns.map(normalizePattern).filter(Boolean);
36
- if (patterns.length === 0) return null;
37
-
38
- const skill =
39
- typeof rule.skill === "string" && rule.skill.trim()
40
- ? rule.skill.trim()
41
- : null;
42
- const action =
43
- typeof rule.action === "string" && rule.action.trim()
44
- ? rule.action.trim()
45
- : null;
46
- const mcpRoute =
47
- typeof rule.mcp_route === "string" && VALID_MCP_ROUTES.has(rule.mcp_route)
48
- ? rule.mcp_route
49
- : null;
50
-
51
- if (!skill && !mcpRoute && !action) return null;
52
-
53
- const hint =
54
- typeof rule.hint === "string" && rule.hint.trim() ? rule.hint.trim() : null;
55
-
56
- const supersedes = Array.isArray(rule.supersedes)
57
- ? rule.supersedes
58
- .filter((id) => typeof id === "string" && id.trim())
59
- .map((id) => id.trim())
60
- : [];
61
-
62
- const state = normalizeState(rule.state);
63
- if (rule.state != null && state == null) return null;
64
-
65
- // repo_scope: path segment 화이트리스트 (빈 배열 = 전역). explicit: 종결 라우팅 마커.
66
- const repoScope = Array.isArray(rule.repo_scope)
67
- ? rule.repo_scope
68
- .filter((s) => typeof s === "string" && s.trim())
69
- .map((s) => s.trim())
70
- : [];
71
-
72
- // platform: process.platform 화이트리스트 (빈 배열 = 전 플랫폼). Windows 전용
73
- // 스킬(tfx-wt 등)이 macOS/Linux 프롬프트에 오탐하는 것을 막는다. 배열이 아닌
74
- // 값은 규칙 자체를 무효로 본다.
75
- if (rule.platform != null && !Array.isArray(rule.platform)) return null;
76
- const platform = Array.isArray(rule.platform)
77
- ? rule.platform
78
- .filter((s) => typeof s === "string" && s.trim())
79
- .map((s) => s.trim())
80
- : [];
81
-
82
- return {
83
- id: rule.id.trim(),
84
- patterns,
85
- skill,
86
- action,
87
- hint,
88
- priority: rule.priority,
89
- supersedes,
90
- exclusive: rule.exclusive === true,
91
- state,
92
- mcp_route: mcpRoute,
93
- repo_scope: repoScope,
94
- platform,
95
- explicit: rule.explicit === true,
96
- };
97
- }
98
-
99
- // 외부 JSON 규칙 로드 + 스키마 검증
100
- export function loadRules(rulesPath) {
101
- try {
102
- const raw = readFileSync(rulesPath, "utf8");
103
- const parsed = JSON.parse(raw);
104
- if (!parsed || !Array.isArray(parsed.rules)) {
105
- logRuleError(`규칙 형식이 올바르지 않습니다: ${rulesPath}`);
106
- return [];
107
- }
108
-
109
- const normalized = parsed.rules.map(normalizeRule).filter(Boolean);
110
- return normalized;
111
- } catch (error) {
112
- logRuleError(`규칙 파일을 읽을 수 없습니다: ${rulesPath}`, error);
113
- return [];
114
- }
115
- }
116
-
117
- // pattern.source / flags를 RegExp로 컴파일
118
- export function compileRules(rules) {
119
- return rules
120
- .map((rule) => {
121
- try {
122
- return {
123
- ...rule,
124
- compiledPatterns: rule.patterns.map(
125
- (p) => new RegExp(p.source, p.flags),
126
- ),
127
- };
128
- } catch (error) {
129
- logRuleError(`정규식 컴파일 실패: ${rule.id}`, error);
130
- return null;
131
- }
132
- })
133
- .filter(Boolean);
134
- }
135
-
136
- // 입력 텍스트에서 매칭된 규칙 목록 반환
137
- export function matchRules(compiledRules, cleanText) {
138
- if (
139
- !Array.isArray(compiledRules) ||
140
- typeof cleanText !== "string" ||
141
- !cleanText
142
- ) {
143
- return [];
144
- }
145
-
146
- const matches = [];
147
-
148
- for (const rule of compiledRules) {
149
- if (
150
- !Array.isArray(rule.compiledPatterns) ||
151
- rule.compiledPatterns.length === 0
152
- ) {
153
- continue;
154
- }
155
-
156
- const matched = rule.compiledPatterns.some((pattern) => {
157
- pattern.lastIndex = 0;
158
- return pattern.test(cleanText);
159
- });
160
-
161
- if (!matched) continue;
162
-
163
- matches.push({
164
- id: rule.id,
165
- skill: rule.skill,
166
- action: rule.action,
167
- hint: rule.hint || null,
168
- priority: rule.priority,
169
- supersedes: rule.supersedes || [],
170
- exclusive: rule.exclusive === true,
171
- state: rule.state || null,
172
- mcp_route: rule.mcp_route || null,
173
- repo_scope: rule.repo_scope || [],
174
- platform: rule.platform || [],
175
- explicit: rule.explicit === true,
176
- });
177
- }
178
-
179
- return matches;
180
- }
181
-
182
- // priority 정렬 + supersedes + exclusive 처리
183
- export function resolveConflicts(matches) {
184
- try {
185
- if (!Array.isArray(matches) || matches.length === 0) return [];
186
-
187
- const sorted = [...matches].sort((a, b) => {
188
- if (a.priority !== b.priority) return a.priority - b.priority;
189
- return String(a.id).localeCompare(String(b.id));
190
- });
191
-
192
- const deduped = [];
193
- const seen = new Set();
194
- for (const match of sorted) {
195
- if (seen.has(match.id)) continue;
196
- deduped.push(match);
197
- seen.add(match.id);
198
- }
199
-
200
- const superseded = new Set();
201
- const resolved = [];
202
-
203
- for (const match of deduped) {
204
- if (superseded.has(match.id)) continue;
205
- resolved.push(match);
206
- for (const targetId of match.supersedes || []) {
207
- superseded.add(targetId);
208
- }
209
- }
210
-
211
- const exclusiveMatch = resolved.find((match) => match.exclusive === true);
212
- if (exclusiveMatch) return [exclusiveMatch];
213
-
214
- return resolved;
215
- } catch (error) {
216
- logRuleError("규칙 충돌 해결 실패", error);
217
- return [];
218
- }
219
- }
@@ -1,224 +0,0 @@
1
- // scripts/lib/mcp-gateway-health-check.mjs
2
- //
3
- // `~/.local/state/triflux/mcp-gateway.out.log` 파싱 헬퍼.
4
- // install-mcp-gateway-startup.mjs 가 LaunchAgent/systemd 로 띄운 gateway daemon
5
- // (mcp-gateway-start.mjs) 의 stdout 로그에서 다음 신호를 추출한다.
6
- //
7
- // [WARN] <server> skipped — missing env: <KEY> → missing-env finding
8
- // [START] <server> on :<port> → started
9
- // [SKIP] <server> already running on :<port> → started (preExisting)
10
- // [SKIP] <server> — manifest에서 비활성 → manifest-disabled skip
11
- //
12
- // 호출자는 `findings` 만 보고 doctor 진단을 출력할 수 있다.
13
- // log 파일이 없으면 `available: false` 로 반환하므로, gateway 미설치 환경에서는
14
- // 침묵한다 (false-positive 방지).
15
-
16
- import { readFileSync, statSync } from "node:fs";
17
- import { createConnection } from "node:net";
18
- import { homedir } from "node:os";
19
- import { join } from "node:path";
20
-
21
- const DEFAULT_LOG_PATH = join(
22
- homedir(),
23
- ".local",
24
- "state",
25
- "triflux",
26
- "mcp-gateway.out.log",
27
- );
28
-
29
- // producer (scripts/mcp-gateway-start.mjs:146) 가 missing env 를 `missing.join(", ")`
30
- // 로 출력하므로 detail 은 공백 포함 다중 키 (예: "JIRA_API_TOKEN, JIRA_EMAIL, JIRA_INSTANCE_URL")
31
- // 일 수 있다. line 끝까지 캡처한다.
32
- const MISSING_ENV_RE = /^\[WARN\]\s+(\S+)\s+skipped\s+—\s+missing env:\s+(.+)$/;
33
- const START_RE = /^\[START\]\s+(\S+)\s+on\s+:(\d+)$/;
34
- const SKIP_RUNNING_RE = /^\[SKIP\]\s+(\S+)\s+already running on\s+:(\d+)$/;
35
- const SKIP_DISABLED_RE = /^\[SKIP\]\s+(\S+)\s+—\s+manifest에서 비활성$/;
36
-
37
- function probePortListening(
38
- port,
39
- { host = "127.0.0.1", timeoutMs = 800 } = {},
40
- ) {
41
- return new Promise((resolve) => {
42
- const socket = createConnection({ host, port });
43
- let settled = false;
44
-
45
- function finish(value) {
46
- if (settled) return;
47
- settled = true;
48
- socket.destroy();
49
- resolve(value);
50
- }
51
-
52
- socket.once("connect", () => finish(true));
53
- socket.once("error", () => finish(false));
54
- socket.setTimeout(timeoutMs, () => finish(false));
55
- });
56
- }
57
-
58
- export function checkMcpGatewayHealth({
59
- fs = { readFileSync, statSync },
60
- logPath = DEFAULT_LOG_PATH,
61
- } = {}) {
62
- try {
63
- fs.statSync(logPath);
64
- } catch {
65
- return {
66
- available: false,
67
- logPath,
68
- findings: [],
69
- started: [],
70
- skipped: [],
71
- };
72
- }
73
-
74
- let text;
75
- try {
76
- text = fs.readFileSync(logPath, "utf8");
77
- } catch (error) {
78
- return {
79
- available: true,
80
- logPath,
81
- findings: [
82
- {
83
- server: null,
84
- reason: "log-read-error",
85
- detail: error?.message || String(error),
86
- },
87
- ],
88
- started: [],
89
- skipped: [],
90
- };
91
- }
92
-
93
- // log 는 append-only 라 옛 [WARN] 라인 뒤에 새 [START] 라인이 올 수 있다.
94
- // server 별 마지막 이벤트가 현재 상태이므로 그것만 finding 으로 환원한다.
95
- // (예: BRAVE_API_KEY missing 으로 한 번 skip → secrets 추가 후 restart 로 start)
96
- const lastEvent = new Map();
97
-
98
- for (const rawLine of text.split("\n")) {
99
- const line = rawLine.trim();
100
- if (!line) continue;
101
-
102
- let m;
103
- if ((m = MISSING_ENV_RE.exec(line))) {
104
- lastEvent.set(m[1], {
105
- type: "missing-env",
106
- detail: m[2],
107
- });
108
- continue;
109
- }
110
- if ((m = START_RE.exec(line))) {
111
- lastEvent.set(m[1], {
112
- type: "started",
113
- port: Number(m[2]),
114
- preExisting: false,
115
- });
116
- continue;
117
- }
118
- if ((m = SKIP_RUNNING_RE.exec(line))) {
119
- lastEvent.set(m[1], {
120
- type: "started",
121
- port: Number(m[2]),
122
- preExisting: true,
123
- });
124
- continue;
125
- }
126
- if ((m = SKIP_DISABLED_RE.exec(line))) {
127
- lastEvent.set(m[1], {
128
- type: "manifest-disabled",
129
- });
130
- }
131
- }
132
-
133
- const findings = [];
134
- const started = [];
135
- const skipped = [];
136
- for (const [server, ev] of lastEvent) {
137
- if (ev.type === "missing-env") {
138
- findings.push({ server, reason: "missing-env", detail: ev.detail });
139
- } else if (ev.type === "started") {
140
- started.push({ server, port: ev.port, preExisting: ev.preExisting });
141
- } else if (ev.type === "manifest-disabled") {
142
- skipped.push({ server, reason: "manifest-disabled" });
143
- }
144
- }
145
-
146
- return {
147
- available: true,
148
- logPath,
149
- findings,
150
- started,
151
- skipped,
152
- };
153
- }
154
-
155
- export async function checkMcpGatewayHealthLive({
156
- probePort = probePortListening,
157
- ...options
158
- } = {}) {
159
- const result = checkMcpGatewayHealth(options);
160
- if (!result.available || result.started.length === 0) {
161
- return {
162
- ...result,
163
- live: [],
164
- };
165
- }
166
-
167
- const live = [];
168
- const findings = [...result.findings];
169
- for (const entry of result.started) {
170
- const listening = await probePort(entry.port);
171
- live.push({ ...entry, listening });
172
- if (!listening) {
173
- findings.push({
174
- server: entry.server,
175
- reason: "port-down",
176
- detail: `expected listener on :${entry.port}`,
177
- port: entry.port,
178
- });
179
- }
180
- }
181
-
182
- return {
183
- ...result,
184
- findings,
185
- live,
186
- };
187
- }
188
-
189
- export function summarizeMcpGatewayHealth(result) {
190
- if (!result?.available) {
191
- return {
192
- level: "skip",
193
- message: "mcp-gateway log 없음 (gateway 미설치 또는 미실행)",
194
- };
195
- }
196
- if (result.findings.length === 0) {
197
- return {
198
- level: "ok",
199
- message: `mcp-gateway healthy (${result.started.length}개 server up)`,
200
- };
201
- }
202
- const missingEnv = result.findings.filter((f) => f.reason === "missing-env");
203
- if (missingEnv.length > 0) {
204
- const list = missingEnv.map((f) => `${f.server} (${f.detail})`).join(", ");
205
- return {
206
- level: "warn",
207
- message: `${missingEnv.length}개 MCP 서버가 missing env 로 skip: ${list}`,
208
- fix: "secrets 를 ~/.config/triflux/secrets.env 에 추가하고 `launchctl kickstart -k gui/$(id -u)/com.tellang.mcp-gateway` (macOS) 또는 `systemctl --user restart mcp-gateway` (linux)",
209
- };
210
- }
211
- const portDown = result.findings.filter((f) => f.reason === "port-down");
212
- if (portDown.length > 0) {
213
- const list = portDown.map((f) => `${f.server} (:${f.port})`).join(", ");
214
- return {
215
- level: "warn",
216
- message: `${portDown.length}개 MCP gateway port down: ${list}`,
217
- fix: "gateway 를 재기동하세요: `node scripts/mcp-gateway-start.mjs` 또는 macOS `launchctl kickstart -k gui/$(id -u)/com.tellang.mcp-gateway`",
218
- };
219
- }
220
- return {
221
- level: "warn",
222
- message: `${result.findings.length}개 gateway 이슈`,
223
- };
224
- }
@@ -1,70 +0,0 @@
1
- const SERVER_DEFINITIONS = Object.freeze([
2
- {
3
- name: "context7",
4
- port: 8100,
5
- stdioCmd: "npx -y @upstash/context7-mcp@latest",
6
- envVars: [],
7
- },
8
- {
9
- name: "brave-search",
10
- port: 8101,
11
- stdioCmd: "npx -y @brave/brave-search-mcp-server",
12
- envVars: ["BRAVE_API_KEY"],
13
- },
14
- {
15
- name: "exa",
16
- port: 8102,
17
- stdioCmd: "npx -y exa-mcp-server",
18
- envVars: ["EXA_API_KEY"],
19
- },
20
- {
21
- name: "tavily",
22
- port: 8103,
23
- stdioCmd: "npx -y tavily-mcp@latest",
24
- envVars: ["TAVILY_API_KEY"],
25
- },
26
- {
27
- name: "jira",
28
- port: 8104,
29
- stdioCmd: "npx -y mcp-jira-cloud@latest",
30
- envVars: ["JIRA_API_TOKEN", "JIRA_EMAIL", "JIRA_INSTANCE_URL"],
31
- },
32
- {
33
- name: "serena",
34
- port: 8105,
35
- stdioCmd:
36
- "uvx --from git+https://github.com/oraios/serena serena start-mcp-server",
37
- envVars: [],
38
- },
39
- {
40
- name: "notion",
41
- port: 8106,
42
- stdioCmd: "npx -y @notionhq/notion-mcp-server",
43
- envVars: ["NOTION_TOKEN"],
44
- },
45
- {
46
- name: "notion-guest",
47
- port: 8107,
48
- stdioCmd: "npx -y @notionhq/notion-mcp-server",
49
- envVars: ["NOTION_TOKEN"],
50
- },
51
- ]);
52
-
53
- export const SERVERS = SERVER_DEFINITIONS.map((server) => ({
54
- name: server.name,
55
- port: server.port,
56
- cmd: server.stdioCmd,
57
- envVars: [...server.envVars],
58
- }));
59
-
60
- export const GATEWAY_SERVERS = SERVER_DEFINITIONS.map((server) => ({
61
- name: server.name,
62
- port: server.port,
63
- stdioCmd: server.stdioCmd,
64
- }));
65
-
66
- export function serversWithSatisfiedEnv(env = process.env) {
67
- return SERVERS.filter((server) =>
68
- server.envVars.every((envName) => Boolean(env[envName])),
69
- );
70
- }
@@ -1,35 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
-
5
- export async function checkWrapperSourcing({
6
- wrapperPath = path.join(
7
- os.homedir(),
8
- ".local",
9
- "bin",
10
- "mcp-gateway-wrapper.sh",
11
- ),
12
- marker = "secrets.env",
13
- } = {}) {
14
- let text;
15
- try {
16
- text = await readFile(wrapperPath, "utf8");
17
- } catch (error) {
18
- const result = { status: "missing", wrapperPath };
19
- if (error?.code !== "ENOENT") {
20
- result.message = `wrapper read failed: ${error?.message || String(error)}`;
21
- }
22
- return result;
23
- }
24
-
25
- if (text.includes(marker)) {
26
- return { status: "ok", wrapperPath };
27
- }
28
-
29
- return {
30
- status: "warn",
31
- wrapperPath,
32
- message: "wrapper exists but does not source secrets.env",
33
- suggestedFix: "Re-run: node scripts/install-mcp-gateway-startup.mjs",
34
- };
35
- }