@pixelbyte-software/pixcode 1.33.10 → 1.33.11

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 (41) hide show
  1. package/dist/api-docs.html +395 -395
  2. package/dist/assets/{index-B_dU5AHA.js → index-oLYHJ2X5.js} +134 -134
  3. package/dist/favicon.svg +8 -8
  4. package/dist/icons/icon-128x128.svg +9 -9
  5. package/dist/icons/icon-144x144.svg +9 -9
  6. package/dist/icons/icon-152x152.svg +9 -9
  7. package/dist/icons/icon-192x192.svg +9 -9
  8. package/dist/icons/icon-384x384.svg +9 -9
  9. package/dist/icons/icon-512x512.svg +9 -9
  10. package/dist/icons/icon-72x72.svg +9 -9
  11. package/dist/icons/icon-96x96.svg +9 -9
  12. package/dist/icons/icon-template.svg +9 -9
  13. package/dist/index.html +1 -1
  14. package/dist/logo.svg +12 -12
  15. package/dist/openapi.yaml +1311 -1311
  16. package/dist-server/server/opencode-cli.js +4 -1
  17. package/dist-server/server/opencode-cli.js.map +1 -1
  18. package/package.json +178 -178
  19. package/server/database/db.js +794 -794
  20. package/server/database/json-store.js +194 -194
  21. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +130 -130
  22. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
  23. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +232 -232
  24. package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
  25. package/server/modules/providers/list/qwen/qwen-auth.provider.ts +145 -145
  26. package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
  27. package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
  28. package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
  29. package/server/modules/providers/shared/provider-configs.ts +142 -142
  30. package/server/opencode-cli.js +4 -1
  31. package/server/opencode-response-handler.js +107 -107
  32. package/server/qwen-code-cli.js +395 -395
  33. package/server/qwen-response-handler.js +73 -73
  34. package/server/routes/qwen.js +27 -27
  35. package/server/services/external-access.js +171 -171
  36. package/server/services/provider-credentials.js +189 -189
  37. package/server/services/provider-models.js +381 -381
  38. package/server/services/telegram/telegram-http-client.js +130 -130
  39. package/server/services/vapid-keys.js +36 -36
  40. package/server/utils/port-access.js +209 -209
  41. package/scripts/rest-sweep.mjs +0 -93
@@ -1,209 +1,209 @@
1
- import os from 'os';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import readline from 'readline';
5
- import { execSync } from 'child_process';
6
-
7
- import { c } from './colors.js';
8
-
9
- /**
10
- * Tracks inbound port access decisions across runs so the server doesn't
11
- * re-prompt the user (on Windows/macOS) every start, and can skip work on
12
- * Linux where a rule has already been applied.
13
- */
14
- const STATE_FILE = path.join(os.homedir(), '.pixcode', 'port-access.json');
15
-
16
- function loadState() {
17
- try {
18
- return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
19
- } catch {
20
- return {};
21
- }
22
- }
23
-
24
- function saveState(state) {
25
- try {
26
- fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
27
- fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
28
- } catch {
29
- // Persisting the decision is a nice-to-have; failure shouldn't block start.
30
- }
31
- }
32
-
33
- /** Return all non-loopback IPv4 addresses assigned to this host. */
34
- export function getLanIps() {
35
- const ifs = os.networkInterfaces();
36
- const ips = [];
37
- for (const name of Object.keys(ifs)) {
38
- for (const ni of ifs[name] || []) {
39
- if (ni.family === 'IPv4' && !ni.internal) ips.push(ni.address);
40
- }
41
- }
42
- return ips;
43
- }
44
-
45
- function askYN(question) {
46
- return new Promise((resolve) => {
47
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
48
- resolve(null); // headless / daemon context — skip
49
- return;
50
- }
51
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
52
- rl.question(question, (answer) => {
53
- rl.close();
54
- const normalized = answer.trim().toLowerCase();
55
- resolve(['y', 'yes', 'e', 'evet'].includes(normalized));
56
- });
57
- });
58
- }
59
-
60
- // ---------------- Linux ----------------
61
-
62
- function tryUfw(port) {
63
- try {
64
- execSync('command -v ufw', { stdio: 'pipe' });
65
- const status = execSync('ufw status', { stdio: 'pipe', encoding: 'utf8' });
66
- // UFW inactive = no rule needed; the port is already reachable.
67
- if (!/active/i.test(status)) return 'inactive';
68
- execSync(`sudo -n ufw allow ${port}/tcp`, { stdio: 'pipe' });
69
- return 'applied';
70
- } catch {
71
- return null;
72
- }
73
- }
74
-
75
- function tryFirewalld(port) {
76
- try {
77
- execSync('command -v firewall-cmd', { stdio: 'pipe' });
78
- const state = execSync('firewall-cmd --state', { stdio: 'pipe', encoding: 'utf8' });
79
- if (!/running/i.test(state)) return 'inactive';
80
- execSync(`sudo -n firewall-cmd --add-port=${port}/tcp --permanent`, { stdio: 'pipe' });
81
- execSync('sudo -n firewall-cmd --reload', { stdio: 'pipe' });
82
- return 'applied';
83
- } catch {
84
- return null;
85
- }
86
- }
87
-
88
- // ---------------- Windows ----------------
89
-
90
- function applyWindowsRule(port) {
91
- const ruleName = `Pixcode-${port}`;
92
- const cmd = `netsh advfirewall firewall add rule name="${ruleName}" dir=in action=allow protocol=TCP localport=${port}`;
93
- try {
94
- execSync(cmd, { stdio: 'pipe' });
95
- return { ok: true, cmd, ruleName };
96
- } catch (error) {
97
- return { ok: false, cmd, ruleName, error: error.message };
98
- }
99
- }
100
-
101
- // ---------------- macOS ----------------
102
-
103
- function macFirewallHint(port) {
104
- return [
105
- `${c.tip('[TIP]')} macOS Application Firewall is per-app, not per-port.`,
106
- ' If other devices can\'t connect, open:',
107
- ' System Settings > Network > Firewall > Options',
108
- ` and add Node.js to "Allow incoming connections" (port ${port}).`,
109
- ].join('\n');
110
- }
111
-
112
- // ---------------- Main entry ----------------
113
-
114
- /**
115
- * Make sure inbound traffic can reach `port` from the local network.
116
- *
117
- * - Linux: silently try ufw/firewalld with `sudo -n` (passwordless). Skips
118
- * cleanly when no firewall is running or sudo is gated — LAN access
119
- * already works on most desktop distros.
120
- * - Windows/macOS: ask the user once; remember the decision in
121
- * ~/.pixcode/port-access.json so subsequent starts stay quiet.
122
- *
123
- * All failure paths are non-fatal: the server is already listening when
124
- * we get here, and LAN clients on the same subnet often can reach it
125
- * without any firewall change.
126
- */
127
- export async function ensurePortOpen(port) {
128
- const state = loadState();
129
- const key = `port:${port}`;
130
- const entry = state[key];
131
-
132
- const lanIps = getLanIps();
133
- if (lanIps.length) {
134
- console.log(`${c.info('[INFO]')} LAN access: ${c.bright(`http://${lanIps[0]}:${port}`)}`);
135
- if (lanIps.length > 1) {
136
- for (const extra of lanIps.slice(1)) {
137
- console.log(`${c.dim(' also:')} http://${extra}:${port}`);
138
- }
139
- }
140
- }
141
-
142
- if (entry && entry.decision === 'deny') {
143
- console.log(`${c.dim('[INFO]')} Firewall prompt suppressed (previously declined). Re-enable via ~/.pixcode/port-access.json`);
144
- return;
145
- }
146
-
147
- const platform = process.platform;
148
-
149
- if (platform === 'linux') {
150
- if (entry && entry.status === 'applied') return;
151
- const result = tryUfw(port) || tryFirewalld(port);
152
- if (result === 'applied') {
153
- console.log(`${c.ok('[OK]')} Inbound firewall rule added for port ${port}.`);
154
- saveState({ ...state, [key]: { decision: 'allow', status: 'applied', via: 'linux' } });
155
- } else if (result === 'inactive') {
156
- console.log(`${c.dim('[INFO]')} No active firewall detected — port ${port} should be reachable.`);
157
- } else {
158
- console.log(`${c.tip('[TIP]')} Couldn't auto-open port (no sudo / unsupported firewall).`);
159
- console.log(` Manual: ${c.bright(`sudo ufw allow ${port}/tcp`)} ${c.dim('(or firewall-cmd equivalent)')}`);
160
- }
161
- return;
162
- }
163
-
164
- if (platform === 'win32') {
165
- if (entry && entry.status === 'applied') return;
166
- const approved = await askYN(
167
- `${c.tip('[?]')} Open port ${port} in Windows Firewall so other devices on your network can connect? [y/N] `,
168
- );
169
- if (approved === null) {
170
- // No TTY — just show the manual command and move on.
171
- console.log(
172
- `${c.tip('[TIP]')} To allow LAN access, run this in an elevated PowerShell:\n ${c.bright(
173
- `netsh advfirewall firewall add rule name="Pixcode-${port}" dir=in action=allow protocol=TCP localport=${port}`,
174
- )}`,
175
- );
176
- return;
177
- }
178
- if (!approved) {
179
- console.log(`${c.dim('[INFO]')} Skipping firewall change.`);
180
- saveState({ ...state, [key]: { decision: 'deny' } });
181
- return;
182
- }
183
- const result = applyWindowsRule(port);
184
- if (result.ok) {
185
- console.log(`${c.ok('[OK]')} Firewall rule "${result.ruleName}" added.`);
186
- saveState({ ...state, [key]: { decision: 'allow', status: 'applied', via: 'windows' } });
187
- } else {
188
- console.log(`${c.tip('[TIP]')} Adding the rule needs Administrator. Run this in an elevated PowerShell:`);
189
- console.log(` ${c.bright(result.cmd)}`);
190
- saveState({ ...state, [key]: { decision: 'allow', status: 'manual' } });
191
- }
192
- return;
193
- }
194
-
195
- if (platform === 'darwin') {
196
- if (entry && entry.status) return;
197
- const approved = await askYN(
198
- `${c.tip('[?]')} Allow inbound connections on port ${port} through macOS firewall? [y/N] `,
199
- );
200
- if (approved === null) return;
201
- if (!approved) {
202
- console.log(`${c.dim('[INFO]')} Skipping firewall hint.`);
203
- saveState({ ...state, [key]: { decision: 'deny' } });
204
- return;
205
- }
206
- console.log(macFirewallHint(port));
207
- saveState({ ...state, [key]: { decision: 'allow', status: 'manual', via: 'macos' } });
208
- }
209
- }
1
+ import os from 'os';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import readline from 'readline';
5
+ import { execSync } from 'child_process';
6
+
7
+ import { c } from './colors.js';
8
+
9
+ /**
10
+ * Tracks inbound port access decisions across runs so the server doesn't
11
+ * re-prompt the user (on Windows/macOS) every start, and can skip work on
12
+ * Linux where a rule has already been applied.
13
+ */
14
+ const STATE_FILE = path.join(os.homedir(), '.pixcode', 'port-access.json');
15
+
16
+ function loadState() {
17
+ try {
18
+ return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
19
+ } catch {
20
+ return {};
21
+ }
22
+ }
23
+
24
+ function saveState(state) {
25
+ try {
26
+ fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
27
+ fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
28
+ } catch {
29
+ // Persisting the decision is a nice-to-have; failure shouldn't block start.
30
+ }
31
+ }
32
+
33
+ /** Return all non-loopback IPv4 addresses assigned to this host. */
34
+ export function getLanIps() {
35
+ const ifs = os.networkInterfaces();
36
+ const ips = [];
37
+ for (const name of Object.keys(ifs)) {
38
+ for (const ni of ifs[name] || []) {
39
+ if (ni.family === 'IPv4' && !ni.internal) ips.push(ni.address);
40
+ }
41
+ }
42
+ return ips;
43
+ }
44
+
45
+ function askYN(question) {
46
+ return new Promise((resolve) => {
47
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
48
+ resolve(null); // headless / daemon context — skip
49
+ return;
50
+ }
51
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
52
+ rl.question(question, (answer) => {
53
+ rl.close();
54
+ const normalized = answer.trim().toLowerCase();
55
+ resolve(['y', 'yes', 'e', 'evet'].includes(normalized));
56
+ });
57
+ });
58
+ }
59
+
60
+ // ---------------- Linux ----------------
61
+
62
+ function tryUfw(port) {
63
+ try {
64
+ execSync('command -v ufw', { stdio: 'pipe' });
65
+ const status = execSync('ufw status', { stdio: 'pipe', encoding: 'utf8' });
66
+ // UFW inactive = no rule needed; the port is already reachable.
67
+ if (!/active/i.test(status)) return 'inactive';
68
+ execSync(`sudo -n ufw allow ${port}/tcp`, { stdio: 'pipe' });
69
+ return 'applied';
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ function tryFirewalld(port) {
76
+ try {
77
+ execSync('command -v firewall-cmd', { stdio: 'pipe' });
78
+ const state = execSync('firewall-cmd --state', { stdio: 'pipe', encoding: 'utf8' });
79
+ if (!/running/i.test(state)) return 'inactive';
80
+ execSync(`sudo -n firewall-cmd --add-port=${port}/tcp --permanent`, { stdio: 'pipe' });
81
+ execSync('sudo -n firewall-cmd --reload', { stdio: 'pipe' });
82
+ return 'applied';
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
88
+ // ---------------- Windows ----------------
89
+
90
+ function applyWindowsRule(port) {
91
+ const ruleName = `Pixcode-${port}`;
92
+ const cmd = `netsh advfirewall firewall add rule name="${ruleName}" dir=in action=allow protocol=TCP localport=${port}`;
93
+ try {
94
+ execSync(cmd, { stdio: 'pipe' });
95
+ return { ok: true, cmd, ruleName };
96
+ } catch (error) {
97
+ return { ok: false, cmd, ruleName, error: error.message };
98
+ }
99
+ }
100
+
101
+ // ---------------- macOS ----------------
102
+
103
+ function macFirewallHint(port) {
104
+ return [
105
+ `${c.tip('[TIP]')} macOS Application Firewall is per-app, not per-port.`,
106
+ ' If other devices can\'t connect, open:',
107
+ ' System Settings > Network > Firewall > Options',
108
+ ` and add Node.js to "Allow incoming connections" (port ${port}).`,
109
+ ].join('\n');
110
+ }
111
+
112
+ // ---------------- Main entry ----------------
113
+
114
+ /**
115
+ * Make sure inbound traffic can reach `port` from the local network.
116
+ *
117
+ * - Linux: silently try ufw/firewalld with `sudo -n` (passwordless). Skips
118
+ * cleanly when no firewall is running or sudo is gated — LAN access
119
+ * already works on most desktop distros.
120
+ * - Windows/macOS: ask the user once; remember the decision in
121
+ * ~/.pixcode/port-access.json so subsequent starts stay quiet.
122
+ *
123
+ * All failure paths are non-fatal: the server is already listening when
124
+ * we get here, and LAN clients on the same subnet often can reach it
125
+ * without any firewall change.
126
+ */
127
+ export async function ensurePortOpen(port) {
128
+ const state = loadState();
129
+ const key = `port:${port}`;
130
+ const entry = state[key];
131
+
132
+ const lanIps = getLanIps();
133
+ if (lanIps.length) {
134
+ console.log(`${c.info('[INFO]')} LAN access: ${c.bright(`http://${lanIps[0]}:${port}`)}`);
135
+ if (lanIps.length > 1) {
136
+ for (const extra of lanIps.slice(1)) {
137
+ console.log(`${c.dim(' also:')} http://${extra}:${port}`);
138
+ }
139
+ }
140
+ }
141
+
142
+ if (entry && entry.decision === 'deny') {
143
+ console.log(`${c.dim('[INFO]')} Firewall prompt suppressed (previously declined). Re-enable via ~/.pixcode/port-access.json`);
144
+ return;
145
+ }
146
+
147
+ const platform = process.platform;
148
+
149
+ if (platform === 'linux') {
150
+ if (entry && entry.status === 'applied') return;
151
+ const result = tryUfw(port) || tryFirewalld(port);
152
+ if (result === 'applied') {
153
+ console.log(`${c.ok('[OK]')} Inbound firewall rule added for port ${port}.`);
154
+ saveState({ ...state, [key]: { decision: 'allow', status: 'applied', via: 'linux' } });
155
+ } else if (result === 'inactive') {
156
+ console.log(`${c.dim('[INFO]')} No active firewall detected — port ${port} should be reachable.`);
157
+ } else {
158
+ console.log(`${c.tip('[TIP]')} Couldn't auto-open port (no sudo / unsupported firewall).`);
159
+ console.log(` Manual: ${c.bright(`sudo ufw allow ${port}/tcp`)} ${c.dim('(or firewall-cmd equivalent)')}`);
160
+ }
161
+ return;
162
+ }
163
+
164
+ if (platform === 'win32') {
165
+ if (entry && entry.status === 'applied') return;
166
+ const approved = await askYN(
167
+ `${c.tip('[?]')} Open port ${port} in Windows Firewall so other devices on your network can connect? [y/N] `,
168
+ );
169
+ if (approved === null) {
170
+ // No TTY — just show the manual command and move on.
171
+ console.log(
172
+ `${c.tip('[TIP]')} To allow LAN access, run this in an elevated PowerShell:\n ${c.bright(
173
+ `netsh advfirewall firewall add rule name="Pixcode-${port}" dir=in action=allow protocol=TCP localport=${port}`,
174
+ )}`,
175
+ );
176
+ return;
177
+ }
178
+ if (!approved) {
179
+ console.log(`${c.dim('[INFO]')} Skipping firewall change.`);
180
+ saveState({ ...state, [key]: { decision: 'deny' } });
181
+ return;
182
+ }
183
+ const result = applyWindowsRule(port);
184
+ if (result.ok) {
185
+ console.log(`${c.ok('[OK]')} Firewall rule "${result.ruleName}" added.`);
186
+ saveState({ ...state, [key]: { decision: 'allow', status: 'applied', via: 'windows' } });
187
+ } else {
188
+ console.log(`${c.tip('[TIP]')} Adding the rule needs Administrator. Run this in an elevated PowerShell:`);
189
+ console.log(` ${c.bright(result.cmd)}`);
190
+ saveState({ ...state, [key]: { decision: 'allow', status: 'manual' } });
191
+ }
192
+ return;
193
+ }
194
+
195
+ if (platform === 'darwin') {
196
+ if (entry && entry.status) return;
197
+ const approved = await askYN(
198
+ `${c.tip('[?]')} Allow inbound connections on port ${port} through macOS firewall? [y/N] `,
199
+ );
200
+ if (approved === null) return;
201
+ if (!approved) {
202
+ console.log(`${c.dim('[INFO]')} Skipping firewall hint.`);
203
+ saveState({ ...state, [key]: { decision: 'deny' } });
204
+ return;
205
+ }
206
+ console.log(macFirewallHint(port));
207
+ saveState({ ...state, [key]: { decision: 'allow', status: 'manual', via: 'macos' } });
208
+ }
209
+ }
@@ -1,93 +0,0 @@
1
- // REST sweep - verifies every spec endpoint actually exists
2
- import fs from 'node:fs';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import YAML from 'yaml';
6
-
7
- const KEY = (fs.readFileSync('.env','utf8').match(/^PIXCODE_TEST_API_KEY=(\S+)/m) || [])[1];
8
- if (!KEY) { console.error('no key'); process.exit(1); }
9
-
10
- const BASE = 'http://localhost:3001';
11
- const PROJECT = 'c--Users-ALICOMERT-Documents-PROJELER-airforce';
12
- const SESSION = '5c89f06c-352a-4c86-ae1a-258e2bfcb321';
13
-
14
- const subs = {
15
- '{provider}': 'claude',
16
- '{projectName}': PROJECT,
17
- '{sessionId}': SESSION,
18
- '{keyId}': '999999',
19
- '{jobId}': 'nonexistent',
20
- '{name}': 'test-mcp',
21
- '{fileId}': 'auth.json',
22
- };
23
-
24
- const spec = YAML.parse(fs.readFileSync('public/openapi.yaml','utf8'));
25
- const endpoints = [];
26
- for (const [p, ops] of Object.entries(spec.paths)) {
27
- for (const [m, op] of Object.entries(ops)) {
28
- if (!['get','post','put','patch','delete'].includes(m)) continue;
29
- const isPublic = op.security && op.security.length === 0;
30
- endpoints.push({ method: m.toUpperCase(), path: p, isPublic });
31
- }
32
- }
33
-
34
- // SKIP destructive endpoints — we know these exist (they killed the server last run)
35
- // We'll mark them as EXISTS based on observed prior 200 response.
36
- const SKIP = new Set([
37
- 'POST /api/system/update',
38
- 'POST /api/system/restart',
39
- 'POST /api/auth/logout', // would invalidate test key potentially
40
- ]);
41
-
42
- function resolvePath(p) {
43
- let out = p;
44
- for (const [k,v] of Object.entries(subs)) out = out.replaceAll(k, v);
45
- // OpenAPI escapes ":" as "\:" — strip backslashes for actual URL
46
- out = out.replace(/\\:/g, ':');
47
- return out;
48
- }
49
-
50
- const results = [];
51
-
52
- for (const ep of endpoints) {
53
- const key = `${ep.method} ${ep.path}`;
54
- if (SKIP.has(key)) {
55
- results.push({ ...ep, skipped: true, status: 200, text: 'SKIPPED (destructive, observed 200 in prior run)' });
56
- console.log(`${ep.method.padEnd(6)} SKIP ${ep.isPublic?'PUB':'AUTH'} ${ep.path}`);
57
- continue;
58
- }
59
- const url = BASE + resolvePath(ep.path);
60
- const headers = {};
61
- if (!ep.isPublic) headers.Authorization = `Bearer ${KEY}`;
62
- let body;
63
- if (['POST','PUT','PATCH'].includes(ep.method)) {
64
- body = '{}';
65
- headers['Content-Type'] = 'application/json';
66
- }
67
- let status, text = '';
68
- try {
69
- const ctl = new AbortController();
70
- const t = setTimeout(() => ctl.abort(), 4000);
71
- const res = await fetch(url, { method: ep.method, headers, body, signal: ctl.signal });
72
- clearTimeout(t);
73
- status = res.status;
74
- text = await res.text();
75
- } catch (e) {
76
- status = 0;
77
- text = `FETCH_ERR:${e.message}`;
78
- }
79
- let parsed = null;
80
- try { parsed = JSON.parse(text); } catch {}
81
- const isGhost = status === 404 && parsed?.error?.code === 'ROUTE_NOT_FOUND';
82
- results.push({ ...ep, url, status, text: text.slice(0, 250), parsed, isGhost });
83
- console.log(`${ep.method.padEnd(6)} ${String(status).padEnd(4)} ${ep.isPublic?'PUB':'AUTH'} ${ep.path}${isGhost?' GHOST':''}${status>=500?' 5XX':''}${ep.isPublic && status===401?' AUTH_BUG':''}`);
84
- }
85
-
86
- const out = path.join(os.tmpdir(), 'sweep-results.json');
87
- fs.writeFileSync(out, JSON.stringify(results, null, 2));
88
- console.log(`\nSaved: ${out}`);
89
- console.log(`Total: ${results.length}`);
90
- console.log(`Ghosts: ${results.filter(r=>r.isGhost).length}`);
91
- console.log(`5xx: ${results.filter(r=>r.status>=500).length}`);
92
- console.log(`401 on public: ${results.filter(r=>r.isPublic && r.status===401).length}`);
93
- console.log(`200 on authed without auth - n/a (we auth all authed)`);