smartcontext-proxy 0.1.0 → 0.2.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 (48) hide show
  1. package/PLAN-v2.md +390 -0
  2. package/dist/src/context/ab-test.d.ts +32 -0
  3. package/dist/src/context/ab-test.js +133 -0
  4. package/dist/src/index.js +99 -78
  5. package/dist/src/proxy/classifier.d.ts +14 -0
  6. package/dist/src/proxy/classifier.js +63 -0
  7. package/dist/src/proxy/connect-proxy.d.ts +34 -0
  8. package/dist/src/proxy/connect-proxy.js +167 -0
  9. package/dist/src/proxy/tls-interceptor.d.ts +23 -0
  10. package/dist/src/proxy/tls-interceptor.js +211 -0
  11. package/dist/src/proxy/tunnel.d.ts +7 -0
  12. package/dist/src/proxy/tunnel.js +33 -0
  13. package/dist/src/system/installer.d.ts +25 -0
  14. package/dist/src/system/installer.js +180 -0
  15. package/dist/src/system/linux.d.ts +11 -0
  16. package/dist/src/system/linux.js +60 -0
  17. package/dist/src/system/macos.d.ts +24 -0
  18. package/dist/src/system/macos.js +98 -0
  19. package/dist/src/system/watchdog.d.ts +7 -0
  20. package/dist/src/system/watchdog.js +115 -0
  21. package/dist/src/test/connect-proxy.test.d.ts +1 -0
  22. package/dist/src/test/connect-proxy.test.js +147 -0
  23. package/dist/src/tls/ca-manager.d.ts +9 -0
  24. package/dist/src/tls/ca-manager.js +117 -0
  25. package/dist/src/tls/trust-store.d.ts +11 -0
  26. package/dist/src/tls/trust-store.js +121 -0
  27. package/dist/src/tray/bridge.d.ts +8 -0
  28. package/dist/src/tray/bridge.js +66 -0
  29. package/dist/src/ui/ws-feed.d.ts +8 -0
  30. package/dist/src/ui/ws-feed.js +30 -0
  31. package/native/macos/SmartContextTray/Package.swift +13 -0
  32. package/native/macos/SmartContextTray/Sources/main.swift +206 -0
  33. package/package.json +6 -2
  34. package/src/context/ab-test.ts +172 -0
  35. package/src/index.ts +104 -74
  36. package/src/proxy/classifier.ts +71 -0
  37. package/src/proxy/connect-proxy.ts +187 -0
  38. package/src/proxy/tls-interceptor.ts +261 -0
  39. package/src/proxy/tunnel.ts +32 -0
  40. package/src/system/installer.ts +148 -0
  41. package/src/system/linux.ts +57 -0
  42. package/src/system/macos.ts +89 -0
  43. package/src/system/watchdog.ts +76 -0
  44. package/src/test/connect-proxy.test.ts +170 -0
  45. package/src/tls/ca-manager.ts +140 -0
  46. package/src/tls/trust-store.ts +123 -0
  47. package/src/tray/bridge.ts +61 -0
  48. package/src/ui/ws-feed.ts +32 -0
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.interceptTLS = interceptTLS;
7
+ const node_tls_1 = __importDefault(require("node:tls"));
8
+ const node_http_1 = __importDefault(require("node:http"));
9
+ const node_https_1 = __importDefault(require("node:https"));
10
+ const ca_manager_js_1 = require("../tls/ca-manager.js");
11
+ const chunker_js_1 = require("../context/chunker.js");
12
+ const canonical_js_1 = require("../context/canonical.js");
13
+ /**
14
+ * Intercept TLS connection to an LLM provider.
15
+ * Terminates TLS with a generated cert, parses the HTTP request inside,
16
+ * optionally optimizes context, then forwards to the real provider.
17
+ */
18
+ function interceptTLS(clientSocket, hostname, port, match, options) {
19
+ const { cert, key } = (0, ca_manager_js_1.getCertForHost)(hostname);
20
+ const tlsSocket = new node_tls_1.default.TLSSocket(clientSocket, {
21
+ isServer: true,
22
+ cert,
23
+ key,
24
+ });
25
+ // Tell client the tunnel is established
26
+ clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
27
+ // Read HTTP request from decrypted TLS stream
28
+ let requestData = Buffer.alloc(0);
29
+ let headersParsed = false;
30
+ let contentLength = 0;
31
+ let headerEnd = -1;
32
+ tlsSocket.on('data', (chunk) => {
33
+ requestData = Buffer.concat([requestData, chunk]);
34
+ if (!headersParsed) {
35
+ headerEnd = requestData.indexOf('\r\n\r\n');
36
+ if (headerEnd === -1)
37
+ return; // Wait for more data
38
+ headersParsed = true;
39
+ const headersStr = requestData.subarray(0, headerEnd).toString();
40
+ const clMatch = headersStr.match(/content-length:\s*(\d+)/i);
41
+ contentLength = clMatch ? parseInt(clMatch[1], 10) : 0;
42
+ }
43
+ const bodyStart = headerEnd + 4;
44
+ const bodyReceived = requestData.length - bodyStart;
45
+ if (bodyReceived >= contentLength) {
46
+ tlsSocket.pause();
47
+ handleInterceptedRequest(requestData, hostname, port, match, options, tlsSocket).catch((err) => {
48
+ options.log('error', `Intercept error: ${err}`);
49
+ try {
50
+ tlsSocket.write('HTTP/1.1 502 Bad Gateway\r\nContent-Length: 0\r\n\r\n');
51
+ tlsSocket.end();
52
+ }
53
+ catch { }
54
+ });
55
+ }
56
+ });
57
+ tlsSocket.on('error', (err) => {
58
+ options.log('error', `TLS socket error for ${hostname}: ${err.message}`);
59
+ });
60
+ }
61
+ /**
62
+ * Handle an intercepted HTTP request: parse, optimize, forward, stream back.
63
+ */
64
+ async function handleInterceptedRequest(rawRequest, hostname, port, match, options, clientTLS) {
65
+ const startTime = Date.now();
66
+ options.requestCounter.value++;
67
+ const reqId = options.requestCounter.value;
68
+ // Parse raw HTTP request
69
+ const headerEnd = rawRequest.indexOf('\r\n\r\n');
70
+ const headersStr = rawRequest.subarray(0, headerEnd).toString();
71
+ const body = rawRequest.subarray(headerEnd + 4);
72
+ const [requestLine, ...headerLines] = headersStr.split('\r\n');
73
+ const [method, path] = requestLine.split(' ');
74
+ const headers = {};
75
+ for (const line of headerLines) {
76
+ const colonIdx = line.indexOf(':');
77
+ if (colonIdx > 0) {
78
+ headers[line.substring(0, colonIdx).toLowerCase().trim()] = line.substring(colonIdx + 1).trim();
79
+ }
80
+ }
81
+ // Find adapter for this provider
82
+ const adapter = options.adapters.get(match.provider);
83
+ let forwardBody;
84
+ let originalTokens = 0;
85
+ let optimizedTokens = 0;
86
+ let savingsPercent = 0;
87
+ let chunksRetrieved = 0;
88
+ let topScore = 0;
89
+ let passThrough = true;
90
+ let reason;
91
+ let model = 'unknown';
92
+ if (adapter && body.length > 0) {
93
+ try {
94
+ const parsed = JSON.parse(body.toString());
95
+ model = parsed.model || 'unknown';
96
+ const canonical = adapter.parseRequest(parsed, headers);
97
+ originalTokens = (0, chunker_js_1.estimateTokens)(canonical.systemPrompt || '') +
98
+ canonical.messages.reduce((sum, m) => sum + (0, chunker_js_1.estimateTokens)((0, canonical_js_1.getTextContent)(m)), 0);
99
+ // Optimize if available and not paused
100
+ if (options.optimizer && !options.paused) {
101
+ try {
102
+ const result = await options.optimizer.optimize(canonical);
103
+ passThrough = result.passThrough;
104
+ reason = result.reason;
105
+ if (!result.passThrough) {
106
+ canonical.messages = result.optimizedMessages;
107
+ if (result.systemPrompt !== undefined)
108
+ canonical.systemPrompt = result.systemPrompt;
109
+ optimizedTokens = result.packed.optimizedTokens;
110
+ savingsPercent = result.packed.savingsPercent;
111
+ }
112
+ if (result.retrieval) {
113
+ chunksRetrieved = result.retrieval.chunks.length;
114
+ topScore = result.retrieval.topScore;
115
+ }
116
+ }
117
+ catch (err) {
118
+ passThrough = true;
119
+ reason = `optimization error: ${err}`;
120
+ }
121
+ }
122
+ if (!passThrough) {
123
+ forwardBody = Buffer.from(JSON.stringify(adapter.serializeRequest(canonical)));
124
+ }
125
+ else {
126
+ forwardBody = body;
127
+ optimizedTokens = originalTokens;
128
+ }
129
+ }
130
+ catch {
131
+ forwardBody = body;
132
+ optimizedTokens = originalTokens;
133
+ }
134
+ }
135
+ else {
136
+ forwardBody = body;
137
+ }
138
+ const latencyOverhead = Date.now() - startTime;
139
+ const savingsStr = passThrough ? 'pass' : `-${savingsPercent}%`;
140
+ options.log('info', `#${reqId} ${match.provider}/${model} ${originalTokens}→${optimizedTokens} ${savingsStr} ${latencyOverhead}ms [intercepted]`);
141
+ // Forward to real provider
142
+ const useHTTPS = port === 443 || hostname.includes('.');
143
+ const transport = useHTTPS ? node_https_1.default : node_http_1.default;
144
+ const forwardUrl = `${useHTTPS ? 'https' : 'http'}://${hostname}:${port}${path}`;
145
+ const forwardHeaders = { ...headers };
146
+ forwardHeaders['host'] = hostname;
147
+ forwardHeaders['content-length'] = String(forwardBody.length);
148
+ const providerRes = await new Promise((resolve, reject) => {
149
+ const proxyReq = transport.request(forwardUrl, {
150
+ method,
151
+ headers: forwardHeaders,
152
+ }, resolve);
153
+ proxyReq.on('error', reject);
154
+ proxyReq.write(forwardBody);
155
+ proxyReq.end();
156
+ });
157
+ // Build response headers
158
+ const resHeaders = [
159
+ `HTTP/1.1 ${providerRes.statusCode} ${providerRes.statusMessage}`,
160
+ ];
161
+ for (const [key, val] of Object.entries(providerRes.headers)) {
162
+ if (val) {
163
+ const values = Array.isArray(val) ? val : [val];
164
+ for (const v of values) {
165
+ resHeaders.push(`${key}: ${v}`);
166
+ }
167
+ }
168
+ }
169
+ resHeaders.push('', '');
170
+ clientTLS.write(resHeaders.join('\r\n'));
171
+ // Stream response body
172
+ await new Promise((resolve) => {
173
+ providerRes.on('data', (chunk) => {
174
+ clientTLS.write(chunk);
175
+ });
176
+ providerRes.on('end', () => {
177
+ clientTLS.end();
178
+ resolve();
179
+ });
180
+ providerRes.on('error', () => {
181
+ clientTLS.end();
182
+ resolve();
183
+ });
184
+ });
185
+ // Record metrics
186
+ options.metrics.record({
187
+ id: reqId,
188
+ timestamp: Date.now(),
189
+ provider: match.provider,
190
+ model,
191
+ streaming: headers['accept']?.includes('text/event-stream') || false,
192
+ originalTokens,
193
+ optimizedTokens,
194
+ savingsPercent,
195
+ latencyOverheadMs: latencyOverhead,
196
+ chunksRetrieved,
197
+ topScore,
198
+ passThrough,
199
+ reason,
200
+ });
201
+ // Async post-indexing
202
+ if (options.optimizer && !passThrough && adapter) {
203
+ try {
204
+ const parsed = JSON.parse(body.toString());
205
+ const canonical = adapter.parseRequest(parsed, headers);
206
+ options.optimizer.indexExchange(canonical.messages, `intercepted-${reqId}`).catch(() => { });
207
+ }
208
+ catch { }
209
+ }
210
+ }
211
+ //# sourceMappingURL=tls-interceptor.js.map
@@ -0,0 +1,7 @@
1
+ import type { Socket } from 'node:net';
2
+ /**
3
+ * Create a blind TCP tunnel between client and target.
4
+ * Zero overhead — no inspection, no buffering.
5
+ * Used for non-LLM HTTPS traffic.
6
+ */
7
+ export declare function createTunnel(clientSocket: Socket, targetHost: string, targetPort: number): void;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createTunnel = createTunnel;
7
+ const node_net_1 = __importDefault(require("node:net"));
8
+ /**
9
+ * Create a blind TCP tunnel between client and target.
10
+ * Zero overhead — no inspection, no buffering.
11
+ * Used for non-LLM HTTPS traffic.
12
+ */
13
+ function createTunnel(clientSocket, targetHost, targetPort) {
14
+ const targetSocket = node_net_1.default.connect(targetPort, targetHost, () => {
15
+ clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
16
+ targetSocket.pipe(clientSocket);
17
+ clientSocket.pipe(targetSocket);
18
+ });
19
+ targetSocket.on('error', (err) => {
20
+ clientSocket.write(`HTTP/1.1 502 Bad Gateway\r\n\r\n`);
21
+ clientSocket.end();
22
+ });
23
+ clientSocket.on('error', () => {
24
+ targetSocket.destroy();
25
+ });
26
+ clientSocket.on('close', () => {
27
+ targetSocket.destroy();
28
+ });
29
+ targetSocket.on('close', () => {
30
+ clientSocket.destroy();
31
+ });
32
+ }
33
+ //# sourceMappingURL=tunnel.js.map
@@ -0,0 +1,25 @@
1
+ export interface InstallResult {
2
+ success: boolean;
3
+ steps: Array<{
4
+ step: string;
5
+ success: boolean;
6
+ message: string;
7
+ }>;
8
+ }
9
+ /**
10
+ * Full installation:
11
+ * 1. Generate CA cert
12
+ * 2. Install CA in trust store
13
+ * 3. Configure system proxy
14
+ * 4. Install system service
15
+ */
16
+ export declare function install(port: number): InstallResult;
17
+ /**
18
+ * Full uninstallation:
19
+ * 1. Remove system service
20
+ * 2. Clear system proxy
21
+ * 3. Remove CA from trust store
22
+ */
23
+ export declare function uninstall(purge?: boolean): InstallResult;
24
+ /** Check installation status */
25
+ export declare function status(port: number): Record<string, boolean | string>;
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.install = install;
37
+ exports.uninstall = uninstall;
38
+ exports.status = status;
39
+ const ca_manager_js_1 = require("../tls/ca-manager.js");
40
+ const trust_store_js_1 = require("../tls/trust-store.js");
41
+ const service_js_1 = require("../daemon/service.js");
42
+ const macos = __importStar(require("./macos.js"));
43
+ const linux = __importStar(require("./linux.js"));
44
+ const platform = process.platform === 'darwin' ? macos : linux;
45
+ /**
46
+ * Full installation:
47
+ * 1. Generate CA cert
48
+ * 2. Install CA in trust store
49
+ * 3. Configure system proxy
50
+ * 4. Install system service
51
+ */
52
+ function install(port) {
53
+ const steps = [];
54
+ let allOk = true;
55
+ // Step 1: Generate CA
56
+ try {
57
+ (0, ca_manager_js_1.ensureCA)();
58
+ steps.push({ step: 'Generate CA certificate', success: true, message: `CA cert at ${(0, ca_manager_js_1.getCACertPath)()}` });
59
+ }
60
+ catch (err) {
61
+ steps.push({ step: 'Generate CA certificate', success: false, message: String(err) });
62
+ allOk = false;
63
+ }
64
+ // Step 2: Install CA in trust store
65
+ if (allOk) {
66
+ const result = (0, trust_store_js_1.installCA)();
67
+ steps.push({ step: 'Install CA in trust store', success: result.success, message: result.message });
68
+ if (!result.success)
69
+ allOk = false;
70
+ }
71
+ // Step 3: Configure system proxy (PAC URL for selective proxying)
72
+ if (allOk) {
73
+ if (process.platform === 'darwin') {
74
+ const result = macos.setAutoproxyURL(`http://127.0.0.1:${port}/proxy.pac`);
75
+ steps.push({ step: 'Configure system proxy (PAC)', success: result.success, message: result.message });
76
+ if (!result.success)
77
+ allOk = false;
78
+ }
79
+ else {
80
+ const result = platform.setSystemProxy('127.0.0.1', port);
81
+ steps.push({ step: 'Configure system proxy', success: result.success, message: result.message });
82
+ if (!result.success)
83
+ allOk = false;
84
+ }
85
+ }
86
+ // Step 4: Install system service
87
+ if (allOk) {
88
+ try {
89
+ const servicePath = (0, service_js_1.installService)(port);
90
+ steps.push({ step: 'Install system service', success: true, message: `Service at ${servicePath}` });
91
+ }
92
+ catch (err) {
93
+ steps.push({ step: 'Install system service', success: false, message: String(err) });
94
+ // Non-fatal: proxy can still run manually
95
+ }
96
+ }
97
+ // Rollback on failure
98
+ if (!allOk) {
99
+ rollback(steps);
100
+ }
101
+ return { success: allOk, steps };
102
+ }
103
+ /**
104
+ * Full uninstallation:
105
+ * 1. Remove system service
106
+ * 2. Clear system proxy
107
+ * 3. Remove CA from trust store
108
+ */
109
+ function uninstall(purge = false) {
110
+ const steps = [];
111
+ // Step 1: Remove service
112
+ try {
113
+ const msg = (0, service_js_1.uninstallService)();
114
+ steps.push({ step: 'Remove system service', success: true, message: msg });
115
+ }
116
+ catch (err) {
117
+ steps.push({ step: 'Remove system service', success: false, message: String(err) });
118
+ }
119
+ // Step 2: Clear proxy
120
+ if (process.platform === 'darwin') {
121
+ const r1 = macos.clearAutoproxyURL();
122
+ steps.push({ step: 'Clear auto-proxy', success: r1.success, message: r1.message });
123
+ const r2 = macos.clearSystemProxy();
124
+ steps.push({ step: 'Clear system proxy', success: r2.success, message: r2.message });
125
+ }
126
+ else {
127
+ const r = linux.clearSystemProxy();
128
+ steps.push({ step: 'Clear system proxy', success: r.success, message: r.message });
129
+ }
130
+ // Step 3: Remove CA
131
+ const caResult = (0, trust_store_js_1.uninstallCA)();
132
+ steps.push({ step: 'Remove CA from trust store', success: caResult.success, message: caResult.message });
133
+ // Step 4: Purge data (optional)
134
+ if (purge) {
135
+ try {
136
+ const fs = require('node:fs');
137
+ const path = require('node:path');
138
+ const dataDir = path.join(process.env['HOME'] || '.', '.smartcontext');
139
+ fs.rmSync(dataDir, { recursive: true, force: true });
140
+ steps.push({ step: 'Purge data directory', success: true, message: `Removed ${dataDir}` });
141
+ }
142
+ catch (err) {
143
+ steps.push({ step: 'Purge data directory', success: false, message: String(err) });
144
+ }
145
+ }
146
+ const allOk = steps.every((s) => s.success);
147
+ return { success: allOk, steps };
148
+ }
149
+ /** Check installation status */
150
+ function status(port) {
151
+ return {
152
+ caExists: require('node:fs').existsSync((0, ca_manager_js_1.getCACertPath)()),
153
+ caInstalled: (0, trust_store_js_1.isCAInstalled)(),
154
+ proxyConfigured: platform.isProxyConfigured(port),
155
+ };
156
+ }
157
+ /** Rollback completed steps on failure */
158
+ function rollback(completedSteps) {
159
+ for (const step of completedSteps.reverse()) {
160
+ if (!step.success)
161
+ continue;
162
+ try {
163
+ if (step.step.includes('CA in trust store'))
164
+ (0, trust_store_js_1.uninstallCA)();
165
+ if (step.step.includes('system proxy') || step.step.includes('PAC')) {
166
+ if (process.platform === 'darwin') {
167
+ macos.clearAutoproxyURL();
168
+ macos.clearSystemProxy();
169
+ }
170
+ else {
171
+ linux.clearSystemProxy();
172
+ }
173
+ }
174
+ if (step.step.includes('system service'))
175
+ (0, service_js_1.uninstallService)();
176
+ }
177
+ catch { }
178
+ }
179
+ }
180
+ //# sourceMappingURL=installer.js.map
@@ -0,0 +1,11 @@
1
+ /** Configure Linux system proxy */
2
+ export declare function setSystemProxy(host: string, port: number): {
3
+ success: boolean;
4
+ message: string;
5
+ };
6
+ /** Clear Linux system proxy */
7
+ export declare function clearSystemProxy(): {
8
+ success: boolean;
9
+ message: string;
10
+ };
11
+ export declare function isProxyConfigured(port: number): boolean;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setSystemProxy = setSystemProxy;
4
+ exports.clearSystemProxy = clearSystemProxy;
5
+ exports.isProxyConfigured = isProxyConfigured;
6
+ const node_child_process_1 = require("node:child_process");
7
+ /** Configure Linux system proxy */
8
+ function setSystemProxy(host, port) {
9
+ const proxyUrl = `http://${host}:${port}`;
10
+ try {
11
+ // Try GNOME settings
12
+ try {
13
+ (0, node_child_process_1.execFileSync)('gsettings', ['set', 'org.gnome.system.proxy', 'mode', "'manual'"], { stdio: 'pipe' });
14
+ (0, node_child_process_1.execFileSync)('gsettings', ['set', 'org.gnome.system.proxy.http', 'host', `'${host}'`], { stdio: 'pipe' });
15
+ (0, node_child_process_1.execFileSync)('gsettings', ['set', 'org.gnome.system.proxy.http', 'port', String(port)], { stdio: 'pipe' });
16
+ (0, node_child_process_1.execFileSync)('gsettings', ['set', 'org.gnome.system.proxy.https', 'host', `'${host}'`], { stdio: 'pipe' });
17
+ (0, node_child_process_1.execFileSync)('gsettings', ['set', 'org.gnome.system.proxy.https', 'port', String(port)], { stdio: 'pipe' });
18
+ }
19
+ catch { }
20
+ // Write environment file for shell sessions
21
+ const envFile = '/etc/profile.d/smartcontext-proxy.sh';
22
+ const content = `# SmartContext Proxy - auto-generated
23
+ export http_proxy="${proxyUrl}"
24
+ export https_proxy="${proxyUrl}"
25
+ export HTTP_PROXY="${proxyUrl}"
26
+ export HTTPS_PROXY="${proxyUrl}"
27
+ export no_proxy="localhost,127.0.0.1,::1"
28
+ `;
29
+ try {
30
+ (0, node_child_process_1.execFileSync)('sudo', ['tee', envFile], { input: content, stdio: ['pipe', 'pipe', 'pipe'] });
31
+ }
32
+ catch { }
33
+ return { success: true, message: `System proxy set to ${proxyUrl}` };
34
+ }
35
+ catch (err) {
36
+ return { success: false, message: `Failed: ${err}` };
37
+ }
38
+ }
39
+ /** Clear Linux system proxy */
40
+ function clearSystemProxy() {
41
+ try {
42
+ try {
43
+ (0, node_child_process_1.execFileSync)('gsettings', ['set', 'org.gnome.system.proxy', 'mode', "'none'"], { stdio: 'pipe' });
44
+ }
45
+ catch { }
46
+ try {
47
+ (0, node_child_process_1.execFileSync)('sudo', ['rm', '-f', '/etc/profile.d/smartcontext-proxy.sh'], { stdio: 'pipe' });
48
+ }
49
+ catch { }
50
+ return { success: true, message: 'System proxy cleared' };
51
+ }
52
+ catch (err) {
53
+ return { success: false, message: `Failed: ${err}` };
54
+ }
55
+ }
56
+ function isProxyConfigured(port) {
57
+ const proxyEnv = process.env['https_proxy'] || process.env['HTTPS_PROXY'] || '';
58
+ return proxyEnv.includes(String(port));
59
+ }
60
+ //# sourceMappingURL=linux.js.map
@@ -0,0 +1,24 @@
1
+ /** Get active network interface name (e.g., "Wi-Fi", "Ethernet") */
2
+ export declare function getActiveInterface(): string;
3
+ /** Configure macOS system proxy to use SmartContext */
4
+ export declare function setSystemProxy(host: string, port: number): {
5
+ success: boolean;
6
+ message: string;
7
+ };
8
+ /** Remove system proxy configuration */
9
+ export declare function clearSystemProxy(): {
10
+ success: boolean;
11
+ message: string;
12
+ };
13
+ /** Set PAC URL as auto-proxy configuration */
14
+ export declare function setAutoproxyURL(pacUrl: string): {
15
+ success: boolean;
16
+ message: string;
17
+ };
18
+ /** Clear auto-proxy configuration */
19
+ export declare function clearAutoproxyURL(): {
20
+ success: boolean;
21
+ message: string;
22
+ };
23
+ /** Check if system proxy is currently set to SmartContext */
24
+ export declare function isProxyConfigured(port: number): boolean;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getActiveInterface = getActiveInterface;
4
+ exports.setSystemProxy = setSystemProxy;
5
+ exports.clearSystemProxy = clearSystemProxy;
6
+ exports.setAutoproxyURL = setAutoproxyURL;
7
+ exports.clearAutoproxyURL = clearAutoproxyURL;
8
+ exports.isProxyConfigured = isProxyConfigured;
9
+ const node_child_process_1 = require("node:child_process");
10
+ /** Get active network interface name (e.g., "Wi-Fi", "Ethernet") */
11
+ function getActiveInterface() {
12
+ try {
13
+ const route = (0, node_child_process_1.execFileSync)('route', ['-n', 'get', 'default'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
14
+ const ifMatch = route.match(/interface:\s*(\S+)/);
15
+ if (!ifMatch)
16
+ return 'Wi-Fi';
17
+ const ifName = ifMatch[1];
18
+ // Map interface ID to service name
19
+ const services = (0, node_child_process_1.execFileSync)('networksetup', ['-listallhardwareports'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
20
+ const lines = services.split('\n');
21
+ for (let i = 0; i < lines.length; i++) {
22
+ if (lines[i].includes(`Device: ${ifName}`)) {
23
+ const nameLine = lines[i - 1];
24
+ const nameMatch = nameLine?.match(/Hardware Port:\s*(.+)/);
25
+ if (nameMatch)
26
+ return nameMatch[1];
27
+ }
28
+ }
29
+ return 'Wi-Fi';
30
+ }
31
+ catch {
32
+ return 'Wi-Fi';
33
+ }
34
+ }
35
+ /** Configure macOS system proxy to use SmartContext */
36
+ function setSystemProxy(host, port) {
37
+ const iface = getActiveInterface();
38
+ try {
39
+ // Set HTTPS proxy
40
+ (0, node_child_process_1.execFileSync)('networksetup', ['-setsecurewebproxy', iface, host, String(port)], { stdio: 'pipe' });
41
+ // Set HTTP proxy
42
+ (0, node_child_process_1.execFileSync)('networksetup', ['-setwebproxy', iface, host, String(port)], { stdio: 'pipe' });
43
+ // Enable them
44
+ (0, node_child_process_1.execFileSync)('networksetup', ['-setsecurewebproxystate', iface, 'on'], { stdio: 'pipe' });
45
+ (0, node_child_process_1.execFileSync)('networksetup', ['-setwebproxystate', iface, 'on'], { stdio: 'pipe' });
46
+ return { success: true, message: `System proxy set to ${host}:${port} on ${iface}` };
47
+ }
48
+ catch (err) {
49
+ return { success: false, message: `Failed to set proxy on ${iface}: ${err}` };
50
+ }
51
+ }
52
+ /** Remove system proxy configuration */
53
+ function clearSystemProxy() {
54
+ const iface = getActiveInterface();
55
+ try {
56
+ (0, node_child_process_1.execFileSync)('networksetup', ['-setsecurewebproxystate', iface, 'off'], { stdio: 'pipe' });
57
+ (0, node_child_process_1.execFileSync)('networksetup', ['-setwebproxystate', iface, 'off'], { stdio: 'pipe' });
58
+ return { success: true, message: `System proxy cleared on ${iface}` };
59
+ }
60
+ catch (err) {
61
+ return { success: false, message: `Failed to clear proxy on ${iface}: ${err}` };
62
+ }
63
+ }
64
+ /** Set PAC URL as auto-proxy configuration */
65
+ function setAutoproxyURL(pacUrl) {
66
+ const iface = getActiveInterface();
67
+ try {
68
+ (0, node_child_process_1.execFileSync)('networksetup', ['-setautoproxyurl', iface, pacUrl], { stdio: 'pipe' });
69
+ (0, node_child_process_1.execFileSync)('networksetup', ['-setautoproxystate', iface, 'on'], { stdio: 'pipe' });
70
+ return { success: true, message: `Auto-proxy URL set to ${pacUrl} on ${iface}` };
71
+ }
72
+ catch (err) {
73
+ return { success: false, message: `Failed: ${err}` };
74
+ }
75
+ }
76
+ /** Clear auto-proxy configuration */
77
+ function clearAutoproxyURL() {
78
+ const iface = getActiveInterface();
79
+ try {
80
+ (0, node_child_process_1.execFileSync)('networksetup', ['-setautoproxystate', iface, 'off'], { stdio: 'pipe' });
81
+ return { success: true, message: `Auto-proxy cleared on ${iface}` };
82
+ }
83
+ catch (err) {
84
+ return { success: false, message: `Failed: ${err}` };
85
+ }
86
+ }
87
+ /** Check if system proxy is currently set to SmartContext */
88
+ function isProxyConfigured(port) {
89
+ const iface = getActiveInterface();
90
+ try {
91
+ const info = (0, node_child_process_1.execFileSync)('networksetup', ['-getsecurewebproxy', iface], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
92
+ return info.includes(`Port: ${port}`) && info.includes('Enabled: Yes');
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ }
98
+ //# sourceMappingURL=macos.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Start watchdog that monitors proxy health.
3
+ * If proxy becomes unreachable, auto-removes system proxy config
4
+ * to prevent breaking the user's internet.
5
+ */
6
+ export declare function startWatchdog(proxyPort: number): void;
7
+ export declare function stopWatchdog(): void;