@houtini/seo-audit-console 0.2.2 → 0.3.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.
@@ -0,0 +1,104 @@
1
+ const tokens = (s) => new Set(s.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length > 1));
2
+ const jaccard = (a, b) => {
3
+ if (!a.size || !b.size)
4
+ return 0;
5
+ let inter = 0;
6
+ for (const t of a)
7
+ if (b.has(t))
8
+ inter++;
9
+ return inter / (a.size + b.size - inter);
10
+ };
11
+ export function computeMarketSizing(inputs, location) {
12
+ const universe = new Map();
13
+ for (const d of inputs) {
14
+ for (const k of d.keywords) {
15
+ if (!k.keyword)
16
+ continue;
17
+ const key = k.keyword.toLowerCase();
18
+ const u = universe.get(key) ?? { volume: 0, etv: new Map() };
19
+ u.volume = Math.max(u.volume, k.volume ?? 0); // same keyword, same volume - max guards nulls
20
+ if (k.etv != null && k.etv > 0)
21
+ u.etv.set(d.domain, (u.etv.get(d.domain) ?? 0) + k.etv);
22
+ universe.set(key, u);
23
+ }
24
+ }
25
+ const domains = inputs.map(d => d.domain);
26
+ const etvByDomain = Object.fromEntries(domains.map(d => [d, 0]));
27
+ let universeVolume = 0;
28
+ for (const u of universe.values()) {
29
+ universeVolume += u.volume;
30
+ for (const [d, e] of u.etv)
31
+ etvByDomain[d] = (etvByDomain[d] ?? 0) + e;
32
+ }
33
+ const totalEtv = Object.values(etvByDomain).reduce((s, v) => s + v, 0) || 1;
34
+ const sovByDomain = Object.fromEntries(domains.map(d => [d, Math.round((etvByDomain[d] / totalEtv) * 1000) / 10]));
35
+ const entries = [...universe.entries()].sort((a, b) => b[1].volume - a[1].volume);
36
+ const clustersW = [];
37
+ for (const [key, u] of entries) {
38
+ const tok = tokens(key);
39
+ let best = null;
40
+ let bestJ = 0;
41
+ for (const w of clustersW) {
42
+ const j = jaccard(tok, w.tok);
43
+ if (j > bestJ) {
44
+ bestJ = j;
45
+ best = w;
46
+ }
47
+ }
48
+ if (best && bestJ >= 0.35) {
49
+ best.members.push({ key, u });
50
+ best.volume += u.volume;
51
+ for (const t of tok)
52
+ best.tok.add(t);
53
+ }
54
+ else
55
+ clustersW.push({ tok, members: [{ key, u }], volume: u.volume });
56
+ }
57
+ const clusters = clustersW
58
+ .filter(w => w.members.length >= 2 || w.volume >= 500)
59
+ .sort((a, b) => b.volume - a.volume)
60
+ .slice(0, 15)
61
+ .map(w => {
62
+ const clusterEtv = Object.fromEntries(domains.map(d => [d, 0]));
63
+ for (const m of w.members)
64
+ for (const [d, e] of m.u.etv)
65
+ clusterEtv[d] = (clusterEtv[d] ?? 0) + e;
66
+ const tot = Object.values(clusterEtv).reduce((s, v) => s + v, 0);
67
+ const sov = Object.fromEntries(domains.map(d => [d, tot ? Math.round((clusterEtv[d] / tot) * 1000) / 10 : 0]));
68
+ const leader = tot ? domains.reduce((a, b) => (clusterEtv[a] >= clusterEtv[b] ? a : b)) : null;
69
+ return { head: w.members[0].key, volume: Math.round(w.volume), keywords: w.members.length, leader, sov };
70
+ });
71
+ return {
72
+ domains, location,
73
+ universeKeywords: universe.size,
74
+ universeVolume: Math.round(universeVolume),
75
+ etvByDomain: Object.fromEntries(domains.map(d => [d, Math.round(etvByDomain[d])])),
76
+ sovByDomain, clusters,
77
+ };
78
+ }
79
+ export function ensureMarketTable(db) {
80
+ db.exec(`
81
+ CREATE TABLE IF NOT EXISTS market_sizing (
82
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
83
+ payload TEXT NOT NULL, -- JSON MarketSizing
84
+ fetched_at TEXT DEFAULT (datetime('now'))
85
+ );
86
+ `);
87
+ }
88
+ export function persistMarketSizing(db, m) {
89
+ ensureMarketTable(db);
90
+ db.prepare(`INSERT INTO market_sizing (payload) VALUES (?)`).run(JSON.stringify(m));
91
+ }
92
+ export function latestMarketSizing(db) {
93
+ ensureMarketTable(db);
94
+ const row = db.prepare(`SELECT payload, fetched_at FROM market_sizing ORDER BY id DESC LIMIT 1`).get();
95
+ if (!row)
96
+ return null;
97
+ try {
98
+ return { ...JSON.parse(row.payload), fetchedAt: row.fetched_at };
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ }
104
+ //# sourceMappingURL=marketSizing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marketSizing.js","sourceRoot":"","sources":["../../src/core/marketSizing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type Database from 'better-sqlite3';
2
+ /**
3
+ * SERP-feature footprint - "how much of my market is being eaten by SERP features?"
4
+ *
5
+ * Built from ONE cached DataForSEO Labs ranked_keywords pull (top-down, never SERP loops):
6
+ * each row carries keyword_data.serp_info.serp_item_types (the features PRESENT on that
7
+ * keyword's SERP) plus our rank. Aggregated volume-weighted, split by whether we rank
8
+ * page 1 on that keyword.
9
+ *
10
+ * Honesty: feature PRESENCE is deterministic (D - it's in the Labs index). Whether the
11
+ * feature helps or hurts US is a judgement proxy (N) - "we rank page 1" is the nearest
12
+ * cheap signal to ownership; true feature ownership needs per-keyword SERP calls.
13
+ */
14
+ export interface FootprintRow {
15
+ feature: string;
16
+ label: string;
17
+ keywords: number;
18
+ volume: number;
19
+ volumeSharePct: number;
20
+ page1Keywords: number;
21
+ page1Volume: number;
22
+ page1SharePct: number;
23
+ }
24
+ export interface SerpFootprint {
25
+ target: string;
26
+ location: string | null;
27
+ sampleKeywords: number;
28
+ sampleVolume: number;
29
+ features: FootprintRow[];
30
+ fetchedAt?: string;
31
+ }
32
+ export interface SampleKeyword {
33
+ keyword: string;
34
+ position: number | null;
35
+ searchVolume: number | null;
36
+ serpFeatures: string[] | null;
37
+ }
38
+ export declare function computeSerpFootprint(target: string, location: string | null, sample: SampleKeyword[]): SerpFootprint;
39
+ export declare function ensureFootprintTable(db: Database.Database): void;
40
+ export declare function persistSerpFootprint(db: Database.Database, fp: SerpFootprint): void;
41
+ export declare function latestSerpFootprint(db: Database.Database): SerpFootprint | null;
42
+ //# sourceMappingURL=serpFootprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serpFootprint.d.ts","sourceRoot":"","sources":["../../src/core/serpFootprint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAgCD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAC/B;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,aAAa,CA4BpH;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAYhE;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAE,EAAE,aAAa,GAAG,IAAI,CAInF;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,aAAa,GAAG,IAAI,CAe/E"}
@@ -0,0 +1,99 @@
1
+ const FEATURE_LABELS = {
2
+ ai_overview: 'AI Overview',
3
+ featured_snippet: 'Featured snippet',
4
+ people_also_ask: 'People also ask',
5
+ local_pack: 'Local pack',
6
+ shopping: 'Shopping',
7
+ popular_products: 'Popular products',
8
+ video: 'Video',
9
+ images: 'Images',
10
+ knowledge_graph: 'Knowledge panel',
11
+ top_stories: 'Top stories',
12
+ related_searches: 'Related searches',
13
+ people_also_search: 'People also search for',
14
+ organic: 'Organic results',
15
+ paid: 'Paid ads',
16
+ carousel: 'Carousel',
17
+ map: 'Map',
18
+ twitter: 'X (Twitter)',
19
+ discussions_and_forums: 'Discussions & forums',
20
+ short_videos: 'Short videos',
21
+ refine_products: 'Product refinements',
22
+ found_on_web: 'Found on the web',
23
+ perspectives: 'Perspectives',
24
+ product_considerations: 'Product considerations',
25
+ ai_mode: 'AI Mode',
26
+ };
27
+ // Ubiquitous furniture that says nothing about the market - excluded from the chart.
28
+ const NOISE = new Set(['organic', 'related_searches', 'people_also_search', 'paid', 'images']);
29
+ export function computeSerpFootprint(target, location, sample) {
30
+ const totVolume = sample.reduce((s, k) => s + (k.searchVolume ?? 0), 0);
31
+ const byFeature = new Map();
32
+ for (const k of sample) {
33
+ const vol = k.searchVolume ?? 0;
34
+ const page1 = k.position != null && k.position <= 10;
35
+ for (const f of k.serpFeatures ?? []) {
36
+ const b = byFeature.get(f) ?? { keywords: 0, volume: 0, p1k: 0, p1v: 0 };
37
+ b.keywords++;
38
+ b.volume += vol;
39
+ if (page1) {
40
+ b.p1k++;
41
+ b.p1v += vol;
42
+ }
43
+ byFeature.set(f, b);
44
+ }
45
+ }
46
+ const features = [...byFeature.entries()]
47
+ .filter(([f]) => !NOISE.has(f))
48
+ .map(([f, b]) => ({
49
+ feature: f,
50
+ label: FEATURE_LABELS[f] ?? f.replace(/_/g, ' '),
51
+ keywords: b.keywords,
52
+ volume: Math.round(b.volume),
53
+ volumeSharePct: totVolume ? Math.round((b.volume / totVolume) * 1000) / 10 : 0,
54
+ page1Keywords: b.p1k,
55
+ page1Volume: Math.round(b.p1v),
56
+ page1SharePct: b.volume ? Math.round((b.p1v / b.volume) * 1000) / 10 : 0,
57
+ }))
58
+ .sort((a, b) => b.volume - a.volume)
59
+ .slice(0, 12);
60
+ return { target, location, sampleKeywords: sample.length, sampleVolume: Math.round(totVolume), features };
61
+ }
62
+ export function ensureFootprintTable(db) {
63
+ db.exec(`
64
+ CREATE TABLE IF NOT EXISTS serp_footprint (
65
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
66
+ target TEXT NOT NULL,
67
+ location TEXT,
68
+ sample_keywords INTEGER NOT NULL,
69
+ sample_volume REAL NOT NULL,
70
+ features TEXT NOT NULL, -- JSON FootprintRow[]
71
+ fetched_at TEXT DEFAULT (datetime('now'))
72
+ );
73
+ `);
74
+ }
75
+ export function persistSerpFootprint(db, fp) {
76
+ ensureFootprintTable(db);
77
+ db.prepare(`INSERT INTO serp_footprint (target, location, sample_keywords, sample_volume, features) VALUES (?, ?, ?, ?, ?)`)
78
+ .run(fp.target, fp.location, fp.sampleKeywords, fp.sampleVolume, JSON.stringify(fp.features));
79
+ }
80
+ export function latestSerpFootprint(db) {
81
+ ensureFootprintTable(db);
82
+ const row = db.prepare(`SELECT target, location, sample_keywords, sample_volume, features, fetched_at FROM serp_footprint ORDER BY id DESC LIMIT 1`).get();
83
+ if (!row)
84
+ return null;
85
+ try {
86
+ return {
87
+ target: row.target,
88
+ location: row.location,
89
+ sampleKeywords: row.sample_keywords,
90
+ sampleVolume: row.sample_volume,
91
+ features: JSON.parse(row.features),
92
+ fetchedAt: row.fetched_at,
93
+ };
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
99
+ //# sourceMappingURL=serpFootprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serpFootprint.js","sourceRoot":"","sources":["../../src/core/serpFootprint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Local dashboard webserver — the "Phase 2" delivery surface. Serves the built dashboard
3
+ * UI at http://127.0.0.1:PORT with live data from the property SQLite DBs, so the report
4
+ * lives in a real browser tab: no MCP-App host cache, no iframe sandbox, native file
5
+ * downloads, and a property switcher. Localhost-only by design (binds 127.0.0.1).
6
+ */
7
+ /** Named server-side handlers the web UI may invoke via POST /api/call (allowlist). */
8
+ export type WebCallHandlers = Record<string, (args: Record<string, unknown>) => Promise<Record<string, unknown>>>;
9
+ export interface WebServerOptions {
10
+ dataDir: () => string;
11
+ /** Returns the built dashboard.html template (read per-request so rebuilds show without restart). */
12
+ uiHtml: () => string;
13
+ call: WebCallHandlers;
14
+ port?: number;
15
+ }
16
+ /** Enumerate properties that have a local DB (property_meta.site_url is the source of truth). */
17
+ export declare function listLocalProperties(dataDir: string): {
18
+ siteUrl: string;
19
+ file: string;
20
+ }[];
21
+ export declare function startDashboardServer(opts: WebServerOptions): Promise<{
22
+ url: string;
23
+ port: number;
24
+ }>;
25
+ export declare function stopDashboardServer(): Promise<boolean>;
26
+ export declare function dashboardServerUrl(): string | null;
27
+ //# sourceMappingURL=webServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webServer.d.ts","sourceRoot":"","sources":["../../src/core/webServer.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AAEH,uFAAuF;AACvF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAElH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,qGAAqG;IACrG,MAAM,EAAE,MAAM,MAAM,CAAC;IACrB,IAAI,EAAE,eAAe,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAgBD,iGAAiG;AACjG,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAaxF;AA4BD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA2EzG;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAM5D;AAED,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAElD"}
@@ -0,0 +1,156 @@
1
+ import http from 'node:http';
2
+ import { readdirSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import Database from 'better-sqlite3';
5
+ let running = null;
6
+ let starting = null;
7
+ /** DNS-rebinding / CSRF guard: only ever answer requests addressed to localhost itself.
8
+ * A malicious page that rebinds its domain to 127.0.0.1 arrives with a foreign Host. */
9
+ function isLocalHostHeader(host) {
10
+ if (!host)
11
+ return false;
12
+ const bare = host.replace(/:\d+$/, '').toLowerCase();
13
+ return bare === '127.0.0.1' || bare === 'localhost' || bare === '[::1]';
14
+ }
15
+ const CACHE_DBS = new Set(['dataforseo-cache.db']);
16
+ /** Enumerate properties that have a local DB (property_meta.site_url is the source of truth). */
17
+ export function listLocalProperties(dataDir) {
18
+ let files = [];
19
+ try {
20
+ files = readdirSync(dataDir).filter(f => f.endsWith('.db') && !CACHE_DBS.has(f));
21
+ }
22
+ catch {
23
+ return [];
24
+ }
25
+ const out = [];
26
+ for (const file of files.sort()) {
27
+ let db = null;
28
+ try {
29
+ db = new Database(path.join(dataDir, file), { readonly: true, fileMustExist: true });
30
+ const meta = db.prepare(`SELECT site_url FROM property_meta ORDER BY id LIMIT 1`).get();
31
+ if (meta?.site_url)
32
+ out.push({ siteUrl: meta.site_url, file });
33
+ }
34
+ catch { /* not a property DB */ }
35
+ finally {
36
+ db?.close();
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+ const json = (res, status, body) => {
42
+ const s = JSON.stringify(body);
43
+ res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' });
44
+ res.end(s);
45
+ };
46
+ const readBody = (req, limit = 256 * 1024) => new Promise((resolve, reject) => {
47
+ let size = 0;
48
+ const chunks = [];
49
+ req.on('data', (c) => { size += c.length; if (size > limit) {
50
+ reject(new Error('body too large'));
51
+ req.destroy();
52
+ }
53
+ else
54
+ chunks.push(c); });
55
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
56
+ req.on('error', reject);
57
+ });
58
+ const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
59
+ function pickerPage(props) {
60
+ const items = props.length
61
+ ? props.map(p => `<li><a href="/dashboard?siteUrl=${encodeURIComponent(p.siteUrl)}">${esc(p.siteUrl)}</a></li>`).join('')
62
+ : '<li>No properties yet — run refresh_property first.</li>';
63
+ return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>SEO Audit Console</title>
64
+ <style>body{font-family:system-ui,sans-serif;max-width:640px;margin:4rem auto;padding:0 1rem;color:#1a1a1a}
65
+ h1{font-size:1.3rem}ul{line-height:2}a{color:#cd2026}</style></head>
66
+ <body><h1>SEO Audit Console — choose a property</h1><ul>${items}</ul></body></html>`;
67
+ }
68
+ export async function startDashboardServer(opts) {
69
+ if (running)
70
+ return { url: running.url, port: running.port };
71
+ if (starting)
72
+ return starting; // concurrent callers share one startup — never bind twice
73
+ const server = http.createServer(async (req, res) => {
74
+ try {
75
+ if (!isLocalHostHeader(req.headers.host))
76
+ return json(res, 403, { error: 'forbidden' });
77
+ const u = new URL(req.url ?? '/', 'http://localhost');
78
+ const route = u.pathname;
79
+ if (req.method === 'GET' && (route === '/' || route === '/dashboard')) {
80
+ const props = listLocalProperties(opts.dataDir());
81
+ const siteUrl = u.searchParams.get('siteUrl');
82
+ if (!siteUrl) {
83
+ if (route === '/' && props.length === 1) {
84
+ res.writeHead(302, { location: `/dashboard?siteUrl=${encodeURIComponent(props[0].siteUrl)}` });
85
+ return res.end();
86
+ }
87
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
88
+ return res.end(pickerPage(props));
89
+ }
90
+ const boot = { siteUrl, properties: props.map(p => p.siteUrl) };
91
+ const inject = `<script>window.__SAC_WEB__=${JSON.stringify(boot).replace(/</g, '\\u003c')};</script>`;
92
+ // Replacement FUNCTION, not string — $-sequences in the payload must stay literal.
93
+ const html = opts.uiHtml().replace(/<head([^>]*)>/i, (_m, attrs) => `<head${attrs}>${inject}`);
94
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
95
+ return res.end(html);
96
+ }
97
+ if (req.method === 'GET' && route === '/api/properties') {
98
+ return json(res, 200, { properties: listLocalProperties(opts.dataDir()) });
99
+ }
100
+ if (req.method === 'POST' && route === '/api/call') {
101
+ // Require a JSON content-type: cross-origin "simple" requests can only send
102
+ // text/plain without a CORS preflight, so this blocks blind CSRF POSTs.
103
+ if (!/^application\/json\b/i.test(String(req.headers['content-type'] ?? ''))) {
104
+ return json(res, 415, { error: 'content-type must be application/json' });
105
+ }
106
+ const body = JSON.parse((await readBody(req)) || '{}');
107
+ const handler = body.name ? opts.call[body.name] : undefined;
108
+ if (!handler)
109
+ return json(res, 404, { error: `unknown tool ${body.name ?? ''}` });
110
+ try {
111
+ const structuredContent = await handler(body.arguments ?? {});
112
+ return json(res, 200, { structuredContent });
113
+ }
114
+ catch (e) {
115
+ return json(res, 500, { error: e instanceof Error ? e.message : String(e) });
116
+ }
117
+ }
118
+ json(res, 404, { error: 'not found' });
119
+ }
120
+ catch (e) {
121
+ json(res, 500, { error: e instanceof Error ? e.message : String(e) });
122
+ }
123
+ });
124
+ starting = (async () => {
125
+ const port = await new Promise((resolve, reject) => {
126
+ server.once('error', reject);
127
+ // 127.0.0.1 only — never expose the dashboard (or the SQLite data behind it) on the network.
128
+ server.listen(opts.port ?? 0, '127.0.0.1', () => {
129
+ const addr = server.address();
130
+ resolve(typeof addr === 'object' && addr ? addr.port : (opts.port ?? 0));
131
+ });
132
+ });
133
+ server.unref(); // never keep the MCP process alive on our account
134
+ const url = `http://127.0.0.1:${port}`;
135
+ running = { server, port, url };
136
+ return { url, port };
137
+ })();
138
+ try {
139
+ return await starting;
140
+ }
141
+ finally {
142
+ starting = null;
143
+ }
144
+ }
145
+ export async function stopDashboardServer() {
146
+ if (!running)
147
+ return false;
148
+ const s = running.server;
149
+ running = null;
150
+ await new Promise(resolve => s.close(() => resolve()));
151
+ return true;
152
+ }
153
+ export function dashboardServerUrl() {
154
+ return running?.url ?? null;
155
+ }
156
+ //# sourceMappingURL=webServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webServer.js","sourceRoot":"","sources":["../../src/core/webServer.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwDpE,wBAAgB,OAAO,IAAI,MAAM,CAGhC;AAuJD,wBAAgB,YAAY,IAAI;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CA03C9E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAqEpE,wBAAgB,OAAO,IAAI,MAAM,CAGhC;AA6KD,wBAAgB,YAAY,IAAI;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAmnD9E"}