@pixelbyte-software/pixcode 1.33.8 → 1.33.10

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 (67) hide show
  1. package/dist/api-docs.html +395 -879
  2. package/dist/assets/{index-JU38YIxa.js → index-B_dU5AHA.js} +153 -165
  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 -0
  16. package/dist-server/server/gemini-cli.js +59 -0
  17. package/dist-server/server/gemini-cli.js.map +1 -1
  18. package/dist-server/server/index.js +6 -1
  19. package/dist-server/server/index.js.map +1 -1
  20. package/dist-server/server/middleware/auth.js +51 -9
  21. package/dist-server/server/middleware/auth.js.map +1 -1
  22. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +54 -15
  23. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
  24. package/dist-server/server/modules/providers/list/qwen/qwen-sessions.provider.js +46 -0
  25. package/dist-server/server/modules/providers/list/qwen/qwen-sessions.provider.js.map +1 -1
  26. package/dist-server/server/modules/providers/provider.routes.js +32 -1
  27. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  28. package/dist-server/server/opencode-cli.js +37 -1
  29. package/dist-server/server/opencode-cli.js.map +1 -1
  30. package/dist-server/server/opencode-response-handler.js +36 -34
  31. package/dist-server/server/opencode-response-handler.js.map +1 -1
  32. package/dist-server/server/routes/agent.js +187 -56
  33. package/dist-server/server/routes/agent.js.map +1 -1
  34. package/dist-server/server/routes/projects.js +134 -8
  35. package/dist-server/server/routes/projects.js.map +1 -1
  36. package/dist-server/server/services/provider-credentials.js +42 -8
  37. package/dist-server/server/services/provider-credentials.js.map +1 -1
  38. package/package.json +178 -178
  39. package/scripts/rest-sweep.mjs +93 -0
  40. package/server/database/db.js +794 -794
  41. package/server/database/json-store.js +194 -194
  42. package/server/gemini-cli.js +60 -0
  43. package/server/index.js +6 -1
  44. package/server/middleware/auth.js +50 -9
  45. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +130 -130
  46. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
  47. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +232 -193
  48. package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
  49. package/server/modules/providers/list/qwen/qwen-auth.provider.ts +145 -145
  50. package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
  51. package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -218
  52. package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
  53. package/server/modules/providers/provider.routes.ts +37 -4
  54. package/server/modules/providers/shared/provider-configs.ts +142 -142
  55. package/server/opencode-cli.js +37 -1
  56. package/server/opencode-response-handler.js +107 -100
  57. package/server/qwen-code-cli.js +395 -395
  58. package/server/qwen-response-handler.js +73 -73
  59. package/server/routes/agent.js +178 -58
  60. package/server/routes/projects.js +136 -8
  61. package/server/routes/qwen.js +27 -27
  62. package/server/services/external-access.js +171 -171
  63. package/server/services/provider-credentials.js +189 -155
  64. package/server/services/provider-models.js +381 -381
  65. package/server/services/telegram/telegram-http-client.js +130 -130
  66. package/server/services/vapid-keys.js +36 -36
  67. package/server/utils/port-access.js +209 -209
package/package.json CHANGED
@@ -1,178 +1,178 @@
1
- {
2
- "name": "@pixelbyte-software/pixcode",
3
- "version": "1.33.8",
4
- "description": "Pixcode — a desktop and mobile web UI for Claude Code, Cursor CLI, Codex, Gemini CLI, Qwen Code, and OpenCode.",
5
- "type": "module",
6
- "main": "dist-server/server/index.js",
7
- "bin": {
8
- "pixcode": "dist-server/server/cli.js"
9
- },
10
- "files": [
11
- "server/",
12
- "shared/",
13
- "dist/",
14
- "dist-server/",
15
- "scripts/",
16
- "README.md"
17
- ],
18
- "homepage": "https://github.com/alicomert/pixcode",
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/alicomert/pixcode.git"
22
- },
23
- "bugs": {
24
- "url": "https://github.com/alicomert/pixcode/issues"
25
- },
26
- "publishConfig": {
27
- "access": "public"
28
- },
29
- "scripts": {
30
- "dev": "npm run server:dev",
31
- "server": "node dist-server/server/cli.js start",
32
- "server:dev": "node server/cli.js daemon install --mode system --port 3001 --frontend-port 5173",
33
- "server:dev-watch": "node server/cli.js daemon restart --mode system --port 3001 --frontend-port 5173",
34
- "client": "vite",
35
- "build": "npm run build:client && npm run build:server",
36
- "build:client": "vite build",
37
- "prebuild:server": "node -e \"require('node:fs').rmSync('dist-server', { recursive: true, force: true })\"",
38
- "build:server": "tsc -p server/tsconfig.json && tsc-alias -p server/tsconfig.json",
39
- "preview": "vite preview",
40
- "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p server/tsconfig.json",
41
- "lint": "eslint src/ server/",
42
- "lint:fix": "eslint src/ server/ --fix",
43
- "start": "npm run build && npm run server",
44
- "release": "./release.sh",
45
- "prepublishOnly": "npm run build",
46
- "postinstall": "node scripts/fix-node-pty.js",
47
- "prepare": "husky",
48
- "update:platform": "./update-platform.sh"
49
- },
50
- "keywords": [
51
- "claude code",
52
- "claude-code",
53
- "pixcode",
54
- "codex",
55
- "gemini",
56
- "gemini-cli",
57
- "cursor",
58
- "cursor-cli",
59
- "opencode",
60
- "opencode-cli",
61
- "anthropic",
62
- "openai",
63
- "google",
64
- "coding-agent",
65
- "web-ui",
66
- "ui",
67
- "mobile IDE"
68
- ],
69
- "author": "Pixcode Contributors",
70
- "license": "AGPL-3.0-or-later",
71
- "dependencies": {
72
- "@anthropic-ai/claude-agent-sdk": "^0.2.116",
73
- "@iarna/toml": "^2.2.5",
74
- "@octokit/rest": "^22.0.0",
75
- "@openai/codex-sdk": "^0.101.0",
76
- "bcryptjs": "^3.0.3",
77
- "better-sqlite3": "^12.6.2",
78
- "chokidar": "^4.0.3",
79
- "cors": "^2.8.5",
80
- "cross-spawn": "^7.0.3",
81
- "express": "^4.18.2",
82
- "gray-matter": "^4.0.3",
83
- "jsonwebtoken": "^9.0.2",
84
- "mime-types": "^3.0.1",
85
- "multer": "^2.0.1",
86
- "node-fetch": "^2.7.0",
87
- "node-pty": "^1.2.0-beta.12",
88
- "tar": "^7.5.13",
89
- "web-push": "^3.6.7",
90
- "ws": "^8.14.2"
91
- },
92
- "devDependencies": {
93
- "@codemirror/lang-css": "^6.3.1",
94
- "@codemirror/lang-html": "^6.4.9",
95
- "@codemirror/lang-javascript": "^6.2.4",
96
- "@codemirror/lang-json": "^6.0.1",
97
- "@codemirror/lang-markdown": "^6.3.3",
98
- "@codemirror/lang-python": "^6.2.1",
99
- "@codemirror/merge": "^6.11.1",
100
- "@codemirror/theme-one-dark": "^6.1.2",
101
- "@commitlint/cli": "^20.5.0",
102
- "@commitlint/config-conventional": "^20.5.0",
103
- "@eslint/js": "^9.39.3",
104
- "@heroicons/react": "^2.2.0",
105
- "@release-it/conventional-changelog": "^10.0.5",
106
- "@replit/codemirror-minimap": "^0.5.2",
107
- "@tailwindcss/typography": "^0.5.16",
108
- "@types/better-sqlite3": "^7.6.13",
109
- "@types/cross-spawn": "^6.0.6",
110
- "@types/express": "^5.0.6",
111
- "@types/node": "^22.19.7",
112
- "@types/qrcode": "^1.5.6",
113
- "@types/react": "^18.2.43",
114
- "@types/react-dom": "^18.2.17",
115
- "@uiw/react-codemirror": "^4.23.13",
116
- "@vitejs/plugin-react": "^4.6.0",
117
- "@xterm/addon-clipboard": "^0.1.0",
118
- "@xterm/addon-fit": "^0.10.0",
119
- "@xterm/addon-web-links": "^0.11.0",
120
- "@xterm/addon-webgl": "^0.18.0",
121
- "@xterm/xterm": "^5.5.0",
122
- "auto-changelog": "^2.5.0",
123
- "autoprefixer": "^10.4.16",
124
- "class-variance-authority": "^0.7.1",
125
- "clsx": "^2.1.1",
126
- "concurrently": "^8.2.2",
127
- "eslint": "^9.39.3",
128
- "eslint-import-resolver-typescript": "^4.4.4",
129
- "eslint-plugin-boundaries": "^6.0.2",
130
- "eslint-plugin-import-x": "^4.16.1",
131
- "eslint-plugin-react": "^7.37.5",
132
- "eslint-plugin-react-hooks": "^7.0.1",
133
- "eslint-plugin-react-refresh": "^0.5.2",
134
- "eslint-plugin-tailwindcss": "^3.18.2",
135
- "eslint-plugin-unused-imports": "^4.4.1",
136
- "fuse.js": "^7.0.0",
137
- "globals": "^17.4.0",
138
- "gsap": "^3.12.5",
139
- "husky": "^9.1.7",
140
- "i18next": "^25.7.4",
141
- "i18next-browser-languagedetector": "^8.2.0",
142
- "jszip": "^3.10.1",
143
- "katex": "^0.16.25",
144
- "lint-staged": "^16.3.2",
145
- "lucide-react": "^0.515.0",
146
- "node-gyp": "^12.0.0",
147
- "postcss": "^8.4.32",
148
- "qrcode": "^1.5.4",
149
- "react": "^18.2.0",
150
- "react-dom": "^18.2.0",
151
- "react-dropzone": "^14.2.3",
152
- "react-error-boundary": "^4.1.2",
153
- "react-i18next": "^16.5.3",
154
- "react-markdown": "^10.1.0",
155
- "react-router-dom": "^6.8.1",
156
- "react-syntax-highlighter": "^16.1.1",
157
- "rehype-katex": "^7.0.1",
158
- "rehype-raw": "^7.0.0",
159
- "release-it": "^19.0.5",
160
- "remark-gfm": "^4.0.0",
161
- "remark-math": "^6.0.0",
162
- "sharp": "^0.34.2",
163
- "tailwind-merge": "^3.3.1",
164
- "tailwindcss": "^3.4.0",
165
- "tsc-alias": "^1.8.16",
166
- "tsx": "^4.21.0",
167
- "typescript": "^5.9.3",
168
- "typescript-eslint": "^8.56.1",
169
- "vite": "^7.0.4"
170
- },
171
- "lint-staged": {
172
- "src/**/*.{ts,tsx,js,jsx}": "eslint",
173
- "server/**/*.{js,ts}": "eslint"
174
- },
175
- "overrides": {
176
- "undici": ">=6.23.1"
177
- }
178
- }
1
+ {
2
+ "name": "@pixelbyte-software/pixcode",
3
+ "version": "1.33.10",
4
+ "description": "Pixcode — a desktop and mobile web UI for Claude Code, Cursor CLI, Codex, Gemini CLI, Qwen Code, and OpenCode.",
5
+ "type": "module",
6
+ "main": "dist-server/server/index.js",
7
+ "bin": {
8
+ "pixcode": "dist-server/server/cli.js"
9
+ },
10
+ "files": [
11
+ "server/",
12
+ "shared/",
13
+ "dist/",
14
+ "dist-server/",
15
+ "scripts/",
16
+ "README.md"
17
+ ],
18
+ "homepage": "https://github.com/alicomert/pixcode",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/alicomert/pixcode.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/alicomert/pixcode/issues"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "scripts": {
30
+ "dev": "npm run server:dev",
31
+ "server": "node dist-server/server/cli.js start",
32
+ "server:dev": "node server/cli.js daemon install --mode system --port 3001 --frontend-port 5173",
33
+ "server:dev-watch": "node server/cli.js daemon restart --mode system --port 3001 --frontend-port 5173",
34
+ "client": "vite",
35
+ "build": "npm run build:client && npm run build:server",
36
+ "build:client": "vite build",
37
+ "prebuild:server": "node -e \"require('node:fs').rmSync('dist-server', { recursive: true, force: true })\"",
38
+ "build:server": "tsc -p server/tsconfig.json && tsc-alias -p server/tsconfig.json",
39
+ "preview": "vite preview",
40
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p server/tsconfig.json",
41
+ "lint": "eslint src/ server/",
42
+ "lint:fix": "eslint src/ server/ --fix",
43
+ "start": "npm run build && npm run server",
44
+ "release": "./release.sh",
45
+ "prepublishOnly": "npm run build",
46
+ "postinstall": "node scripts/fix-node-pty.js",
47
+ "prepare": "husky",
48
+ "update:platform": "./update-platform.sh"
49
+ },
50
+ "keywords": [
51
+ "claude code",
52
+ "claude-code",
53
+ "pixcode",
54
+ "codex",
55
+ "gemini",
56
+ "gemini-cli",
57
+ "cursor",
58
+ "cursor-cli",
59
+ "opencode",
60
+ "opencode-cli",
61
+ "anthropic",
62
+ "openai",
63
+ "google",
64
+ "coding-agent",
65
+ "web-ui",
66
+ "ui",
67
+ "mobile IDE"
68
+ ],
69
+ "author": "Pixcode Contributors",
70
+ "license": "AGPL-3.0-or-later",
71
+ "dependencies": {
72
+ "@anthropic-ai/claude-agent-sdk": "^0.2.116",
73
+ "@iarna/toml": "^2.2.5",
74
+ "@octokit/rest": "^22.0.0",
75
+ "@openai/codex-sdk": "^0.101.0",
76
+ "bcryptjs": "^3.0.3",
77
+ "better-sqlite3": "^12.6.2",
78
+ "chokidar": "^4.0.3",
79
+ "cors": "^2.8.5",
80
+ "cross-spawn": "^7.0.3",
81
+ "express": "^4.18.2",
82
+ "gray-matter": "^4.0.3",
83
+ "jsonwebtoken": "^9.0.2",
84
+ "mime-types": "^3.0.1",
85
+ "multer": "^2.0.1",
86
+ "node-fetch": "^2.7.0",
87
+ "node-pty": "^1.2.0-beta.12",
88
+ "tar": "^7.5.13",
89
+ "web-push": "^3.6.7",
90
+ "ws": "^8.14.2"
91
+ },
92
+ "devDependencies": {
93
+ "@codemirror/lang-css": "^6.3.1",
94
+ "@codemirror/lang-html": "^6.4.9",
95
+ "@codemirror/lang-javascript": "^6.2.4",
96
+ "@codemirror/lang-json": "^6.0.1",
97
+ "@codemirror/lang-markdown": "^6.3.3",
98
+ "@codemirror/lang-python": "^6.2.1",
99
+ "@codemirror/merge": "^6.11.1",
100
+ "@codemirror/theme-one-dark": "^6.1.2",
101
+ "@commitlint/cli": "^20.5.0",
102
+ "@commitlint/config-conventional": "^20.5.0",
103
+ "@eslint/js": "^9.39.3",
104
+ "@heroicons/react": "^2.2.0",
105
+ "@release-it/conventional-changelog": "^10.0.5",
106
+ "@replit/codemirror-minimap": "^0.5.2",
107
+ "@tailwindcss/typography": "^0.5.16",
108
+ "@types/better-sqlite3": "^7.6.13",
109
+ "@types/cross-spawn": "^6.0.6",
110
+ "@types/express": "^5.0.6",
111
+ "@types/node": "^22.19.7",
112
+ "@types/qrcode": "^1.5.6",
113
+ "@types/react": "^18.2.43",
114
+ "@types/react-dom": "^18.2.17",
115
+ "@uiw/react-codemirror": "^4.23.13",
116
+ "@vitejs/plugin-react": "^4.6.0",
117
+ "@xterm/addon-clipboard": "^0.1.0",
118
+ "@xterm/addon-fit": "^0.10.0",
119
+ "@xterm/addon-web-links": "^0.11.0",
120
+ "@xterm/addon-webgl": "^0.18.0",
121
+ "@xterm/xterm": "^5.5.0",
122
+ "auto-changelog": "^2.5.0",
123
+ "autoprefixer": "^10.4.16",
124
+ "class-variance-authority": "^0.7.1",
125
+ "clsx": "^2.1.1",
126
+ "concurrently": "^8.2.2",
127
+ "eslint": "^9.39.3",
128
+ "eslint-import-resolver-typescript": "^4.4.4",
129
+ "eslint-plugin-boundaries": "^6.0.2",
130
+ "eslint-plugin-import-x": "^4.16.1",
131
+ "eslint-plugin-react": "^7.37.5",
132
+ "eslint-plugin-react-hooks": "^7.0.1",
133
+ "eslint-plugin-react-refresh": "^0.5.2",
134
+ "eslint-plugin-tailwindcss": "^3.18.2",
135
+ "eslint-plugin-unused-imports": "^4.4.1",
136
+ "fuse.js": "^7.0.0",
137
+ "globals": "^17.4.0",
138
+ "gsap": "^3.12.5",
139
+ "husky": "^9.1.7",
140
+ "i18next": "^25.7.4",
141
+ "i18next-browser-languagedetector": "^8.2.0",
142
+ "jszip": "^3.10.1",
143
+ "katex": "^0.16.25",
144
+ "lint-staged": "^16.3.2",
145
+ "lucide-react": "^0.515.0",
146
+ "node-gyp": "^12.0.0",
147
+ "postcss": "^8.4.32",
148
+ "qrcode": "^1.5.4",
149
+ "react": "^18.2.0",
150
+ "react-dom": "^18.2.0",
151
+ "react-dropzone": "^14.2.3",
152
+ "react-error-boundary": "^4.1.2",
153
+ "react-i18next": "^16.5.3",
154
+ "react-markdown": "^10.1.0",
155
+ "react-router-dom": "^6.8.1",
156
+ "react-syntax-highlighter": "^16.1.1",
157
+ "rehype-katex": "^7.0.1",
158
+ "rehype-raw": "^7.0.0",
159
+ "release-it": "^19.0.5",
160
+ "remark-gfm": "^4.0.0",
161
+ "remark-math": "^6.0.0",
162
+ "sharp": "^0.34.2",
163
+ "tailwind-merge": "^3.3.1",
164
+ "tailwindcss": "^3.4.0",
165
+ "tsc-alias": "^1.8.16",
166
+ "tsx": "^4.21.0",
167
+ "typescript": "^5.9.3",
168
+ "typescript-eslint": "^8.56.1",
169
+ "vite": "^7.0.4"
170
+ },
171
+ "lint-staged": {
172
+ "src/**/*.{ts,tsx,js,jsx}": "eslint",
173
+ "server/**/*.{js,ts}": "eslint"
174
+ },
175
+ "overrides": {
176
+ "undici": ">=6.23.1"
177
+ }
178
+ }
@@ -0,0 +1,93 @@
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)`);