@triflux/remote 10.50.2 → 10.52.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.
Files changed (134) hide show
  1. package/cto/brief.mjs +0 -20
  2. package/cto/collect.mjs +1 -200
  3. package/cto/current.schema.json +2 -23
  4. package/cto/hygiene.mjs +2 -61
  5. package/cto/lake-root.mjs +0 -56
  6. package/cto/status.mjs +5 -187
  7. package/hub/index.mjs +0 -17
  8. package/hub/team/agent-map.json +0 -1
  9. package/hub/team/agents-row-attach.py +120 -0
  10. package/hub/team/agents-row.mjs +200 -0
  11. package/hub/team/ansi.mjs +1 -35
  12. package/hub/team/backend.mjs +9 -145
  13. package/hub/team/claude-daemon-control.mjs +1 -2
  14. package/hub/team/claude-session-projection.mjs +5 -73
  15. package/hub/team/cli/commands/attach.mjs +1 -17
  16. package/hub/team/cli/commands/control.mjs +12 -38
  17. package/hub/team/cli/commands/debug.mjs +2 -39
  18. package/hub/team/cli/commands/focus.mjs +5 -31
  19. package/hub/team/cli/commands/interrupt.mjs +6 -25
  20. package/hub/team/cli/commands/kill.mjs +1 -30
  21. package/hub/team/cli/commands/list.mjs +0 -16
  22. package/hub/team/cli/commands/send.mjs +6 -26
  23. package/hub/team/cli/commands/start/index.mjs +42 -133
  24. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  25. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  26. package/hub/team/cli/commands/start/start-mux.mjs +2 -7
  27. package/hub/team/cli/commands/status.mjs +2 -57
  28. package/hub/team/cli/commands/stop.mjs +14 -19
  29. package/hub/team/cli/help.mjs +4 -5
  30. package/hub/team/cli/manifest.mjs +5 -1
  31. package/hub/team/cli/render.mjs +0 -12
  32. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  33. package/hub/team/cli/services/state-store.mjs +5 -20
  34. package/hub/team/handoff.mjs +3 -34
  35. package/hub/team/headless.mjs +108 -1296
  36. package/hub/team/orchestrator.mjs +4 -30
  37. package/hub/team/pane.mjs +6 -28
  38. package/hub/team/psmux.mjs +3 -34
  39. package/hub/team/remote-session.mjs +1 -2
  40. package/hub/team/session-context.mjs +1 -1
  41. package/hub/team/session.mjs +7 -91
  42. package/hub/team/tui-core.mjs +1 -1
  43. package/hub/team/tui-viewer.mjs +11 -58
  44. package/hub/team/tui.mjs +8 -306
  45. package/hub/team/uds-orchestrator.mjs +2 -580
  46. package/hub/team/wt-manager.mjs +2 -2
  47. package/hub/workers/factory.mjs +6 -84
  48. package/hub/workers/interface.mjs +1 -7
  49. package/hub/workers/lib/jsonrpc-core.mjs +11 -0
  50. package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -8
  51. package/package.json +3 -8
  52. package/scripts/lib/agent-route-policy.mjs +143 -62
  53. package/scripts/lib/cli-claude.mjs +0 -14
  54. package/scripts/lib/env-probe.mjs +2 -131
  55. package/scripts/lib/gemini-profiles.mjs +2 -69
  56. package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
  57. package/scripts/lib/machine-profile.mjs +3 -17
  58. package/scripts/lib/mcp-filter.mjs +109 -53
  59. package/scripts/lib/mcp-guard-engine.mjs +40 -98
  60. package/scripts/lib/mcp-health.mjs +2 -2
  61. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  62. package/scripts/lib/psmux-info.mjs +11 -59
  63. package/scripts/lib/skill-template.mjs +0 -245
  64. package/hub/hub-lifecycle.mjs +0 -248
  65. package/hub/pipe.mjs +0 -825
  66. package/hub/promote-penalties.mjs +0 -138
  67. package/hub/public/dashboard.html +0 -416
  68. package/hub/schema.sql +0 -209
  69. package/hub/server.mjs +0 -3048
  70. package/hub/store-adapter.mjs +0 -182
  71. package/hub/store.mjs +0 -1736
  72. package/hub/team/build-worker-prompt.mjs +0 -92
  73. package/hub/team/check-mcp-hub.mjs +0 -49
  74. package/hub/team/claude-native-bridge.mjs +0 -987
  75. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  76. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  77. package/hub/team/cli/services/hub-client.mjs +0 -264
  78. package/hub/team/cli/services/native-control.mjs +0 -145
  79. package/hub/team/codex-review.mjs +0 -520
  80. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  81. package/hub/team/conductor-registry.mjs +0 -121
  82. package/hub/team/conductor.mjs +0 -1529
  83. package/hub/team/consensus-meta.mjs +0 -176
  84. package/hub/team/cto-auto-collect.mjs +0 -115
  85. package/hub/team/cto-pull.mjs +0 -31
  86. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  87. package/hub/team/dashboard.mjs +0 -322
  88. package/hub/team/event-log.mjs +0 -84
  89. package/hub/team/execution-mode.mjs +0 -324
  90. package/hub/team/extract-completion-payload.mjs +0 -110
  91. package/hub/team/headless-bridge-session.mjs +0 -17
  92. package/hub/team/health-probe.mjs +0 -435
  93. package/hub/team/interactive-native-launcher.mjs +0 -119
  94. package/hub/team/interactive-native-worker.mjs +0 -80
  95. package/hub/team/interactive-tui-transport.mjs +0 -479
  96. package/hub/team/launcher-template.mjs +0 -144
  97. package/hub/team/lead-control.mjs +0 -191
  98. package/hub/team/mcp-selector.mjs +0 -145
  99. package/hub/team/native-supervisor.mjs +0 -460
  100. package/hub/team/native.mjs +0 -727
  101. package/hub/team/recovery-store.mjs +0 -149
  102. package/hub/team/remote-probe.mjs +0 -292
  103. package/hub/team/remote-watcher.mjs +0 -506
  104. package/hub/team/runtime-strategy.mjs +0 -200
  105. package/hub/team/schemas/worker-completion.json +0 -39
  106. package/hub/team/sentinel-capture.mjs +0 -127
  107. package/hub/team/session-sync.mjs +0 -316
  108. package/hub/team/swarm-cli.mjs +0 -390
  109. package/hub/team/swarm-hypervisor.mjs +0 -2206
  110. package/hub/team/swarm-intent.mjs +0 -216
  111. package/hub/team/swarm-locks.mjs +0 -335
  112. package/hub/team/swarm-planner.mjs +0 -371
  113. package/hub/team/swarm-preflight.mjs +0 -366
  114. package/hub/team/swarm-reconciler.mjs +0 -242
  115. package/hub/team/synapse-cli.mjs +0 -458
  116. package/hub/team/synapse-http.mjs +0 -311
  117. package/hub/team/synapse-registry.mjs +0 -618
  118. package/hub/team/tui-remote-adapter.mjs +0 -377
  119. package/hub/team/tui-synapse.mjs +0 -223
  120. package/hub/team/worker-completion-validator.mjs +0 -79
  121. package/hub/team/worker-sandbox.mjs +0 -125
  122. package/hub/team/worktree-lifecycle.mjs +0 -639
  123. package/hub/team/wt-templates.json +0 -43
  124. package/hub/tools.mjs +0 -900
  125. package/hub/workers/antigravity-route-worker.mjs +0 -179
  126. package/hub/workers/codex-app-server-worker.mjs +0 -1067
  127. package/hub/workers/delegator-mcp.mjs +0 -1098
  128. package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
  129. package/scripts/lib/claudemd-scanner.mjs +0 -223
  130. package/scripts/lib/context.mjs +0 -67
  131. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  132. package/scripts/lib/handoff.mjs +0 -187
  133. package/scripts/lib/logger.mjs +0 -122
  134. package/scripts/lib/stealth-fetch.mjs +0 -176
@@ -1,187 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { basename, resolve } from "node:path";
4
- import { findAllClaudeMdPaths } from "./claudemd-scanner.mjs";
5
-
6
- function runCommand(command, cwd, executor = execSync) {
7
- try {
8
- const output = executor(command, {
9
- cwd,
10
- encoding: "utf8",
11
- stdio: ["ignore", "pipe", "ignore"],
12
- windowsHide: true,
13
- });
14
- return String(output || "").trimEnd();
15
- } catch {
16
- return "";
17
- }
18
- }
19
-
20
- function parseStatusLines(statusText) {
21
- const lines = String(statusText || "")
22
- .split(/\r?\n/u)
23
- .filter(Boolean);
24
- const changedFiles = [];
25
- const status = [];
26
-
27
- for (const line of lines) {
28
- const normalized = line.trimEnd();
29
- if (!normalized) continue;
30
-
31
- const rawStatus = normalized.slice(0, 2).trim() || "??";
32
- const rawPath = normalized.slice(3).trim();
33
- const filePath = rawPath.includes(" -> ")
34
- ? rawPath.split(" -> ").at(-1)?.trim()
35
- : rawPath;
36
- if (!filePath) continue;
37
-
38
- changedFiles.push(filePath);
39
- status.push({ path: filePath, status: rawStatus });
40
- }
41
-
42
- return {
43
- changedFiles: Array.from(new Set(changedFiles)),
44
- status,
45
- };
46
- }
47
-
48
- function normalizeDecisionLine(line) {
49
- return String(line || "")
50
- .trim()
51
- .replace(/^[-*]\s+/u, "")
52
- .trim();
53
- }
54
-
55
- function parseDecisionFile(decisionFile) {
56
- if (!decisionFile) return [];
57
- const absolute = resolve(decisionFile);
58
- if (!existsSync(absolute)) return [];
59
-
60
- const raw = readFileSync(absolute, "utf8");
61
- return raw
62
- .split(/\r?\n/u)
63
- .map(normalizeDecisionLine)
64
- .filter((line) => line.length > 0);
65
- }
66
-
67
- function normalizeDecisions(decisions, decisionFile) {
68
- const merged = [
69
- ...(Array.isArray(decisions) ? decisions : []),
70
- ...parseDecisionFile(decisionFile),
71
- ]
72
- .map(normalizeDecisionLine)
73
- .filter(Boolean);
74
-
75
- return Array.from(new Set(merged));
76
- }
77
-
78
- function formatAheadBehind(value) {
79
- if (!value) return null;
80
- const [behindRaw, aheadRaw] = value.split(/\s+/u);
81
- const behind = Number.parseInt(behindRaw, 10);
82
- const ahead = Number.parseInt(aheadRaw, 10);
83
- if (!Number.isFinite(behind) || !Number.isFinite(ahead)) return null;
84
- return { ahead, behind };
85
- }
86
-
87
- export function collectHandoffContext(options = {}) {
88
- const cwd = resolve(options.cwd || process.cwd());
89
- const executor =
90
- typeof options.commandRunner === "function"
91
- ? options.commandRunner
92
- : execSync;
93
- const target = options.target === "local" ? "local" : "remote";
94
- const decisions = normalizeDecisions(options.decisions, options.decisionFile);
95
- const generatedAt = options.generatedAt || new Date().toISOString();
96
- const claudeMdPaths = Array.isArray(options.claudeMdPaths)
97
- ? options.claudeMdPaths
98
- : findAllClaudeMdPaths({
99
- cwd,
100
- homeDir: options.homeDir,
101
- });
102
-
103
- const gitRoot = runCommand("git rev-parse --show-toplevel", cwd, executor);
104
- const branch =
105
- runCommand("git rev-parse --abbrev-ref HEAD", cwd, executor) || null;
106
- const shortStatus = runCommand("git status --short", cwd, executor);
107
- const diffStat = runCommand("git diff --stat --no-color", cwd, executor);
108
- const upstreamRaw = runCommand(
109
- "git rev-list --left-right --count @{upstream}...HEAD",
110
- cwd,
111
- executor,
112
- );
113
- const parsedStatus = parseStatusLines(shortStatus);
114
-
115
- return {
116
- generatedAt,
117
- target,
118
- cwd,
119
- gitRoot: gitRoot || null,
120
- repository: gitRoot ? basename(gitRoot) : basename(cwd),
121
- branch,
122
- upstream: formatAheadBehind(upstreamRaw),
123
- changedFiles: parsedStatus.changedFiles,
124
- fileStatus: parsedStatus.status,
125
- diffStat,
126
- decisions,
127
- claudeMdPaths,
128
- };
129
- }
130
-
131
- export function buildHandoffPrompt(context) {
132
- const safeContext = context || collectHandoffContext();
133
- const branch = safeContext.branch || "unknown";
134
- const upstream = safeContext.upstream
135
- ? `ahead ${safeContext.upstream.ahead}, behind ${safeContext.upstream.behind}`
136
- : "unknown";
137
- const changedFiles =
138
- safeContext.changedFiles.length > 0
139
- ? safeContext.changedFiles.map((file) => `- ${file}`).join("\n")
140
- : "- 변경 파일 없음";
141
- const decisions =
142
- safeContext.decisions.length > 0
143
- ? safeContext.decisions.map((decision) => `- ${decision}`).join("\n")
144
- : "- 명시된 결정사항 없음";
145
- const claudeMdList =
146
- Array.isArray(safeContext.claudeMdPaths) &&
147
- safeContext.claudeMdPaths.length > 0
148
- ? safeContext.claudeMdPaths.map((path) => `- ${path}`).join("\n")
149
- : "- 자동 탐지된 CLAUDE.md 없음";
150
- const diffStat = safeContext.diffStat || "(diff stat 없음)";
151
-
152
- return [
153
- "## TFX Remote Handoff",
154
- `- generated_at: ${safeContext.generatedAt}`,
155
- `- target: ${safeContext.target}`,
156
- `- repository: ${safeContext.repository}`,
157
- `- branch: ${branch} (${upstream})`,
158
- `- cwd: ${safeContext.cwd}`,
159
- "",
160
- "### 변경 파일",
161
- changedFiles,
162
- "",
163
- "### 변경 요약 (git diff --stat)",
164
- "```",
165
- diffStat,
166
- "```",
167
- "",
168
- "### 결정사항",
169
- decisions,
170
- "",
171
- "### CLAUDE.md 참조",
172
- claudeMdList,
173
- "",
174
- "### 다음 세션 지시",
175
- "- 위 변경사항을 먼저 검토하고 누락된 테스트를 확인하세요.",
176
- "- 필요 시 CLAUDE.md 지침을 재확인한 뒤 작업을 이어가세요.",
177
- "- 작업 완료 후 변경 파일과 검증 결과를 요약하세요.",
178
- ].join("\n");
179
- }
180
-
181
- export function serializeHandoff(options = {}) {
182
- const context = collectHandoffContext(options);
183
- return {
184
- ...context,
185
- prompt: buildHandoffPrompt(context),
186
- };
187
- }
@@ -1,122 +0,0 @@
1
- /**
2
- * Logify — triflux 구조화 로깅 설정
3
- *
4
- * 사용법:
5
- * import { logger, createModuleLogger } from './lib/logger.mjs';
6
- *
7
- * // 기본 로거
8
- * logger.info({ taskId: 'abc' }, 'task.started');
9
- *
10
- * // 모듈별 로거
11
- * const log = createModuleLogger('hub');
12
- * log.info({ port: 27888 }, 'server.started');
13
- * log.error({ err }, 'server.error');
14
- *
15
- * 이벤트 네이밍: {도메인}.{액션} 형식
16
- * hub.started, hub.stopped, route.started, route.completed,
17
- * worker.spawned, worker.completed, worker.timeout,
18
- * mcp.connected, mcp.disconnected, mcp.error,
19
- * team.created, team.deleted, task.claimed, task.completed,
20
- * pipe.connected, pipe.message, pipe.error,
21
- * http.request, http.response, http.error
22
- *
23
- * 로그 레벨 가이드:
24
- * debug — 개발/트러블슈팅용 (변수 값, MCP 메시지, 캐시 키)
25
- * info — 정상 흐름 상태 변경 (서버 시작, 워커 완료, 팀 생성)
26
- * warn — 위험 신호 (재시도 발생, 쿼타 임박, 느린 워커)
27
- * error — 작업 실패 (CLI 실행 실패, MCP 연결 끊김)
28
- * fatal — 프로세스 위협 (DB 연결 불가, 포트 충돌)
29
- */
30
- import pino from "pino";
31
-
32
- const isDev = process.env.NODE_ENV !== "production";
33
-
34
- // 로그는 항상 stderr(fd 2)로 보낸다.
35
- //
36
- // Claude Code 훅은 stdout 을 훅의 JSON 페이로드로 파싱한다. 로그가 stdout 으로
37
- // 나가면 페이로드와 한 스트림에 섞여, 훅이 돌려준 hookSpecificOutput 이 JSON 으로
38
- // 해석되지 않고 그냥 본문 텍스트가 된다. Claude Code 2.1.257 부터는 stdout 이
39
- // "{" 로 시작하는데 유효한 JSON 이 아니면 훅 오류로 보고하므로, 로그가 먼저 나가느냐
40
- // 페이로드가 먼저 나가느냐에 따라 훅 전체가 실패로 뒤집힌다.
41
- //
42
- // stderr 로 보내면 Claude Code 가 verbose/transcript 에서 그대로 보여주고 stdout 은
43
- // 페이로드만 남는다. CLI 관례상으로도 구조화 로그는 stderr 가 맞다.
44
- const LOG_FD = 2;
45
-
46
- export const logger = pino(
47
- {
48
- level: process.env.LOG_LEVEL || (isDev ? "debug" : "info"),
49
-
50
- // 모든 로그에 포함되는 기본 필드
51
- base: {
52
- service: process.env.SERVICE_NAME || "triflux",
53
- env: process.env.NODE_ENV || "development",
54
- },
55
-
56
- // 레벨을 대문자로 출력 (AI 파싱 용이)
57
- formatters: {
58
- level: (label) => ({ level: label.toUpperCase() }),
59
- },
60
-
61
- // ISO 8601 타임스탬프
62
- timestamp: pino.stdTimeFunctions.isoTime,
63
-
64
- // 민감정보 자동 필터링
65
- redact: {
66
- paths: [
67
- "password",
68
- "token",
69
- "apiKey",
70
- "secret",
71
- "authorization",
72
- "*.password",
73
- "*.token",
74
- "*.apiKey",
75
- "*.secret",
76
- "req.headers.authorization",
77
- "req.headers.cookie",
78
- "hubToken",
79
- ],
80
- remove: true,
81
- },
82
-
83
- // 개발 환경: 컬러 콘솔 출력 (stderr)
84
- transport: isDev
85
- ? {
86
- target: "pino-pretty",
87
- options: {
88
- colorize: true,
89
- translateTime: "yyyy-mm-dd HH:MM:ss",
90
- ignore: "pid,hostname",
91
- destination: LOG_FD,
92
- },
93
- }
94
- : undefined,
95
- },
96
- // 운영 환경(transport 미사용)에서도 stdout 이 아니라 stderr 로 쓴다.
97
- isDev ? undefined : pino.destination({ dest: LOG_FD, sync: false }),
98
- );
99
-
100
- /**
101
- * 모듈별 Child Logger 생성.
102
- * 모듈 이름이 모든 로그에 자동 포함된다.
103
- *
104
- * @param {string} module — 모듈 이름 (hub, route, worker, mcp, team 등)
105
- * @returns {import('pino').Logger}
106
- */
107
- export function createModuleLogger(module) {
108
- return logger.child({ module });
109
- }
110
-
111
- // 정상 종료 시 버퍼 flush 보장
112
- process.on("uncaughtException", (err) => {
113
- logger.fatal({ err }, "process.uncaught_exception");
114
- logger.flush();
115
- process.exit(1);
116
- });
117
-
118
- process.on("unhandledRejection", (reason) => {
119
- logger.fatal({ reason: String(reason) }, "process.unhandled_rejection");
120
- logger.flush();
121
- process.exit(1);
122
- });
@@ -1,176 +0,0 @@
1
- #!/usr/bin/env node
2
- // SSRF boundary: stealthFetch only accepts http: and https: URLs. It does not
3
- // guard private IPs, metadata hosts such as 169.254.x.x, or localhost; callers
4
- // must treat the URL as already inside their trust boundary.
5
-
6
- import { pathToFileURL } from "node:url";
7
-
8
- export class CloakBrowserUnavailableError extends Error {}
9
-
10
- const DEFAULT_TIMEOUT_MS = 30_000;
11
- const DEFAULT_WAIT_UNTIL = "load";
12
- const SUPPORTED_PLATFORMS = new Set([
13
- "linux:x64",
14
- "linux:arm64",
15
- "darwin:x64",
16
- "darwin:arm64",
17
- "win32:x64",
18
- ]);
19
- const EXIT_CODES = {
20
- not_installed: 3,
21
- unsupported_platform: 4,
22
- runtime_error: 5,
23
- blocked_scheme: 6,
24
- };
25
-
26
- function isSupportedPlatform(platform, arch) {
27
- return SUPPORTED_PLATFORMS.has(`${platform}:${arch}`);
28
- }
29
-
30
- function isModuleNotFound(error) {
31
- return (
32
- error?.code === "ERR_MODULE_NOT_FOUND" || error?.code === "MODULE_NOT_FOUND"
33
- );
34
- }
35
-
36
- function htmlToText(html) {
37
- return String(html || "")
38
- .replace(/<script\b[^>]*>[\s\S]*?<\/script>/giu, " ")
39
- .replace(/<style\b[^>]*>[\s\S]*?<\/style>/giu, " ")
40
- .replace(/<[^>]+>/gu, " ")
41
- .replace(/&nbsp;/giu, " ")
42
- .replace(/&amp;/giu, "&")
43
- .replace(/&lt;/giu, "<")
44
- .replace(/&gt;/giu, ">")
45
- .replace(/&quot;/giu, '"')
46
- .replace(/&#39;/gu, "'")
47
- .replace(/\s+/gu, " ")
48
- .trim();
49
- }
50
-
51
- function responseStatus(response) {
52
- if (!response) return null;
53
- if (typeof response.status === "function") return response.status();
54
- return response.status ?? null;
55
- }
56
-
57
- function responseUrl(response, fallbackUrl) {
58
- if (!response) return fallbackUrl;
59
- if (typeof response.url === "function") return response.url();
60
- return response.url ?? fallbackUrl;
61
- }
62
-
63
- function parseAllowedFetchUrl(url) {
64
- let parsed;
65
- try {
66
- parsed = new URL(url);
67
- } catch {
68
- return { ok: false, scheme: null };
69
- }
70
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
71
- return { ok: false, scheme: parsed.protocol };
72
- }
73
- return { ok: true, href: parsed.href };
74
- }
75
-
76
- async function closeBrowser(browser) {
77
- if (!browser || typeof browser.close !== "function") return;
78
- try {
79
- await browser.close();
80
- } catch {
81
- // best effort: fetch callers only need the primary result signal
82
- }
83
- }
84
-
85
- export async function stealthFetch(url, opts = {}) {
86
- const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
87
- const waitUntil = opts.waitUntil ?? DEFAULT_WAIT_UNTIL;
88
- const loadCloakBrowser = opts._import ?? (() => import("cloakbrowser"));
89
- const platform = opts._platform ?? process.platform;
90
- const arch = opts._arch ?? process.arch;
91
-
92
- if (!isSupportedPlatform(platform, arch)) {
93
- return {
94
- ok: false,
95
- reason: "unsupported_platform",
96
- platform,
97
- arch,
98
- };
99
- }
100
-
101
- const parsedUrl = parseAllowedFetchUrl(url);
102
- if (!parsedUrl.ok) {
103
- return {
104
- ok: false,
105
- reason: "blocked_scheme",
106
- scheme: parsedUrl.scheme,
107
- };
108
- }
109
-
110
- let cloakbrowser;
111
- try {
112
- cloakbrowser = await loadCloakBrowser();
113
- } catch (error) {
114
- if (isModuleNotFound(error)) return { ok: false, reason: "not_installed" };
115
- return { ok: false, reason: "runtime_error", error: String(error) };
116
- }
117
-
118
- let browser;
119
- try {
120
- const launch = cloakbrowser?.launch;
121
- if (typeof launch !== "function") {
122
- throw new CloakBrowserUnavailableError("cloakbrowser launch unavailable");
123
- }
124
- browser = await launch();
125
- const page = await browser.newPage();
126
- const response = await page.goto(parsedUrl.href, {
127
- waitUntil,
128
- timeout: timeoutMs,
129
- });
130
- const html = await page.content();
131
- return {
132
- ok: true,
133
- engine: "cloakbrowser",
134
- url: parsedUrl.href,
135
- finalUrl: responseUrl(response, parsedUrl.href),
136
- status: responseStatus(response),
137
- html,
138
- text: htmlToText(html),
139
- };
140
- } catch (error) {
141
- return { ok: false, reason: "runtime_error", error: String(error) };
142
- } finally {
143
- await closeBrowser(browser);
144
- }
145
- }
146
-
147
- export function exitCodeFor(result) {
148
- return EXIT_CODES[result?.reason] ?? 5;
149
- }
150
-
151
- export async function main(argv = process.argv) {
152
- const url = argv[2];
153
- if (!url) {
154
- console.error("usage: stealth-fetch <url>");
155
- process.exitCode = 2;
156
- return;
157
- }
158
-
159
- const result = await stealthFetch(url);
160
- if (result.ok) {
161
- console.log(JSON.stringify(result));
162
- process.exitCode = 0;
163
- return;
164
- }
165
-
166
- console.error(`[stealth-fetch] 폴백: ${result.reason}`);
167
- console.log(JSON.stringify(result));
168
- process.exitCode = exitCodeFor(result);
169
- }
170
-
171
- if (
172
- process.argv[1] &&
173
- import.meta.url === pathToFileURL(process.argv[1]).href
174
- ) {
175
- await main();
176
- }