@hover-dev/core 0.16.0 → 0.18.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.
- package/README.md +26 -55
- package/dist/agentDirectives.d.ts +55 -0
- package/dist/agentDirectives.d.ts.map +1 -0
- package/dist/agentDirectives.js +276 -0
- package/dist/engine.d.ts +28 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +27 -0
- package/dist/memory/businessMemory.d.ts +29 -0
- package/dist/memory/businessMemory.d.ts.map +1 -0
- package/dist/memory/businessMemory.js +125 -0
- package/dist/playwright/launchChrome.d.ts +18 -0
- package/dist/playwright/launchChrome.d.ts.map +1 -1
- package/dist/playwright/launchChrome.js +46 -3
- package/dist/qa/candidates.d.ts +32 -0
- package/dist/qa/candidates.d.ts.map +1 -0
- package/dist/qa/candidates.js +20 -0
- package/dist/qa/intensity.d.ts +33 -0
- package/dist/qa/intensity.d.ts.map +1 -0
- package/dist/qa/intensity.js +25 -0
- package/dist/qa/qaReport.d.ts +19 -0
- package/dist/qa/qaReport.d.ts.map +1 -0
- package/dist/qa/qaReport.js +50 -0
- package/dist/sessions/sessions.d.ts +125 -0
- package/dist/sessions/sessions.d.ts.map +1 -0
- package/dist/sessions/sessions.js +175 -0
- package/dist/specs/authFixture.d.ts +30 -0
- package/dist/specs/authFixture.d.ts.map +1 -0
- package/dist/specs/authFixture.js +145 -0
- package/dist/specs/detectSharedFlows.d.ts +1 -1
- package/dist/specs/detectSharedFlows.d.ts.map +1 -1
- package/dist/specs/detectSharedFlows.js +20 -21
- package/dist/specs/generatePageObject.d.ts +1 -1
- package/dist/specs/generatePageObject.d.ts.map +1 -1
- package/dist/specs/healPrompt.d.ts +19 -0
- package/dist/specs/healPrompt.d.ts.map +1 -0
- package/dist/specs/healPrompt.js +48 -0
- package/dist/specs/humanSteps.d.ts +4 -8
- package/dist/specs/humanSteps.d.ts.map +1 -1
- package/dist/specs/humanSteps.js +6 -1
- package/dist/specs/optimizeSpec.d.ts +15 -8
- package/dist/specs/optimizeSpec.d.ts.map +1 -1
- package/dist/specs/optimizeSpec.js +71 -41
- package/dist/specs/pageObjectManifest.d.ts +3 -1
- package/dist/specs/pageObjectManifest.d.ts.map +1 -1
- package/dist/specs/pageObjectManifest.js +24 -19
- package/dist/specs/replayGrounded.d.ts +45 -0
- package/dist/specs/replayGrounded.d.ts.map +1 -0
- package/dist/specs/replayGrounded.js +155 -0
- package/dist/specs/runFailures.d.ts +34 -0
- package/dist/specs/runFailures.d.ts.map +1 -0
- package/dist/specs/runFailures.js +93 -0
- package/dist/specs/seeds.d.ts +16 -15
- package/dist/specs/seeds.d.ts.map +1 -1
- package/dist/specs/seeds.js +86 -54
- package/dist/specs/sidecar.d.ts +34 -6
- package/dist/specs/sidecar.d.ts.map +1 -1
- package/dist/specs/sidecar.js +79 -9
- package/dist/specs/specStep.d.ts +21 -0
- package/dist/specs/specStep.d.ts.map +1 -0
- package/dist/specs/specStep.js +1 -0
- package/dist/specs/text.d.ts +8 -6
- package/dist/specs/text.d.ts.map +1 -1
- package/dist/specs/text.js +10 -7
- package/dist/specs/writeSpec.d.ts +62 -1
- package/dist/specs/writeSpec.d.ts.map +1 -1
- package/dist/specs/writeSpec.js +596 -21
- package/package.json +9 -29
- package/dist/agents/aider.d.ts +0 -16
- package/dist/agents/aider.d.ts.map +0 -1
- package/dist/agents/aider.js +0 -161
- package/dist/agents/argv.d.ts +0 -11
- package/dist/agents/argv.d.ts.map +0 -1
- package/dist/agents/argv.js +0 -23
- package/dist/agents/claude.d.ts +0 -3
- package/dist/agents/claude.d.ts.map +0 -1
- package/dist/agents/claude.js +0 -195
- package/dist/agents/codex.d.ts +0 -19
- package/dist/agents/codex.d.ts.map +0 -1
- package/dist/agents/codex.js +0 -216
- package/dist/agents/cursor.d.ts +0 -18
- package/dist/agents/cursor.d.ts.map +0 -1
- package/dist/agents/cursor.js +0 -220
- package/dist/agents/detect.d.ts +0 -46
- package/dist/agents/detect.d.ts.map +0 -1
- package/dist/agents/detect.js +0 -80
- package/dist/agents/gemini.d.ts +0 -17
- package/dist/agents/gemini.d.ts.map +0 -1
- package/dist/agents/gemini.js +0 -186
- package/dist/agents/index.d.ts +0 -6
- package/dist/agents/index.d.ts.map +0 -1
- package/dist/agents/index.js +0 -5
- package/dist/agents/invoke.d.ts +0 -12
- package/dist/agents/invoke.d.ts.map +0 -1
- package/dist/agents/invoke.js +0 -96
- package/dist/agents/qwen.d.ts +0 -17
- package/dist/agents/qwen.d.ts.map +0 -1
- package/dist/agents/qwen.js +0 -172
- package/dist/agents/registry.d.ts +0 -19
- package/dist/agents/registry.d.ts.map +0 -1
- package/dist/agents/registry.js +0 -34
- package/dist/agents/shared.d.ts +0 -28
- package/dist/agents/shared.d.ts.map +0 -1
- package/dist/agents/shared.js +0 -35
- package/dist/agents/types.d.ts +0 -186
- package/dist/agents/types.d.ts.map +0 -1
- package/dist/agents/types.js +0 -23
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/mcp/sourceFence.d.ts +0 -23
- package/dist/mcp/sourceFence.d.ts.map +0 -1
- package/dist/mcp/sourceFence.js +0 -75
- package/dist/mcp/sourceServer.d.ts +0 -3
- package/dist/mcp/sourceServer.d.ts.map +0 -1
- package/dist/mcp/sourceServer.js +0 -116
- package/dist/playwright/cdpStatus.d.ts +0 -29
- package/dist/playwright/cdpStatus.d.ts.map +0 -1
- package/dist/playwright/cdpStatus.js +0 -119
- package/dist/playwright/preflight.d.ts +0 -31
- package/dist/playwright/preflight.d.ts.map +0 -1
- package/dist/playwright/preflight.js +0 -82
- package/dist/playwright/preflightCache.d.ts +0 -27
- package/dist/playwright/preflightCache.d.ts.map +0 -1
- package/dist/playwright/preflightCache.js +0 -21
- package/dist/playwright/raiseWindow.d.ts +0 -10
- package/dist/playwright/raiseWindow.d.ts.map +0 -1
- package/dist/playwright/raiseWindow.js +0 -158
- package/dist/playwright/resolveMcpConfig.d.ts +0 -55
- package/dist/playwright/resolveMcpConfig.d.ts.map +0 -1
- package/dist/playwright/resolveMcpConfig.js +0 -66
- package/dist/plugin-api.d.ts +0 -235
- package/dist/plugin-api.d.ts.map +0 -1
- package/dist/plugin-api.js +0 -52
- package/dist/runSession.d.ts +0 -42
- package/dist/runSession.d.ts.map +0 -1
- package/dist/runSession.js +0 -81
- package/dist/scripts/bench-multi-tab.d.ts +0 -2
- package/dist/scripts/bench-multi-tab.d.ts.map +0 -1
- package/dist/scripts/bench-multi-tab.js +0 -192
- package/dist/scripts/bench-ttfb.d.ts +0 -2
- package/dist/scripts/bench-ttfb.d.ts.map +0 -1
- package/dist/scripts/bench-ttfb.js +0 -127
- package/dist/scripts/start-chrome.d.ts +0 -3
- package/dist/scripts/start-chrome.d.ts.map +0 -1
- package/dist/scripts/start-chrome.js +0 -23
- package/dist/service/cdpHandlers.d.ts +0 -44
- package/dist/service/cdpHandlers.d.ts.map +0 -1
- package/dist/service/cdpHandlers.js +0 -85
- package/dist/service/cdpHint.d.ts +0 -48
- package/dist/service/cdpHint.d.ts.map +0 -1
- package/dist/service/cdpHint.js +0 -216
- package/dist/service/conventions.d.ts +0 -8
- package/dist/service/conventions.d.ts.map +0 -1
- package/dist/service/conventions.js +0 -42
- package/dist/service/saveHandlers.d.ts +0 -52
- package/dist/service/saveHandlers.d.ts.map +0 -1
- package/dist/service/saveHandlers.js +0 -75
- package/dist/service/types.d.ts +0 -58
- package/dist/service/types.d.ts.map +0 -1
- package/dist/service/types.js +0 -26
- package/dist/service.d.ts +0 -50
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -1065
- package/dist/skills/writeSkill.d.ts +0 -27
- package/dist/skills/writeSkill.d.ts.map +0 -1
- package/dist/skills/writeSkill.js +0 -13
- package/dist/specs/extractPageObjects.d.ts +0 -18
- package/dist/specs/extractPageObjects.d.ts.map +0 -1
- package/dist/specs/extractPageObjects.js +0 -98
- package/dist/specs/listSpecs.d.ts +0 -52
- package/dist/specs/listSpecs.d.ts.map +0 -1
- package/dist/specs/listSpecs.js +0 -139
- package/dist/specs/optimizationSuggestion.d.ts +0 -26
- package/dist/specs/optimizationSuggestion.d.ts.map +0 -1
- package/dist/specs/optimizationSuggestion.js +0 -28
- package/dist/specs/optimizeSpecWithAgent.d.ts +0 -11
- package/dist/specs/optimizeSpecWithAgent.d.ts.map +0 -1
- package/dist/specs/optimizeSpecWithAgent.js +0 -40
- package/dist/specs/writeCaseCsv.d.ts +0 -28
- package/dist/specs/writeCaseCsv.d.ts.map +0 -1
- package/dist/specs/writeCaseCsv.js +0 -134
package/dist/agents/types.d.ts
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local CLI Agent First — agent abstraction layer.
|
|
3
|
-
*
|
|
4
|
-
* Hover does not bundle any AI runtime. It spawns whatever coding-agent CLI the
|
|
5
|
-
* user has on PATH (`claude`, `codex`, `cursor`, `aider`, ...) and treats it as
|
|
6
|
-
* a strategy implementation behind this interface.
|
|
7
|
-
*
|
|
8
|
-
* To add a new agent: write an AgentDescriptor and register it in registry.ts.
|
|
9
|
-
*/
|
|
10
|
-
export type AgentProtocol = 'argv' | 'stdin' | 'acp' | 'pi-rpc';
|
|
11
|
-
export type StreamFormat = 'stream-json' | 'sse' | 'plain-text' | 'json-lines';
|
|
12
|
-
export declare class UnsupportedAgentProtocolError extends Error {
|
|
13
|
-
constructor(message: string);
|
|
14
|
-
}
|
|
15
|
-
export declare class AgentNotInstalledError extends Error {
|
|
16
|
-
readonly agentId: string;
|
|
17
|
-
constructor(agentId: string);
|
|
18
|
-
}
|
|
19
|
-
export interface InvokeOptions {
|
|
20
|
-
agentId: string;
|
|
21
|
-
prompt: string;
|
|
22
|
-
mcpConfig?: string;
|
|
23
|
-
allowedTools?: string[];
|
|
24
|
-
disallowedTools?: string[];
|
|
25
|
-
maxBudgetUsd?: number;
|
|
26
|
-
model?: string;
|
|
27
|
-
cwd?: string;
|
|
28
|
-
sessionId?: string;
|
|
29
|
-
/** Extra text appended to the agent's system prompt (claude: via
|
|
30
|
-
* --append-system-prompt). Used to inject session-specific context like
|
|
31
|
-
* "the user's current Chrome tab is already on http://localhost:5173/,
|
|
32
|
-
* don't browser_navigate there". */
|
|
33
|
-
appendSystemPrompt?: string;
|
|
34
|
-
/** Optional model API key. Injected into the spawned CLI's environment under
|
|
35
|
-
* the descriptor's `apiKeyEnv` var (e.g. ANTHROPIC_API_KEY) so a user without
|
|
36
|
-
* a logged-in subscription can drive Hover with their own key. Never logged,
|
|
37
|
-
* never persisted server-side — held only for the lifetime of the spawn. */
|
|
38
|
-
apiKey?: string;
|
|
39
|
-
/** Aborts the spawned child if signaled. Used to stop an orphan run when
|
|
40
|
-
* the WebSocket caller disconnects (e.g. user reloads the dev page). */
|
|
41
|
-
signal?: AbortSignal;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Normalized event emitted by every agent, regardless of its native wire format.
|
|
45
|
-
* Each agent's `parseEvent` translates its own stream into these.
|
|
46
|
-
*/
|
|
47
|
-
export type InvokeEvent = {
|
|
48
|
-
kind: 'session_start';
|
|
49
|
-
sessionId: string;
|
|
50
|
-
model?: string;
|
|
51
|
-
} | {
|
|
52
|
-
kind: 'mcp_status';
|
|
53
|
-
server: string;
|
|
54
|
-
status: string;
|
|
55
|
-
} | {
|
|
56
|
-
kind: 'tool_use';
|
|
57
|
-
tool: string;
|
|
58
|
-
input: unknown;
|
|
59
|
-
costUsdSnapshot?: number;
|
|
60
|
-
} | {
|
|
61
|
-
kind: 'tool_result';
|
|
62
|
-
isError?: boolean;
|
|
63
|
-
preview?: string;
|
|
64
|
-
} | {
|
|
65
|
-
kind: 'text';
|
|
66
|
-
text: string;
|
|
67
|
-
}
|
|
68
|
-
/** Running cost / turn-count update emitted mid-session so the widget can
|
|
69
|
-
* show a live $ counter without waiting for session_end. Claude Code's
|
|
70
|
-
* stream-json includes `total_cost_usd` on intermediate result-ish events;
|
|
71
|
-
* agents that don't surface running cost simply never emit this. */
|
|
72
|
-
| {
|
|
73
|
-
kind: 'usage';
|
|
74
|
-
costUsd?: number;
|
|
75
|
-
turns?: number;
|
|
76
|
-
}
|
|
77
|
-
/** End-of-session event. Three terminal states the widget renders distinctly:
|
|
78
|
-
*
|
|
79
|
-
* - normal completion: `isError: false`, no `cancelled` flag
|
|
80
|
-
* - agent / runtime failure: `isError: true`, no `cancelled` flag
|
|
81
|
-
* - user-initiated stop: `cancelled: true` (and we leave `isError: false`
|
|
82
|
-
* so downstream "did the agent fail?" predicates don't conflate
|
|
83
|
-
* "user pressed Stop" with "agent crashed mid-run"). The widget
|
|
84
|
-
* renders this as a neutral "Stopped" state, not a red Failed card.
|
|
85
|
-
*/
|
|
86
|
-
| {
|
|
87
|
-
kind: 'session_end';
|
|
88
|
-
turns?: number;
|
|
89
|
-
costUsd?: number;
|
|
90
|
-
isError?: boolean;
|
|
91
|
-
cancelled?: boolean;
|
|
92
|
-
summary?: string;
|
|
93
|
-
} | {
|
|
94
|
-
kind: 'raw';
|
|
95
|
-
line: string;
|
|
96
|
-
};
|
|
97
|
-
/**
|
|
98
|
-
* How tightly the agent's tool surface can be locked down per invocation.
|
|
99
|
-
*
|
|
100
|
-
* 'hard' — the agent CLI accepts a deny-list / allow-list that effectively
|
|
101
|
-
* removes built-in tools (shell, file edit, etc.) so the only
|
|
102
|
-
* callable surface is whatever MCP servers we configure. Claude
|
|
103
|
-
* Code's `--strict-mcp-config` + `--allowedTools mcp__playwright`
|
|
104
|
-
* + `--disallowedTools <every built-in>` is the canonical example.
|
|
105
|
-
*
|
|
106
|
-
* 'soft' — the agent CLI does not expose a way to disable its built-in
|
|
107
|
-
* tools (shell, fs). We can constrain side-effects via OS-level
|
|
108
|
-
* sandbox flags (e.g. codex's `--sandbox read-only`) and we lean
|
|
109
|
-
* on a strict `developer_instructions` system-prompt to nudge the
|
|
110
|
-
* agent toward MCP-only behavior, but a determined / hallucinating
|
|
111
|
-
* agent COULD still try a built-in shell call. The widget should
|
|
112
|
-
* mark this agent with a warning indicator.
|
|
113
|
-
*/
|
|
114
|
-
export type SandboxStrength = 'hard' | 'soft';
|
|
115
|
-
/**
|
|
116
|
-
* Human-facing metadata for the widget's agent picker. None of these affect
|
|
117
|
-
* agent invocation — they only shape how the agent is presented in the UI.
|
|
118
|
-
*/
|
|
119
|
-
export interface AgentDisplay {
|
|
120
|
-
/** Pretty name for the dropdown ("Claude Code", "OpenAI Codex"). */
|
|
121
|
-
label: string;
|
|
122
|
-
/** One-line tagline shown under the label. */
|
|
123
|
-
tagline?: string;
|
|
124
|
-
/** Vendor / source URL — clicking the agent name in the widget can open
|
|
125
|
-
* this in a new tab when the agent isn't installed. */
|
|
126
|
-
homepage?: string;
|
|
127
|
-
/** Shell command the user can run to install (copy-paste from a tooltip
|
|
128
|
-
* in the widget when the agent is listed but not on PATH). */
|
|
129
|
-
installHint?: string;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Per-invocation parser state. A fresh object is created by `invokeAgent`
|
|
133
|
-
* for each spawn and passed to both `parseEvent` and `onStreamEnd`.
|
|
134
|
-
*
|
|
135
|
-
* Descriptors that need to accumulate state across lines (cost, turn count,
|
|
136
|
-
* last agent message for synthesized session_end, etc.) read and write
|
|
137
|
-
* their own keys on this object. There is no shared shape — each agent
|
|
138
|
-
* uses whatever fields it needs.
|
|
139
|
-
*
|
|
140
|
-
* Why: module-level state in claude.ts / codex.ts worked only because the
|
|
141
|
-
* service enforces one in-flight invocation per Node process. Two concurrent
|
|
142
|
-
* agent runs (future: tests in parallel, in-process workers) would silently
|
|
143
|
-
* smear their cost accumulators together. Threading the state object per
|
|
144
|
-
* invocation removes that hazard at zero runtime cost.
|
|
145
|
-
*/
|
|
146
|
-
export type ParserState = Record<string, unknown>;
|
|
147
|
-
export interface AgentDescriptor {
|
|
148
|
-
id: string;
|
|
149
|
-
binName: string;
|
|
150
|
-
protocol: AgentProtocol;
|
|
151
|
-
streamFormat: StreamFormat;
|
|
152
|
-
sandboxStrength: SandboxStrength;
|
|
153
|
-
display: AgentDisplay;
|
|
154
|
-
/** Hard-sandbox agents pass this list to `disallowedTools` when the
|
|
155
|
-
* service-level allow/deny config isn't explicitly overridden. Lets the
|
|
156
|
-
* per-CLI deny list live alongside its descriptor instead of as a magic
|
|
157
|
-
* array in the service. Soft-sandbox agents leave this undefined. */
|
|
158
|
-
defaultDisallowedTools?: readonly string[];
|
|
159
|
-
/** Environment variable this CLI reads its model API key from
|
|
160
|
-
* (claude: ANTHROPIC_API_KEY, codex: OPENAI_API_KEY). When set and the
|
|
161
|
-
* caller supplies `InvokeOptions.apiKey`, the key is injected into the spawn
|
|
162
|
-
* env so the user can run on a raw key instead of a logged-in subscription.
|
|
163
|
-
* Undefined for agents that have no API-key env path. */
|
|
164
|
-
apiKeyEnv?: string;
|
|
165
|
-
buildArgs(opts: InvokeOptions): string[];
|
|
166
|
-
/**
|
|
167
|
-
* Parse a single line of agent stdout into normalised InvokeEvents.
|
|
168
|
-
* `state` is a per-invocation scratch pad (see ParserState). Optional
|
|
169
|
-
* for callers that don't accumulate across lines (and for unit tests
|
|
170
|
-
* that don't care about cost / turn carry-over) — descriptors that
|
|
171
|
-
* DO accumulate must check / initialise the state object themselves.
|
|
172
|
-
*/
|
|
173
|
-
parseEvent(line: string, state?: ParserState): InvokeEvent[];
|
|
174
|
-
/**
|
|
175
|
-
* Optional. Called once after the agent's stream closes, with the child's
|
|
176
|
-
* exit code (or null if it was aborted). Lets agents whose protocol does
|
|
177
|
-
* NOT emit an explicit session-terminating event synthesize one from
|
|
178
|
-
* accumulated parser state. Returns `null` if the agent's own `parseEvent`
|
|
179
|
-
* already emitted a `session_end` and nothing further is needed.
|
|
180
|
-
*
|
|
181
|
-
* Used by codex.ts (no native session_end). Claude does not implement
|
|
182
|
-
* this — `result` events terminate naturally.
|
|
183
|
-
*/
|
|
184
|
-
onStreamEnd?(exitCode: number | null, state?: ParserState): InvokeEvent | null;
|
|
185
|
-
}
|
|
186
|
-
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"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"}
|
package/dist/agents/types.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local CLI Agent First — agent abstraction layer.
|
|
3
|
-
*
|
|
4
|
-
* Hover does not bundle any AI runtime. It spawns whatever coding-agent CLI the
|
|
5
|
-
* user has on PATH (`claude`, `codex`, `cursor`, `aider`, ...) and treats it as
|
|
6
|
-
* a strategy implementation behind this interface.
|
|
7
|
-
*
|
|
8
|
-
* To add a new agent: write an AgentDescriptor and register it in registry.ts.
|
|
9
|
-
*/
|
|
10
|
-
export class UnsupportedAgentProtocolError extends Error {
|
|
11
|
-
constructor(message) {
|
|
12
|
-
super(message);
|
|
13
|
-
this.name = 'UnsupportedAgentProtocolError';
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
export class AgentNotInstalledError extends Error {
|
|
17
|
-
agentId;
|
|
18
|
-
constructor(agentId) {
|
|
19
|
-
super(`Agent "${agentId}" is not installed (binary not found on PATH).`);
|
|
20
|
-
this.agentId = agentId;
|
|
21
|
-
this.name = 'AgentNotInstalledError';
|
|
22
|
-
}
|
|
23
|
-
}
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export interface FenceOk {
|
|
2
|
-
ok: true;
|
|
3
|
-
/** Absolute, root-anchored path the server may stat/read (after realpath). */
|
|
4
|
-
abs: string;
|
|
5
|
-
/** POSIX-style path relative to the root — safe to echo back to the agent. */
|
|
6
|
-
rel: string;
|
|
7
|
-
}
|
|
8
|
-
export interface FenceErr {
|
|
9
|
-
ok: false;
|
|
10
|
-
reason: string;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Resolve `input` against `root`, refusing anything outside the root or matching
|
|
14
|
-
* a secret pattern. `input` is treated as relative to the root; an absolute
|
|
15
|
-
* input is resolved too but will fail the containment check unless it happens to
|
|
16
|
-
* live under the root (the agent should pass repo-relative paths).
|
|
17
|
-
*/
|
|
18
|
-
export declare function resolveSourcePath(root: string, input: string): FenceOk | FenceErr;
|
|
19
|
-
/** True if a resolved-and-realpathed absolute path is still inside the root.
|
|
20
|
-
* The server calls this AFTER realpath to defeat symlink escape (a symlink
|
|
21
|
-
* whose lexical path passed resolveSourcePath but points outside the root). */
|
|
22
|
-
export declare function isWithinRoot(root: string, realAbs: string): boolean;
|
|
23
|
-
//# sourceMappingURL=sourceFence.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sourceFence.d.ts","sourceRoot":"","sources":["../../src/mcp/sourceFence.ts"],"names":[],"mappings":"AAgCA,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,IAAI,CAAC;IACT,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;CACb;AACD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAsBjF;AAED;;gFAEgF;AAChF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAInE"}
|
package/dist/mcp/sourceFence.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The path fence for the opt-in `read_source` capability (codeContext).
|
|
3
|
-
*
|
|
4
|
-
* Giving the agent the ability to read source is the ONE place Hover relaxes
|
|
5
|
-
* its "the agent only touches the browser" rule, so the fence is the whole
|
|
6
|
-
* security story. It must guarantee, on every call, that a caller-supplied path:
|
|
7
|
-
* 1. resolves to a location INSIDE the project root (no `..` / absolute-path
|
|
8
|
-
* escape — symlink escape is caught by the server's realpath re-check), and
|
|
9
|
-
* 2. is not a credential / secret / VCS / dependency file.
|
|
10
|
-
* Pure + lexical so it's exhaustively unit-testable; the server layers a
|
|
11
|
-
* realpath check + a size/binary guard on top.
|
|
12
|
-
*/
|
|
13
|
-
import { resolve, relative, isAbsolute, sep } from 'node:path';
|
|
14
|
-
/** Files we refuse to read even inside the root — credentials, keys, VCS,
|
|
15
|
-
* dependency trees, build caches. Matched against the POSIX-style relative
|
|
16
|
-
* path (so `\` on Windows is normalised first). */
|
|
17
|
-
const SECRET_PATTERNS = [
|
|
18
|
-
/(^|\/)\.env(\.[^/]*)?$/i, // .env, .env.local, .env.production
|
|
19
|
-
/(^|\/)\.git(\/|$)/, // the git dir
|
|
20
|
-
/(^|\/)node_modules(\/|$)/, // dependency tree
|
|
21
|
-
/(^|\/)\.(next|nuxt|svelte-kit|astro|turbo|cache|output|vercel)(\/|$)/, // build caches
|
|
22
|
-
/(^|\/)(dist|build|coverage)(\/|$)/, // build output
|
|
23
|
-
/\.(pem|key|p12|pfx|crt|cer|der|keystore|jks)$/i, // key / cert material
|
|
24
|
-
/(^|\/)id_(rsa|dsa|ecdsa|ed25519)(\.[^/]*)?$/i, // ssh keys
|
|
25
|
-
/(^|\/)\.(npmrc|netrc|pgpass)$/i, // token-bearing rc files
|
|
26
|
-
/(^|\/)\.(ssh|aws|gnupg|gcloud|kube|docker)(\/|$)/i, // credential dirs
|
|
27
|
-
/(^|\/)secrets?(\/|\.[^/]*$|$)/i, // a secrets dir, or a secret(s).<ext> file
|
|
28
|
-
/(^|\/)credentials?(\/|\.[^/]*$|$)/i, // a credentials dir, or credential(s).<ext>
|
|
29
|
-
/\.(secret|secrets)$/i,
|
|
30
|
-
];
|
|
31
|
-
/** A path containing a NUL or C0 control char is never a legitimate source file. */
|
|
32
|
-
function hasControlChar(s) {
|
|
33
|
-
for (let i = 0; i < s.length; i++) {
|
|
34
|
-
if (s.charCodeAt(i) < 0x20)
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Resolve `input` against `root`, refusing anything outside the root or matching
|
|
41
|
-
* a secret pattern. `input` is treated as relative to the root; an absolute
|
|
42
|
-
* input is resolved too but will fail the containment check unless it happens to
|
|
43
|
-
* live under the root (the agent should pass repo-relative paths).
|
|
44
|
-
*/
|
|
45
|
-
export function resolveSourcePath(root, input) {
|
|
46
|
-
if (typeof input !== 'string' || !input.trim()) {
|
|
47
|
-
return { ok: false, reason: 'path is required' };
|
|
48
|
-
}
|
|
49
|
-
if (hasControlChar(input)) {
|
|
50
|
-
return { ok: false, reason: 'path contains control characters' };
|
|
51
|
-
}
|
|
52
|
-
const rootAbs = resolve(root);
|
|
53
|
-
const abs = resolve(rootAbs, input);
|
|
54
|
-
const rel = relative(rootAbs, abs);
|
|
55
|
-
// Outside the root: relative() returns '' for the root itself, a '..'-prefixed
|
|
56
|
-
// path for an ancestor/sibling, or an absolute path when on a different drive.
|
|
57
|
-
if (rel === '' || rel === '..' || rel.startsWith('..' + sep) || rel.startsWith('../') || isAbsolute(rel)) {
|
|
58
|
-
return { ok: false, reason: 'path escapes the project root' };
|
|
59
|
-
}
|
|
60
|
-
const relPosix = rel.split(sep).join('/');
|
|
61
|
-
for (const pat of SECRET_PATTERNS) {
|
|
62
|
-
if (pat.test(relPosix)) {
|
|
63
|
-
return { ok: false, reason: `refused: "${relPosix}" matches an excluded (secret / build / VCS) pattern` };
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return { ok: true, abs, rel: relPosix };
|
|
67
|
-
}
|
|
68
|
-
/** True if a resolved-and-realpathed absolute path is still inside the root.
|
|
69
|
-
* The server calls this AFTER realpath to defeat symlink escape (a symlink
|
|
70
|
-
* whose lexical path passed resolveSourcePath but points outside the root). */
|
|
71
|
-
export function isWithinRoot(root, realAbs) {
|
|
72
|
-
const rootAbs = resolve(root);
|
|
73
|
-
const rel = relative(rootAbs, realAbs);
|
|
74
|
-
return rel !== '' && rel !== '..' && !rel.startsWith('..' + sep) && !rel.startsWith('../') && !isAbsolute(rel);
|
|
75
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sourceServer.d.ts","sourceRoot":"","sources":["../../src/mcp/sourceServer.ts"],"names":[],"mappings":""}
|
package/dist/mcp/sourceServer.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Hover source-reader MCP server — the runtime behind the opt-in `codeContext`
|
|
4
|
-
* switch. Spawned by the agent (Claude Code / Codex) as a stdio subprocess when
|
|
5
|
-
* codeContext is enabled, in addition to Playwright MCP. It gives the agent
|
|
6
|
-
* READ-ONLY, fenced access to the project's source so it can author smarter
|
|
7
|
-
* tests and do white-box security/pentest work (read the actual query / authz
|
|
8
|
-
* check, not just the rendered DOM).
|
|
9
|
-
*
|
|
10
|
-
* This is the ONE place Hover relaxes "the agent only touches the browser", so
|
|
11
|
-
* the safety is all in the fence (src/mcp/sourceFence.ts) + the guards here:
|
|
12
|
-
* - every path is resolved INSIDE the project root (no `..` / absolute escape)
|
|
13
|
-
* - a realpath re-check defeats symlink escape
|
|
14
|
-
* - secret / VCS / dependency / build files are refused (.env, keys, .git, …)
|
|
15
|
-
* - read-only: there is no write / exec / delete tool here
|
|
16
|
-
* - a size cap + a binary guard keep it to actual source
|
|
17
|
-
*
|
|
18
|
-
* The project root comes in via env:
|
|
19
|
-
* HOVER_PROJECT_ROOT absolute path to the dev project root (devRoot)
|
|
20
|
-
*
|
|
21
|
-
* Tools exposed:
|
|
22
|
-
* read_source({ path }) → the file's text (fenced, ≤256 KB, text-only)
|
|
23
|
-
* list_source({ subdir? }) → a shallow dir listing (secrets filtered out)
|
|
24
|
-
*/
|
|
25
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
26
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
27
|
-
import { z } from 'zod';
|
|
28
|
-
import { readFileSync, realpathSync, statSync, readdirSync } from 'node:fs';
|
|
29
|
-
import { resolveSourcePath, isWithinRoot } from './sourceFence.js';
|
|
30
|
-
const root = process.env.HOVER_PROJECT_ROOT;
|
|
31
|
-
if (!root) {
|
|
32
|
-
process.stderr.write('[hover-source-mcp] HOVER_PROJECT_ROOT must be set by the host.\n');
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
const ROOT = root;
|
|
36
|
-
const MAX_BYTES = 256 * 1024;
|
|
37
|
-
function md(text) {
|
|
38
|
-
return { content: [{ type: 'text', text }] };
|
|
39
|
-
}
|
|
40
|
-
const server = new McpServer({ name: 'hover-source', version: '0.0.0' });
|
|
41
|
-
server.registerTool('read_source', {
|
|
42
|
-
description: "Read a source file from THIS project (read-only). Pass a repo-relative path (e.g. the one in an element's data-hover-source, `src/app/login.tsx:42` → path `src/app/login.tsx`). Fenced to the project root: paths that escape it, or that name secrets / keys / .env / .git / node_modules / build output, are refused. Use this to write tests against the real selectors & routes, or — in security/pentest mode — to confirm a finding against the actual server code (the SQL query, the authz check). You cannot write, run, or delete anything.",
|
|
43
|
-
inputSchema: {
|
|
44
|
-
path: z.string().describe('Repo-relative path to a source file, e.g. "src/api/orders.ts".'),
|
|
45
|
-
},
|
|
46
|
-
}, async ({ path }) => {
|
|
47
|
-
const f = resolveSourcePath(ROOT, path);
|
|
48
|
-
if (!f.ok)
|
|
49
|
-
return md(`✗ ${f.reason}`);
|
|
50
|
-
let real;
|
|
51
|
-
try {
|
|
52
|
-
real = realpathSync(f.abs);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
return md(`✗ not found: ${f.rel}`);
|
|
56
|
-
}
|
|
57
|
-
if (!isWithinRoot(ROOT, real))
|
|
58
|
-
return md(`✗ refused: "${f.rel}" resolves (via a symlink) outside the project root`);
|
|
59
|
-
let st;
|
|
60
|
-
try {
|
|
61
|
-
st = statSync(real);
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return md(`✗ not found: ${f.rel}`);
|
|
65
|
-
}
|
|
66
|
-
if (st.isDirectory())
|
|
67
|
-
return md(`✗ "${f.rel}" is a directory — use list_source`);
|
|
68
|
-
if (st.size > MAX_BYTES)
|
|
69
|
-
return md(`✗ "${f.rel}" is ${Math.round(st.size / 1024)} KB — too large to read (cap ${MAX_BYTES / 1024} KB)`);
|
|
70
|
-
let buf;
|
|
71
|
-
try {
|
|
72
|
-
buf = readFileSync(real);
|
|
73
|
-
}
|
|
74
|
-
catch (e) {
|
|
75
|
-
return md(`✗ could not read ${f.rel}: ${e instanceof Error ? e.message : String(e)}`);
|
|
76
|
-
}
|
|
77
|
-
// Binary guard — a NUL byte in the first 8 KB means it isn't source text.
|
|
78
|
-
if (buf.subarray(0, 8192).includes(0))
|
|
79
|
-
return md(`✗ "${f.rel}" looks binary — refused`);
|
|
80
|
-
return md(`\`\`\`\n// ${f.rel}\n${buf.toString('utf-8')}\n\`\`\``);
|
|
81
|
-
});
|
|
82
|
-
server.registerTool('list_source', {
|
|
83
|
-
description: 'List the entries of a directory in THIS project (shallow, read-only). Omit `subdir` for the project root. Secret / VCS / dependency / build entries are filtered out. Use it to discover what source exists before reading a file.',
|
|
84
|
-
inputSchema: {
|
|
85
|
-
subdir: z.string().optional().describe('Repo-relative directory, e.g. "src/api". Omit for the root.'),
|
|
86
|
-
},
|
|
87
|
-
}, async ({ subdir }) => {
|
|
88
|
-
let dirAbs = ROOT;
|
|
89
|
-
let base = '';
|
|
90
|
-
if (subdir && subdir.trim() && subdir.trim() !== '.') {
|
|
91
|
-
const d = resolveSourcePath(ROOT, subdir);
|
|
92
|
-
if (!d.ok)
|
|
93
|
-
return md(`✗ ${d.reason}`);
|
|
94
|
-
dirAbs = d.abs;
|
|
95
|
-
base = d.rel;
|
|
96
|
-
}
|
|
97
|
-
let entries;
|
|
98
|
-
try {
|
|
99
|
-
entries = readdirSync(dirAbs, { withFileTypes: true });
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
return md(`✗ not a readable directory: ${base || '.'}`);
|
|
103
|
-
}
|
|
104
|
-
const rows = [];
|
|
105
|
-
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
106
|
-
const rel = base ? `${base}/${e.name}` : e.name;
|
|
107
|
-
// Filter via the same fence so secrets/build/VCS never even show up.
|
|
108
|
-
if (!resolveSourcePath(ROOT, rel).ok)
|
|
109
|
-
continue;
|
|
110
|
-
rows.push(e.isDirectory() ? `${rel}/` : rel);
|
|
111
|
-
}
|
|
112
|
-
if (rows.length === 0)
|
|
113
|
-
return md(`(empty or fully filtered) ${base || '.'}`);
|
|
114
|
-
return md(`${base || '.'} —\n${rows.join('\n')}`);
|
|
115
|
-
});
|
|
116
|
-
await server.connect(new StdioServerTransport());
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export type CdpState = 'same-window' | 'wrong-window' | 'no-cdp';
|
|
2
|
-
export interface CdpStatusResult {
|
|
3
|
-
state: CdpState;
|
|
4
|
-
/** Tab count when state !== 'no-cdp'. */
|
|
5
|
-
tabCount?: number;
|
|
6
|
-
/** Matching tab URL inside the debug Chrome (only set for 'wrong-window'). */
|
|
7
|
-
matchingTabUrl?: string;
|
|
8
|
-
/** Browser product string from /json/version when state !== 'no-cdp'. */
|
|
9
|
-
browser?: string;
|
|
10
|
-
/** When state === 'no-cdp', the preflight reason. */
|
|
11
|
-
reason?: string;
|
|
12
|
-
}
|
|
13
|
-
export declare function checkCdpStatus(cdpUrl: string, pageUrl: string): Promise<CdpStatusResult>;
|
|
14
|
-
/**
|
|
15
|
-
* Bring the debug-Chrome tab matching `pageUrl`'s origin to the front. If no
|
|
16
|
-
* matching tab exists, open a new tab on the origin. Returns the URL of the
|
|
17
|
-
* tab that was focused (or opened) for logging.
|
|
18
|
-
*
|
|
19
|
-
* Uses a short-lived playwright-core connection — opens it, does the work,
|
|
20
|
-
* closes it. We don't keep a long-lived browser handle.
|
|
21
|
-
*/
|
|
22
|
-
export declare function focusDebugTab(cdpUrl: string, pageUrl: string): Promise<{
|
|
23
|
-
ok: true;
|
|
24
|
-
focusedUrl: string;
|
|
25
|
-
} | {
|
|
26
|
-
ok: false;
|
|
27
|
-
reason: string;
|
|
28
|
-
}>;
|
|
29
|
-
//# sourceMappingURL=cdpStatus.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cdpStatus.d.ts","sourceRoot":"","sources":["../../src/playwright/cdpStatus.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,cAAc,GAAG,QAAQ,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,QAAQ,CAAC;IAChB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,CAAC,CA4B1B;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA8C3E"}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* "Is the widget running in the debug Chrome?" — answered by comparing the
|
|
3
|
-
* widget's page origin against the CDP tab list.
|
|
4
|
-
*
|
|
5
|
-
* Three states:
|
|
6
|
-
* - 'same-window' widget IS in the debug Chrome; agent can drive this tab.
|
|
7
|
-
* - 'wrong-window' debug Chrome is up, but on a different Chrome process.
|
|
8
|
-
* Widget should disable itself; service can bringToFront
|
|
9
|
-
* the corresponding tab in the debug Chrome so the user
|
|
10
|
-
* can switch windows.
|
|
11
|
-
* - 'no-cdp' no debug Chrome at all; the widget should let the user
|
|
12
|
-
* trigger a launch.
|
|
13
|
-
*/
|
|
14
|
-
import { chromium } from 'playwright-core';
|
|
15
|
-
import { getPreflight } from './preflightCache.js';
|
|
16
|
-
import { findCdpPid, raiseChromeWindow } from './raiseWindow.js';
|
|
17
|
-
/**
|
|
18
|
-
* Parse a page URL down to its origin (protocol + host + port). We compare
|
|
19
|
-
* by origin, not full URL — the user might be on /login while the debug
|
|
20
|
-
* Chrome tab is on /, but they're the same SPA, same app, same target.
|
|
21
|
-
*/
|
|
22
|
-
function originOf(rawUrl) {
|
|
23
|
-
try {
|
|
24
|
-
return new URL(rawUrl).origin;
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
export async function checkCdpStatus(cdpUrl, pageUrl) {
|
|
31
|
-
const wantOrigin = originOf(pageUrl);
|
|
32
|
-
if (!wantOrigin) {
|
|
33
|
-
// Treat unparseable page URLs as no-cdp so the UI nudges a relaunch.
|
|
34
|
-
return { state: 'no-cdp', reason: `unparseable page URL: ${pageUrl}` };
|
|
35
|
-
}
|
|
36
|
-
const cdp = await getPreflight(cdpUrl);
|
|
37
|
-
if (!cdp.ok) {
|
|
38
|
-
return { state: 'no-cdp', reason: cdp.reason };
|
|
39
|
-
}
|
|
40
|
-
const match = cdp.tabs.find(t => originOf(t.url) === wantOrigin);
|
|
41
|
-
if (match) {
|
|
42
|
-
return {
|
|
43
|
-
state: 'same-window',
|
|
44
|
-
tabCount: cdp.tabs.length,
|
|
45
|
-
browser: cdp.browser,
|
|
46
|
-
matchingTabUrl: match.url,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
return {
|
|
50
|
-
state: 'wrong-window',
|
|
51
|
-
tabCount: cdp.tabs.length,
|
|
52
|
-
browser: cdp.browser,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Bring the debug-Chrome tab matching `pageUrl`'s origin to the front. If no
|
|
57
|
-
* matching tab exists, open a new tab on the origin. Returns the URL of the
|
|
58
|
-
* tab that was focused (or opened) for logging.
|
|
59
|
-
*
|
|
60
|
-
* Uses a short-lived playwright-core connection — opens it, does the work,
|
|
61
|
-
* closes it. We don't keep a long-lived browser handle.
|
|
62
|
-
*/
|
|
63
|
-
export async function focusDebugTab(cdpUrl, pageUrl) {
|
|
64
|
-
const wantOrigin = originOf(pageUrl);
|
|
65
|
-
if (!wantOrigin) {
|
|
66
|
-
return { ok: false, reason: `unparseable page URL: ${pageUrl}` };
|
|
67
|
-
}
|
|
68
|
-
let browser;
|
|
69
|
-
try {
|
|
70
|
-
browser = await chromium.connectOverCDP(cdpUrl);
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
74
|
-
return { ok: false, reason: `couldn't connect to CDP at ${cdpUrl}: ${msg}` };
|
|
75
|
-
}
|
|
76
|
-
let focusedUrl;
|
|
77
|
-
try {
|
|
78
|
-
const pages = browser.contexts().flatMap(c => c.pages());
|
|
79
|
-
const match = pages.find(p => originOf(p.url()) === wantOrigin);
|
|
80
|
-
if (match) {
|
|
81
|
-
await match.bringToFront();
|
|
82
|
-
focusedUrl = match.url();
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
// No tab on the dev origin yet — open one so the widget appears.
|
|
86
|
-
const context = browser.contexts()[0] ?? (await browser.newContext());
|
|
87
|
-
const page = await context.newPage();
|
|
88
|
-
await page.goto(pageUrl, { waitUntil: 'domcontentloaded' });
|
|
89
|
-
await page.bringToFront();
|
|
90
|
-
focusedUrl = page.url();
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
await browser.close().catch(() => { });
|
|
95
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
96
|
-
return { ok: false, reason: `bringToFront failed: ${msg}` };
|
|
97
|
-
}
|
|
98
|
-
await browser.close().catch(() => { });
|
|
99
|
-
// CDP-level bringToFront only activates the tab inside the Chrome process;
|
|
100
|
-
// on macOS in particular the Chrome *window* stays buried if it wasn't
|
|
101
|
-
// foreground already. Raise the OS window too. Best-effort, never fatal.
|
|
102
|
-
const port = portFromCdpUrl(cdpUrl);
|
|
103
|
-
if (port !== null) {
|
|
104
|
-
const pid = await findCdpPid(port);
|
|
105
|
-
if (pid !== null)
|
|
106
|
-
await raiseChromeWindow(pid);
|
|
107
|
-
}
|
|
108
|
-
return { ok: true, focusedUrl };
|
|
109
|
-
}
|
|
110
|
-
function portFromCdpUrl(cdpUrl) {
|
|
111
|
-
try {
|
|
112
|
-
const u = new URL(cdpUrl);
|
|
113
|
-
const port = Number.parseInt(u.port, 10);
|
|
114
|
-
return Number.isInteger(port) && port > 0 ? port : null;
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Connect to the user's Chrome via CDP and return the URLs of all open tabs.
|
|
3
|
-
* Closes the connection before returning (does not hold a Playwright session).
|
|
4
|
-
*/
|
|
5
|
-
export declare function connectAndListTabs(cdpUrl: string): Promise<string[]>;
|
|
6
|
-
export interface CdpTabInfo {
|
|
7
|
-
url: string;
|
|
8
|
-
title?: string;
|
|
9
|
-
type?: string;
|
|
10
|
-
}
|
|
11
|
-
export type CdpPreflightResult = {
|
|
12
|
-
ok: true;
|
|
13
|
-
browser: string;
|
|
14
|
-
tabs: CdpTabInfo[];
|
|
15
|
-
} | {
|
|
16
|
-
ok: false;
|
|
17
|
-
reason: string;
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Lightweight CDP health check via the /json endpoints.
|
|
21
|
-
*
|
|
22
|
-
* Critical: this MUST run before invoking the agent. If CDP isn't responsive,
|
|
23
|
-
* the Playwright MCP server falls back to launching its OWN Chromium — and
|
|
24
|
-
* Hover's premise is to drive the user's existing Chrome (with their dev
|
|
25
|
-
* state, cookies, devtools open), never spawn a fresh one.
|
|
26
|
-
*
|
|
27
|
-
* Pure HTTP — no playwright-core handshake, no setDownloadBehavior nonsense
|
|
28
|
-
* that can get stuck on busy CDP sessions.
|
|
29
|
-
*/
|
|
30
|
-
export declare function preflightCDP(cdpUrl: string, timeoutMs?: number): Promise<CdpPreflightResult>;
|
|
31
|
-
//# sourceMappingURL=preflight.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/playwright/preflight.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ1E;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,EAAE,CAAA;CAAE,GACjD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,SAAS,SAAO,GACf,OAAO,CAAC,kBAAkB,CAAC,CAsD7B"}
|