oc-inspector 1.0.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 +48 -0
- package/bin/cli.mjs +89 -0
- package/package.json +41 -0
- package/src/config.mjs +284 -0
- package/src/dashboard.mjs +452 -0
- package/src/providers.mjs +186 -0
- package/src/proxy.mjs +356 -0
- package/src/server.mjs +207 -0
- package/src/usage-parsers.mjs +223 -0
- package/src/ws.mjs +143 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# oc-inspector
|
|
2
|
+
|
|
3
|
+
Real-time API traffic inspector for [OpenClaw](https://openclaw.ai). Intercepts LLM provider requests (Anthropic, OpenAI, BytePlus, Ollama, and more), shows token usage, costs, and message flow in a live web dashboard.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx oc-inspector
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This starts the inspector proxy on port 18800 and opens a web dashboard.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **One-click enable/disable** — patches OpenClaw config automatically, no manual editing
|
|
16
|
+
- **All providers** — Anthropic, OpenAI, BytePlus, Ollama, Groq, Mistral, xAI, OpenRouter, and any custom provider
|
|
17
|
+
- **Real-time dashboard** — WebSocket-powered live view of every API request and response
|
|
18
|
+
- **Token tracking** — accurate token counts for streaming (SSE) and non-streaming responses
|
|
19
|
+
- **Cost estimation** — per-request cost based on model pricing
|
|
20
|
+
- **Message inspector** — view system prompts, tool calls, thinking blocks, and full conversation history
|
|
21
|
+
- **Zero dependencies on OpenClaw internals** — works as a standalone reverse proxy
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Start with defaults (port 18800)
|
|
27
|
+
npx oc-inspector
|
|
28
|
+
|
|
29
|
+
# Custom port
|
|
30
|
+
npx oc-inspector --port 9000
|
|
31
|
+
|
|
32
|
+
# Auto-open browser
|
|
33
|
+
npx oc-inspector --open
|
|
34
|
+
|
|
35
|
+
# Custom OpenClaw config path
|
|
36
|
+
npx oc-inspector --config /path/to/openclaw.json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## How It Works
|
|
40
|
+
|
|
41
|
+
1. The inspector starts an HTTP reverse proxy on `localhost:18800`
|
|
42
|
+
2. Click **Enable** in the web UI — it patches `openclaw.json` to route all providers through the proxy and restarts the gateway
|
|
43
|
+
3. All LLM API traffic flows through the inspector, which logs requests/responses and extracts token usage
|
|
44
|
+
4. Click **Disable** to restore the original config
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI entry point for @kshidenko/openclaw-inspector.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx @kshidenko/openclaw-inspector [options]
|
|
8
|
+
*
|
|
9
|
+
* Options:
|
|
10
|
+
* --port <number> Port for the inspector proxy (default: 18800)
|
|
11
|
+
* --open Auto-open the dashboard in a browser
|
|
12
|
+
* --config <path> Custom path to openclaw.json
|
|
13
|
+
* --help Show help message
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { startServer } from "../src/server.mjs";
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
|
|
20
|
+
/** Parse a simple --key value or --flag argument list. */
|
|
21
|
+
function parseArgs(argv) {
|
|
22
|
+
const opts = { port: 18800, open: false, config: undefined, help: false };
|
|
23
|
+
for (let i = 0; i < argv.length; i++) {
|
|
24
|
+
const arg = argv[i];
|
|
25
|
+
if (arg === "--port" && argv[i + 1]) {
|
|
26
|
+
opts.port = parseInt(argv[++i], 10);
|
|
27
|
+
} else if (arg === "--open") {
|
|
28
|
+
opts.open = true;
|
|
29
|
+
} else if (arg === "--config" && argv[i + 1]) {
|
|
30
|
+
opts.config = argv[++i];
|
|
31
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
32
|
+
opts.help = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return opts;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const opts = parseArgs(args);
|
|
39
|
+
|
|
40
|
+
if (opts.help) {
|
|
41
|
+
console.log(`
|
|
42
|
+
oc-inspector — Real-time API traffic inspector for OpenClaw
|
|
43
|
+
|
|
44
|
+
Usage:
|
|
45
|
+
npx oc-inspector [options]
|
|
46
|
+
|
|
47
|
+
Options:
|
|
48
|
+
--port <number> Port for the inspector proxy (default: 18800)
|
|
49
|
+
--open Auto-open the dashboard in a browser
|
|
50
|
+
--config <path> Custom path to openclaw.json
|
|
51
|
+
--help, -h Show this help message
|
|
52
|
+
|
|
53
|
+
Once running, open http://localhost:<port> in your browser.
|
|
54
|
+
Click "Enable" to start intercepting OpenClaw API traffic.
|
|
55
|
+
`);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log("");
|
|
60
|
+
console.log(" \x1b[38;5;208m🦞 OpenClaw Inspector\x1b[0m");
|
|
61
|
+
console.log("");
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const { url, openclawDir } = await startServer({
|
|
65
|
+
port: opts.port,
|
|
66
|
+
configPath: opts.config,
|
|
67
|
+
open: opts.open,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
console.log(` \x1b[32m✓\x1b[0m Dashboard: ${url}`);
|
|
71
|
+
console.log(` \x1b[32m✓\x1b[0m Config: ${openclawDir}/openclaw.json`);
|
|
72
|
+
console.log(` \x1b[32m✓\x1b[0m Proxy port: ${opts.port}`);
|
|
73
|
+
console.log("");
|
|
74
|
+
console.log(" Press \x1b[1mCtrl+C\x1b[0m to stop");
|
|
75
|
+
console.log("");
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error(`\x1b[31m ✗ Failed to start: ${err.message}\x1b[0m`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Graceful shutdown
|
|
82
|
+
process.on("SIGINT", () => {
|
|
83
|
+
console.log("\n Shutting down...");
|
|
84
|
+
process.exit(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
process.on("SIGTERM", () => {
|
|
88
|
+
process.exit(0);
|
|
89
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "oc-inspector",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Real-time API traffic inspector for OpenClaw — intercepts LLM provider requests, shows token usage, costs, and message flow in a live web dashboard.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"oc-inspector": "./bin/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"main": "./src/server.mjs",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin/",
|
|
15
|
+
"src/",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"start": "node bin/cli.mjs",
|
|
20
|
+
"dev": "node bin/cli.mjs --open"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"openclaw",
|
|
24
|
+
"llm",
|
|
25
|
+
"inspector",
|
|
26
|
+
"proxy",
|
|
27
|
+
"anthropic",
|
|
28
|
+
"openai",
|
|
29
|
+
"token-usage",
|
|
30
|
+
"api-monitor"
|
|
31
|
+
],
|
|
32
|
+
"author": "kshidenko",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/kshidenko/openclaw-inspector"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"ws": "^8.18.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/config.mjs
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw config manager for the Inspector.
|
|
3
|
+
*
|
|
4
|
+
* Handles detection of the OpenClaw installation, reading/patching/restoring
|
|
5
|
+
* openclaw.json to route provider traffic through the inspector proxy,
|
|
6
|
+
* and restarting the gateway.
|
|
7
|
+
*
|
|
8
|
+
* @module config
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, copyFileSync, unlinkSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { execSync } from "node:child_process";
|
|
15
|
+
import { BUILTIN_URLS, detectActiveProviders } from "./providers.mjs";
|
|
16
|
+
|
|
17
|
+
/** Default OpenClaw state directory. */
|
|
18
|
+
const DEFAULT_OPENCLAW_DIR = join(homedir(), ".openclaw");
|
|
19
|
+
|
|
20
|
+
/** Filename for the main config. */
|
|
21
|
+
const CONFIG_FILENAME = "openclaw.json";
|
|
22
|
+
|
|
23
|
+
/** Filename for the inspector state (stores original URLs for restore). */
|
|
24
|
+
const STATE_FILENAME = ".inspector-state.json";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Detect the OpenClaw directory and config file path.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} [customPath] - Optional explicit path to openclaw.json.
|
|
30
|
+
* @returns {{ dir: string, configPath: string, exists: boolean }}
|
|
31
|
+
*
|
|
32
|
+
* Example:
|
|
33
|
+
* >>> detect()
|
|
34
|
+
* { dir: "/Users/me/.openclaw", configPath: "/Users/me/.openclaw/openclaw.json", exists: true }
|
|
35
|
+
*/
|
|
36
|
+
export function detect(customPath) {
|
|
37
|
+
if (customPath) {
|
|
38
|
+
const dir = customPath.endsWith(CONFIG_FILENAME)
|
|
39
|
+
? customPath.slice(0, -CONFIG_FILENAME.length - 1)
|
|
40
|
+
: customPath;
|
|
41
|
+
const configPath = customPath.endsWith(".json") ? customPath : join(customPath, CONFIG_FILENAME);
|
|
42
|
+
return { dir, configPath, exists: existsSync(configPath) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check OPENCLAW_STATE_DIR env, then default
|
|
46
|
+
const stateDir = process.env.OPENCLAW_STATE_DIR || DEFAULT_OPENCLAW_DIR;
|
|
47
|
+
const configPath = join(stateDir, CONFIG_FILENAME);
|
|
48
|
+
return { dir: stateDir, configPath, exists: existsSync(configPath) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Read and parse openclaw.json.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} configPath - Full path to openclaw.json.
|
|
55
|
+
* @returns {object} Parsed config object.
|
|
56
|
+
* @throws {Error} If file cannot be read or parsed.
|
|
57
|
+
*/
|
|
58
|
+
export function readConfig(configPath) {
|
|
59
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
60
|
+
return JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Write config object back to openclaw.json.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} configPath - Full path to openclaw.json.
|
|
67
|
+
* @param {object} config - Config object to serialize.
|
|
68
|
+
*/
|
|
69
|
+
function writeConfig(configPath, config) {
|
|
70
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Read the inspector state file (stores original provider URLs).
|
|
75
|
+
*
|
|
76
|
+
* @param {string} openclawDir - Path to ~/.openclaw.
|
|
77
|
+
* @returns {object|null} State object or null if not found.
|
|
78
|
+
*/
|
|
79
|
+
function readState(openclawDir) {
|
|
80
|
+
const statePath = join(openclawDir, STATE_FILENAME);
|
|
81
|
+
if (!existsSync(statePath)) return null;
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(readFileSync(statePath, "utf-8"));
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Write the inspector state file.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} openclawDir - Path to ~/.openclaw.
|
|
93
|
+
* @param {object} state - State object to persist.
|
|
94
|
+
*/
|
|
95
|
+
function writeState(openclawDir, state) {
|
|
96
|
+
const statePath = join(openclawDir, STATE_FILENAME);
|
|
97
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Remove the inspector state file.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} openclawDir - Path to ~/.openclaw.
|
|
104
|
+
*/
|
|
105
|
+
function removeState(openclawDir) {
|
|
106
|
+
const statePath = join(openclawDir, STATE_FILENAME);
|
|
107
|
+
if (existsSync(statePath)) {
|
|
108
|
+
unlinkSync(statePath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Restart the OpenClaw gateway via CLI.
|
|
114
|
+
*
|
|
115
|
+
* @returns {{ ok: boolean, output: string }}
|
|
116
|
+
*/
|
|
117
|
+
export function restartGateway() {
|
|
118
|
+
try {
|
|
119
|
+
const output = execSync("openclaw gateway restart", {
|
|
120
|
+
encoding: "utf-8",
|
|
121
|
+
timeout: 15_000,
|
|
122
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
123
|
+
});
|
|
124
|
+
return { ok: true, output };
|
|
125
|
+
} catch (err) {
|
|
126
|
+
return { ok: false, output: err.stderr || err.message };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Enable interception: patch openclaw.json to route all providers through
|
|
132
|
+
* the inspector proxy.
|
|
133
|
+
*
|
|
134
|
+
* Steps:
|
|
135
|
+
* 1. Create backup of openclaw.json
|
|
136
|
+
* 2. Detect active providers (from auth-profiles + config)
|
|
137
|
+
* 3. Save original URLs to .inspector-state.json
|
|
138
|
+
* 4. Patch config: set baseUrl to proxy for each provider
|
|
139
|
+
* 5. Restart gateway
|
|
140
|
+
*
|
|
141
|
+
* @param {object} params
|
|
142
|
+
* @param {string} params.configPath - Path to openclaw.json.
|
|
143
|
+
* @param {string} params.openclawDir - Path to ~/.openclaw.
|
|
144
|
+
* @param {number} params.port - Inspector proxy port.
|
|
145
|
+
* @returns {{ ok: boolean, message: string, providers: string[] }}
|
|
146
|
+
*
|
|
147
|
+
* Example:
|
|
148
|
+
* >>> enable({ configPath: "~/.openclaw/openclaw.json", openclawDir: "~/.openclaw", port: 18800 })
|
|
149
|
+
* { ok: true, message: "Enabled 3 providers", providers: ["anthropic", "byteplus", "ollama"] }
|
|
150
|
+
*/
|
|
151
|
+
export function enable({ configPath, openclawDir, port }) {
|
|
152
|
+
const proxyBase = `http://127.0.0.1:${port}`;
|
|
153
|
+
|
|
154
|
+
// 1. Backup
|
|
155
|
+
const backupPath = configPath + ".inspector-backup";
|
|
156
|
+
copyFileSync(configPath, backupPath);
|
|
157
|
+
|
|
158
|
+
// 2. Read config
|
|
159
|
+
const config = readConfig(configPath);
|
|
160
|
+
if (!config.models) config.models = {};
|
|
161
|
+
if (!config.models.providers) config.models.providers = {};
|
|
162
|
+
|
|
163
|
+
const providers = config.models.providers;
|
|
164
|
+
const originals = {}; // provider -> original baseUrl or null (for built-in)
|
|
165
|
+
const enabled = [];
|
|
166
|
+
|
|
167
|
+
// 3. Patch custom providers (already have baseUrl in config)
|
|
168
|
+
for (const [name, cfg] of Object.entries(providers)) {
|
|
169
|
+
if (cfg.baseUrl && !cfg.baseUrl.startsWith(proxyBase)) {
|
|
170
|
+
originals[name] = { baseUrl: cfg.baseUrl, wasCustom: true };
|
|
171
|
+
cfg.baseUrl = `${proxyBase}/${name}`;
|
|
172
|
+
enabled.push(name);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 4. Add built-in providers that have auth but no config entry yet
|
|
177
|
+
const active = detectActiveProviders(openclawDir);
|
|
178
|
+
for (const name of active) {
|
|
179
|
+
if (providers[name]) continue; // Already patched above
|
|
180
|
+
if (!BUILTIN_URLS[name]) continue; // Unknown provider
|
|
181
|
+
|
|
182
|
+
originals[name] = { baseUrl: BUILTIN_URLS[name], wasCustom: false };
|
|
183
|
+
providers[name] = {
|
|
184
|
+
baseUrl: `${proxyBase}/${name}`,
|
|
185
|
+
models: [],
|
|
186
|
+
};
|
|
187
|
+
enabled.push(name);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 5. Save state for restore
|
|
191
|
+
writeState(openclawDir, {
|
|
192
|
+
enabled: true,
|
|
193
|
+
port,
|
|
194
|
+
timestamp: new Date().toISOString(),
|
|
195
|
+
originals,
|
|
196
|
+
backupPath,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// 6. Write patched config
|
|
200
|
+
writeConfig(configPath, config);
|
|
201
|
+
|
|
202
|
+
// 7. Restart gateway
|
|
203
|
+
const restart = restartGateway();
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
ok: restart.ok,
|
|
207
|
+
message: restart.ok
|
|
208
|
+
? `Enabled ${enabled.length} providers`
|
|
209
|
+
: `Config patched but gateway restart failed: ${restart.output}`,
|
|
210
|
+
providers: enabled,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Disable interception: restore openclaw.json to original state.
|
|
216
|
+
*
|
|
217
|
+
* @param {object} params
|
|
218
|
+
* @param {string} params.configPath - Path to openclaw.json.
|
|
219
|
+
* @param {string} params.openclawDir - Path to ~/.openclaw.
|
|
220
|
+
* @returns {{ ok: boolean, message: string }}
|
|
221
|
+
*/
|
|
222
|
+
export function disable({ configPath, openclawDir }) {
|
|
223
|
+
const state = readState(openclawDir);
|
|
224
|
+
|
|
225
|
+
if (!state || !state.originals) {
|
|
226
|
+
// Try restoring from backup
|
|
227
|
+
const backupPath = configPath + ".inspector-backup";
|
|
228
|
+
if (existsSync(backupPath)) {
|
|
229
|
+
copyFileSync(backupPath, configPath);
|
|
230
|
+
const restart = restartGateway();
|
|
231
|
+
return {
|
|
232
|
+
ok: restart.ok,
|
|
233
|
+
message: restart.ok ? "Restored from backup" : `Restored config but gateway restart failed`,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return { ok: false, message: "No inspector state found — nothing to restore" };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Read current config and restore originals
|
|
240
|
+
const config = readConfig(configPath);
|
|
241
|
+
const providers = config.models?.providers || {};
|
|
242
|
+
|
|
243
|
+
for (const [name, orig] of Object.entries(state.originals)) {
|
|
244
|
+
if (orig.wasCustom) {
|
|
245
|
+
// Restore original baseUrl
|
|
246
|
+
if (providers[name]) {
|
|
247
|
+
providers[name].baseUrl = orig.baseUrl;
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
// Remove the entry we added for built-in providers
|
|
251
|
+
delete providers[name];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
writeConfig(configPath, config);
|
|
256
|
+
|
|
257
|
+
// Clean up state file
|
|
258
|
+
removeState(openclawDir);
|
|
259
|
+
|
|
260
|
+
const restart = restartGateway();
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
ok: restart.ok,
|
|
264
|
+
message: restart.ok ? "Disabled — config restored" : "Config restored but gateway restart failed",
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Check current interception status.
|
|
270
|
+
*
|
|
271
|
+
* @param {string} openclawDir - Path to ~/.openclaw.
|
|
272
|
+
* @returns {{ enabled: boolean, providers: string[], port: number|null }}
|
|
273
|
+
*/
|
|
274
|
+
export function status(openclawDir) {
|
|
275
|
+
const state = readState(openclawDir);
|
|
276
|
+
if (!state || !state.enabled) {
|
|
277
|
+
return { enabled: false, providers: [], port: null };
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
enabled: true,
|
|
281
|
+
providers: Object.keys(state.originals || {}),
|
|
282
|
+
port: state.port || null,
|
|
283
|
+
};
|
|
284
|
+
}
|