@ccpocket-base-auth/bridge 1.26.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 +67 -0
- package/dist/archive-store.d.ts +28 -0
- package/dist/archive-store.js +68 -0
- package/dist/archive-store.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +82 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-process.d.ts +171 -0
- package/dist/codex-process.js +1928 -0
- package/dist/codex-process.js.map +1 -0
- package/dist/debug-trace-store.d.ts +15 -0
- package/dist/debug-trace-store.js +78 -0
- package/dist/debug-trace-store.js.map +1 -0
- package/dist/doctor.d.ts +58 -0
- package/dist/doctor.js +663 -0
- package/dist/doctor.js.map +1 -0
- package/dist/firebase-auth.d.ts +35 -0
- package/dist/firebase-auth.js +132 -0
- package/dist/firebase-auth.js.map +1 -0
- package/dist/gallery-store.d.ts +67 -0
- package/dist/gallery-store.js +333 -0
- package/dist/gallery-store.js.map +1 -0
- package/dist/image-store.d.ts +23 -0
- package/dist/image-store.js +142 -0
- package/dist/image-store.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +191 -0
- package/dist/index.js.map +1 -0
- package/dist/mdns.d.ts +7 -0
- package/dist/mdns.js +49 -0
- package/dist/mdns.js.map +1 -0
- package/dist/parser.d.ts +465 -0
- package/dist/parser.js +251 -0
- package/dist/parser.js.map +1 -0
- package/dist/project-history.d.ts +10 -0
- package/dist/project-history.js +73 -0
- package/dist/project-history.js.map +1 -0
- package/dist/prompt-history-backup.d.ts +15 -0
- package/dist/prompt-history-backup.js +46 -0
- package/dist/prompt-history-backup.js.map +1 -0
- package/dist/proxy.d.ts +15 -0
- package/dist/proxy.js +95 -0
- package/dist/proxy.js.map +1 -0
- package/dist/push-i18n.d.ts +7 -0
- package/dist/push-i18n.js +75 -0
- package/dist/push-i18n.js.map +1 -0
- package/dist/push-relay.d.ts +29 -0
- package/dist/push-relay.js +70 -0
- package/dist/push-relay.js.map +1 -0
- package/dist/recording-store.d.ts +51 -0
- package/dist/recording-store.js +158 -0
- package/dist/recording-store.js.map +1 -0
- package/dist/screenshot.d.ts +28 -0
- package/dist/screenshot.js +98 -0
- package/dist/screenshot.js.map +1 -0
- package/dist/sdk-process.d.ts +180 -0
- package/dist/sdk-process.js +937 -0
- package/dist/sdk-process.js.map +1 -0
- package/dist/session.d.ts +142 -0
- package/dist/session.js +615 -0
- package/dist/session.js.map +1 -0
- package/dist/sessions-index.d.ts +128 -0
- package/dist/sessions-index.js +1767 -0
- package/dist/sessions-index.js.map +1 -0
- package/dist/setup-launchd.d.ts +8 -0
- package/dist/setup-launchd.js +109 -0
- package/dist/setup-launchd.js.map +1 -0
- package/dist/setup-systemd.d.ts +8 -0
- package/dist/setup-systemd.js +118 -0
- package/dist/setup-systemd.js.map +1 -0
- package/dist/startup-info.d.ts +8 -0
- package/dist/startup-info.js +92 -0
- package/dist/startup-info.js.map +1 -0
- package/dist/usage.d.ts +69 -0
- package/dist/usage.js +545 -0
- package/dist/usage.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.js +43 -0
- package/dist/version.js.map +1 -0
- package/dist/websocket.d.ts +127 -0
- package/dist/websocket.js +2482 -0
- package/dist/websocket.js.map +1 -0
- package/dist/worktree-store.d.ts +25 -0
- package/dist/worktree-store.js +59 -0
- package/dist/worktree-store.js.map +1 -0
- package/dist/worktree.d.ts +47 -0
- package/dist/worktree.js +313 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +68 -0
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge Server doctor command.
|
|
3
|
+
*
|
|
4
|
+
* Checks the health of all dependencies and provides actionable guidance
|
|
5
|
+
* when issues are found — similar to `flutter doctor`.
|
|
6
|
+
*/
|
|
7
|
+
import { execFile, execSync } from "node:child_process";
|
|
8
|
+
import { accessSync, constants as fsConstants, existsSync, } from "node:fs";
|
|
9
|
+
import net from "node:net";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Helper
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
function execQuiet(cmd) {
|
|
16
|
+
return execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
17
|
+
}
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Individual checks
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
export async function checkNodeVersion() {
|
|
22
|
+
const version = process.version; // e.g. "v22.5.0"
|
|
23
|
+
const major = parseInt(version.slice(1), 10);
|
|
24
|
+
if (major >= 18) {
|
|
25
|
+
return { name: "Node.js", status: "pass", message: version };
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
name: "Node.js",
|
|
29
|
+
status: "fail",
|
|
30
|
+
message: `${version} (requires >=18.0.0)`,
|
|
31
|
+
remediation: "Install Node.js >=18.0.0: https://nodejs.org/",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function checkGit() {
|
|
35
|
+
try {
|
|
36
|
+
const out = execQuiet("git --version"); // "git version 2.44.0"
|
|
37
|
+
const version = out.replace("git version ", "");
|
|
38
|
+
return { name: "Git", status: "pass", message: `v${version}` };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return {
|
|
42
|
+
name: "Git",
|
|
43
|
+
status: "fail",
|
|
44
|
+
message: "Not installed",
|
|
45
|
+
remediation: "Install Git: https://git-scm.com/downloads",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Check both Claude Code CLI and Codex CLI. At least one must be installed. */
|
|
50
|
+
export async function checkCliProviders() {
|
|
51
|
+
const providers = [];
|
|
52
|
+
// --- Claude Code CLI ---
|
|
53
|
+
{
|
|
54
|
+
let installed = false;
|
|
55
|
+
let version;
|
|
56
|
+
let authenticated = false;
|
|
57
|
+
let authMessage;
|
|
58
|
+
let remediation;
|
|
59
|
+
try {
|
|
60
|
+
const out = execQuiet("claude --version");
|
|
61
|
+
installed = true;
|
|
62
|
+
version = out.trim().split("\n")[0];
|
|
63
|
+
// Check auth
|
|
64
|
+
try {
|
|
65
|
+
const authOut = execQuiet("claude auth status");
|
|
66
|
+
// If exit code 0, authenticated
|
|
67
|
+
if (authOut.toLowerCase().includes("not logged in") || authOut.toLowerCase().includes("unauthenticated")) {
|
|
68
|
+
authenticated = false;
|
|
69
|
+
authMessage = "Not authenticated";
|
|
70
|
+
remediation = "Run: claude auth login";
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
authenticated = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// auth command failed — treat as unauthenticated
|
|
78
|
+
authenticated = false;
|
|
79
|
+
authMessage = "Not authenticated";
|
|
80
|
+
remediation = "Run: claude auth login";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
remediation = "Install Claude Code: https://docs.anthropic.com/en/docs/claude-code/getting-started";
|
|
85
|
+
}
|
|
86
|
+
providers.push({
|
|
87
|
+
name: "Claude Code CLI",
|
|
88
|
+
installed,
|
|
89
|
+
version,
|
|
90
|
+
authenticated,
|
|
91
|
+
authMessage,
|
|
92
|
+
remediation,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// --- Codex CLI ---
|
|
96
|
+
{
|
|
97
|
+
let installed = false;
|
|
98
|
+
let version;
|
|
99
|
+
let authenticated = false;
|
|
100
|
+
let authMessage;
|
|
101
|
+
let remediation;
|
|
102
|
+
try {
|
|
103
|
+
const out = execQuiet("codex --version");
|
|
104
|
+
installed = true;
|
|
105
|
+
version = out.trim().split("\n")[0];
|
|
106
|
+
// Codex authenticates via OPENAI_API_KEY env var or ~/.codex/auth.json
|
|
107
|
+
if (process.env.OPENAI_API_KEY) {
|
|
108
|
+
authenticated = true;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const authFile = join(homedir(), ".codex", "auth.json");
|
|
112
|
+
if (existsSync(authFile)) {
|
|
113
|
+
authenticated = true;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
authenticated = false;
|
|
117
|
+
authMessage = "Not authenticated";
|
|
118
|
+
remediation = "Run: codex login";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
remediation = "Install Codex CLI: https://github.com/openai/codex";
|
|
124
|
+
}
|
|
125
|
+
providers.push({
|
|
126
|
+
name: "Codex CLI",
|
|
127
|
+
installed,
|
|
128
|
+
version,
|
|
129
|
+
authenticated,
|
|
130
|
+
authMessage,
|
|
131
|
+
remediation,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
const installedCount = providers.filter((p) => p.installed).length;
|
|
135
|
+
const total = providers.length;
|
|
136
|
+
if (installedCount === 0) {
|
|
137
|
+
return {
|
|
138
|
+
name: "CLI providers",
|
|
139
|
+
status: "fail",
|
|
140
|
+
message: "No CLI providers installed",
|
|
141
|
+
remediation: "Install at least one: https://docs.anthropic.com/en/docs/claude-code/getting-started OR https://github.com/openai/codex",
|
|
142
|
+
providers,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// At least one installed — check if any auth warnings
|
|
146
|
+
const hasAuthWarn = providers.some((p) => p.installed && !p.authenticated);
|
|
147
|
+
return {
|
|
148
|
+
name: "CLI providers",
|
|
149
|
+
status: hasAuthWarn ? "warn" : "pass",
|
|
150
|
+
message: `${installedCount} of ${total} available`,
|
|
151
|
+
providers,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export async function checkDependencies() {
|
|
155
|
+
// In monorepo setups, node_modules may be hoisted to the workspace root.
|
|
156
|
+
// Use import.meta.resolve() to check if packages are resolvable.
|
|
157
|
+
const requiredPackages = [
|
|
158
|
+
"ws",
|
|
159
|
+
"@anthropic-ai/claude-agent-sdk",
|
|
160
|
+
"bonjour-service",
|
|
161
|
+
];
|
|
162
|
+
const missing = [];
|
|
163
|
+
for (const pkg of requiredPackages) {
|
|
164
|
+
try {
|
|
165
|
+
import.meta.resolve(pkg);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
missing.push(pkg);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (missing.length > 0) {
|
|
172
|
+
return {
|
|
173
|
+
name: "npm dependencies",
|
|
174
|
+
status: "fail",
|
|
175
|
+
message: `Missing: ${missing.join(", ")}`,
|
|
176
|
+
remediation: "Run: npm install",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return { name: "npm dependencies", status: "pass", message: "All packages available" };
|
|
180
|
+
}
|
|
181
|
+
export async function checkPortAvailable(port) {
|
|
182
|
+
return new Promise((resolve) => {
|
|
183
|
+
let resolved = false;
|
|
184
|
+
const done = (result) => {
|
|
185
|
+
if (resolved)
|
|
186
|
+
return;
|
|
187
|
+
resolved = true;
|
|
188
|
+
resolve(result);
|
|
189
|
+
};
|
|
190
|
+
const server = net.createServer();
|
|
191
|
+
server.once("error", (err) => {
|
|
192
|
+
if (err.code === "EADDRINUSE") {
|
|
193
|
+
done({
|
|
194
|
+
name: "Port availability",
|
|
195
|
+
status: "warn",
|
|
196
|
+
message: `Port ${port} is in use`,
|
|
197
|
+
remediation: `Another Bridge may be running, or set BRIDGE_PORT to a different port`,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
done({
|
|
202
|
+
name: "Port availability",
|
|
203
|
+
status: "warn",
|
|
204
|
+
message: `Port ${port} check failed: ${err.code}`,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
server.listen(port, "127.0.0.1", () => {
|
|
209
|
+
server.close(() => {
|
|
210
|
+
done({
|
|
211
|
+
name: "Port availability",
|
|
212
|
+
status: "pass",
|
|
213
|
+
message: `Port ${port} is available`,
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
// Safety timeout
|
|
218
|
+
setTimeout(() => {
|
|
219
|
+
try {
|
|
220
|
+
server.close();
|
|
221
|
+
}
|
|
222
|
+
catch { /* ignore */ }
|
|
223
|
+
done({
|
|
224
|
+
name: "Port availability",
|
|
225
|
+
status: "warn",
|
|
226
|
+
message: `Port ${port} check timed out`,
|
|
227
|
+
});
|
|
228
|
+
}, 3000);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/** Resolve the tailscale CLI binary path (may be inside macOS .app bundle). */
|
|
232
|
+
function tailscaleCmd() {
|
|
233
|
+
// Try bare command first (Linux, Homebrew install, etc.)
|
|
234
|
+
try {
|
|
235
|
+
execQuiet("tailscale version");
|
|
236
|
+
return "tailscale";
|
|
237
|
+
}
|
|
238
|
+
catch { /* not in PATH */ }
|
|
239
|
+
// macOS: Tailscale.app bundles the CLI inside the app
|
|
240
|
+
const macPath = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
|
|
241
|
+
if (existsSync(macPath))
|
|
242
|
+
return macPath;
|
|
243
|
+
throw new Error("tailscale not found");
|
|
244
|
+
}
|
|
245
|
+
export async function checkTailscale() {
|
|
246
|
+
let cmd;
|
|
247
|
+
try {
|
|
248
|
+
cmd = tailscaleCmd();
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return {
|
|
252
|
+
name: "Tailscale",
|
|
253
|
+
status: "skip",
|
|
254
|
+
message: "Not installed (optional for remote access)",
|
|
255
|
+
remediation: "Install: https://tailscale.com/download",
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const out = execQuiet(`${cmd} status`);
|
|
260
|
+
// Extract the Tailscale IP (first IPv4 in output)
|
|
261
|
+
const ipMatch = out.match(/(\d+\.\d+\.\d+\.\d+)/);
|
|
262
|
+
const ip = ipMatch ? ipMatch[1] : "";
|
|
263
|
+
return {
|
|
264
|
+
name: "Tailscale",
|
|
265
|
+
status: "pass",
|
|
266
|
+
message: ip ? `Connected (${ip})` : "Connected",
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
return {
|
|
271
|
+
name: "Tailscale",
|
|
272
|
+
status: "warn",
|
|
273
|
+
message: "Installed but not connected",
|
|
274
|
+
remediation: "Run: tailscale up",
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
export async function checkFirebaseConnectivity() {
|
|
279
|
+
// Use a read-only endpoint to avoid creating anonymous accounts as a side effect
|
|
280
|
+
const FIREBASE_API_KEY = "AIzaSyAptNnokWPqJIgv2Lr3I8ETN6bqZb5BGvc";
|
|
281
|
+
const url = `https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=${FIREBASE_API_KEY}`;
|
|
282
|
+
try {
|
|
283
|
+
const response = await fetch(url, {
|
|
284
|
+
method: "POST",
|
|
285
|
+
headers: { "Content-Type": "application/json" },
|
|
286
|
+
body: JSON.stringify({}),
|
|
287
|
+
signal: AbortSignal.timeout(5000),
|
|
288
|
+
});
|
|
289
|
+
// Any response (even 400) means the API is reachable
|
|
290
|
+
if (response.status < 500) {
|
|
291
|
+
return {
|
|
292
|
+
name: "Firebase connectivity",
|
|
293
|
+
status: "pass",
|
|
294
|
+
message: "Firebase Auth API reachable",
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
name: "Firebase connectivity",
|
|
299
|
+
status: "warn",
|
|
300
|
+
message: `Firebase Auth API returned ${response.status}`,
|
|
301
|
+
remediation: "Push notifications may not work. Check network connectivity.",
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return {
|
|
306
|
+
name: "Firebase connectivity",
|
|
307
|
+
status: "warn",
|
|
308
|
+
message: "Unreachable",
|
|
309
|
+
remediation: "Push notifications will be disabled. Check network connectivity.",
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
export async function checkDataDirectory() {
|
|
314
|
+
const dir = join(homedir(), ".ccpocket");
|
|
315
|
+
if (!existsSync(dir)) {
|
|
316
|
+
return {
|
|
317
|
+
name: "Data directory",
|
|
318
|
+
status: "pass",
|
|
319
|
+
message: "~/.ccpocket/ will be created on first run",
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
accessSync(dir, fsConstants.R_OK | fsConstants.W_OK);
|
|
324
|
+
return {
|
|
325
|
+
name: "Data directory",
|
|
326
|
+
status: "pass",
|
|
327
|
+
message: "~/.ccpocket/ exists",
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return {
|
|
332
|
+
name: "Data directory",
|
|
333
|
+
status: "warn",
|
|
334
|
+
message: "~/.ccpocket/ is not writable",
|
|
335
|
+
remediation: "Fix permissions: chmod u+rw ~/.ccpocket",
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
export async function checkLaunchdService() {
|
|
340
|
+
if (process.platform !== "darwin") {
|
|
341
|
+
return {
|
|
342
|
+
name: "launchd service",
|
|
343
|
+
status: "skip",
|
|
344
|
+
message: "macOS only",
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
const out = execSync("launchctl list", {
|
|
349
|
+
encoding: "utf-8",
|
|
350
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
351
|
+
});
|
|
352
|
+
if (out.includes("com.ccpocket.bridge")) {
|
|
353
|
+
return {
|
|
354
|
+
name: "launchd service",
|
|
355
|
+
status: "pass",
|
|
356
|
+
message: "Registered",
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
name: "launchd service",
|
|
361
|
+
status: "skip",
|
|
362
|
+
message: "Not registered",
|
|
363
|
+
remediation: "Register with: ccpocket-bridge setup",
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return {
|
|
368
|
+
name: "launchd service",
|
|
369
|
+
status: "skip",
|
|
370
|
+
message: "Unable to check",
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
export async function checkSystemdService() {
|
|
375
|
+
if (process.platform !== "linux") {
|
|
376
|
+
return {
|
|
377
|
+
name: "systemd service",
|
|
378
|
+
status: "skip",
|
|
379
|
+
message: "Linux only",
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
const out = execSync("systemctl --user is-active ccpocket-bridge.service", {
|
|
384
|
+
encoding: "utf-8",
|
|
385
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
386
|
+
});
|
|
387
|
+
if (out.trim() === "active") {
|
|
388
|
+
return {
|
|
389
|
+
name: "systemd service",
|
|
390
|
+
status: "pass",
|
|
391
|
+
message: "Active",
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
name: "systemd service",
|
|
396
|
+
status: "skip",
|
|
397
|
+
message: `Status: ${out.trim()}`,
|
|
398
|
+
remediation: "Register with: ccpocket-bridge setup",
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
return {
|
|
403
|
+
name: "systemd service",
|
|
404
|
+
status: "skip",
|
|
405
|
+
message: "Not registered",
|
|
406
|
+
remediation: "Register with: ccpocket-bridge setup",
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// ---------------------------------------------------------------------------
|
|
411
|
+
// macOS permission checks
|
|
412
|
+
// ---------------------------------------------------------------------------
|
|
413
|
+
/**
|
|
414
|
+
* Swift inline script to check Screen Recording permission.
|
|
415
|
+
* CGWindowListCopyWindowInfo returns window names only when the process has
|
|
416
|
+
* Screen Recording permission. Without it, kCGWindowName is always empty.
|
|
417
|
+
* We check if *any* on-screen window has a non-empty name.
|
|
418
|
+
*/
|
|
419
|
+
const CHECK_SCREEN_RECORDING_SWIFT = `
|
|
420
|
+
import CoreGraphics
|
|
421
|
+
|
|
422
|
+
let windowList = CGWindowListCopyWindowInfo(
|
|
423
|
+
[.optionOnScreenOnly, .excludeDesktopElements],
|
|
424
|
+
kCGNullWindowID
|
|
425
|
+
) as? [[String: Any]] ?? []
|
|
426
|
+
|
|
427
|
+
var hasName = false
|
|
428
|
+
for w in windowList {
|
|
429
|
+
guard let layer = w[kCGWindowLayer as String] as? Int, layer == 0 else { continue }
|
|
430
|
+
if let name = w[kCGWindowName as String] as? String, !name.isEmpty {
|
|
431
|
+
hasName = true
|
|
432
|
+
break
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
print(hasName ? "granted" : "denied")
|
|
436
|
+
`;
|
|
437
|
+
export async function checkScreenRecording() {
|
|
438
|
+
if (process.platform !== "darwin") {
|
|
439
|
+
return { name: "Screen Recording", status: "skip", message: "macOS only" };
|
|
440
|
+
}
|
|
441
|
+
return new Promise((resolve) => {
|
|
442
|
+
execFile("swift", ["-e", CHECK_SCREEN_RECORDING_SWIFT], { timeout: 15_000 }, (err, stdout) => {
|
|
443
|
+
if (err) {
|
|
444
|
+
resolve({
|
|
445
|
+
name: "Screen Recording",
|
|
446
|
+
status: "warn",
|
|
447
|
+
message: "Unable to check (swift not available)",
|
|
448
|
+
remediation: "Install Xcode Command Line Tools: xcode-select --install",
|
|
449
|
+
});
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const result = stdout.trim();
|
|
453
|
+
if (result === "granted") {
|
|
454
|
+
resolve({
|
|
455
|
+
name: "Screen Recording",
|
|
456
|
+
status: "pass",
|
|
457
|
+
message: "Permission granted",
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
resolve({
|
|
462
|
+
name: "Screen Recording",
|
|
463
|
+
status: "warn",
|
|
464
|
+
message: "Permission not granted (screenshots will fail)",
|
|
465
|
+
remediation: "System Settings > Privacy & Security > Screen Recording > enable your terminal app",
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Check if Claude Code credentials are available.
|
|
473
|
+
*
|
|
474
|
+
* Checks ~/.claude/.credentials.json first, then falls back to
|
|
475
|
+
* macOS Keychain ("Claude Code-credentials" service).
|
|
476
|
+
*/
|
|
477
|
+
export async function checkKeychainAccess() {
|
|
478
|
+
const credPath = join(homedir(), ".claude", ".credentials.json");
|
|
479
|
+
if (existsSync(credPath)) {
|
|
480
|
+
return {
|
|
481
|
+
name: "Keychain access",
|
|
482
|
+
status: "pass",
|
|
483
|
+
message: "Claude Code credentials found (~/.claude/.credentials.json)",
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
// Fallback: check macOS Keychain
|
|
487
|
+
if (process.platform === "darwin") {
|
|
488
|
+
try {
|
|
489
|
+
const { execFileSync } = await import("node:child_process");
|
|
490
|
+
execFileSync("security", ["find-generic-password", "-s", "Claude Code-credentials"], { stdio: "ignore" });
|
|
491
|
+
return {
|
|
492
|
+
name: "Keychain access",
|
|
493
|
+
status: "pass",
|
|
494
|
+
message: "Claude Code credentials found (macOS Keychain)",
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
// Not in Keychain either
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
name: "Keychain access",
|
|
503
|
+
status: "skip",
|
|
504
|
+
message: "No Claude Code credentials stored",
|
|
505
|
+
remediation: "Run: claude auth login (if using Claude Code)",
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
// ---------------------------------------------------------------------------
|
|
509
|
+
// Runner
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
function getAllChecks() {
|
|
512
|
+
const port = parseInt(process.env.BRIDGE_PORT ?? "8765", 10);
|
|
513
|
+
return [
|
|
514
|
+
// Required
|
|
515
|
+
{ name: "Node.js", category: "required", run: checkNodeVersion },
|
|
516
|
+
{ name: "Git", category: "required", run: checkGit },
|
|
517
|
+
{ name: "CLI providers", category: "required", run: checkCliProviders },
|
|
518
|
+
{ name: "npm dependencies", category: "required", run: checkDependencies },
|
|
519
|
+
{
|
|
520
|
+
name: "Port availability",
|
|
521
|
+
category: "required",
|
|
522
|
+
run: () => checkPortAvailable(port),
|
|
523
|
+
},
|
|
524
|
+
// Optional — macOS permissions
|
|
525
|
+
{
|
|
526
|
+
name: "Screen Recording",
|
|
527
|
+
category: "optional",
|
|
528
|
+
run: checkScreenRecording,
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
name: "Keychain access",
|
|
532
|
+
category: "optional",
|
|
533
|
+
run: checkKeychainAccess,
|
|
534
|
+
},
|
|
535
|
+
// Optional — connectivity & services
|
|
536
|
+
{ name: "Tailscale", category: "optional", run: checkTailscale },
|
|
537
|
+
{
|
|
538
|
+
name: "Firebase connectivity",
|
|
539
|
+
category: "optional",
|
|
540
|
+
run: checkFirebaseConnectivity,
|
|
541
|
+
},
|
|
542
|
+
{ name: "Data directory", category: "optional", run: checkDataDirectory },
|
|
543
|
+
// Platform-specific service checks
|
|
544
|
+
...(process.platform === "darwin"
|
|
545
|
+
? [{ name: "launchd service", category: "optional", run: checkLaunchdService }]
|
|
546
|
+
: []),
|
|
547
|
+
...(process.platform === "linux"
|
|
548
|
+
? [{ name: "systemd service", category: "optional", run: checkSystemdService }]
|
|
549
|
+
: []),
|
|
550
|
+
];
|
|
551
|
+
}
|
|
552
|
+
export async function runDoctor() {
|
|
553
|
+
const checks = getAllChecks();
|
|
554
|
+
const results = [];
|
|
555
|
+
for (const check of checks) {
|
|
556
|
+
const result = await check.run();
|
|
557
|
+
results.push({ ...result, category: check.category });
|
|
558
|
+
}
|
|
559
|
+
const allRequiredPassed = results
|
|
560
|
+
.filter((r) => r.category === "required")
|
|
561
|
+
.every((r) => r.status === "pass" || r.status === "warn");
|
|
562
|
+
return { results, allRequiredPassed };
|
|
563
|
+
}
|
|
564
|
+
// ---------------------------------------------------------------------------
|
|
565
|
+
// Output formatting
|
|
566
|
+
// ---------------------------------------------------------------------------
|
|
567
|
+
const SYMBOLS_TTY = {
|
|
568
|
+
pass: "\x1b[32m✓\x1b[0m",
|
|
569
|
+
fail: "\x1b[31m✗\x1b[0m",
|
|
570
|
+
warn: "\x1b[33m!\x1b[0m",
|
|
571
|
+
skip: "\x1b[90m-\x1b[0m",
|
|
572
|
+
};
|
|
573
|
+
const SYMBOLS_PLAIN = {
|
|
574
|
+
pass: "[OK]",
|
|
575
|
+
fail: "[FAIL]",
|
|
576
|
+
warn: "[WARN]",
|
|
577
|
+
skip: "[SKIP]",
|
|
578
|
+
};
|
|
579
|
+
function providerStatusIcon(p, sym) {
|
|
580
|
+
if (!p.installed)
|
|
581
|
+
return sym.skip;
|
|
582
|
+
if (!p.authenticated)
|
|
583
|
+
return sym.warn;
|
|
584
|
+
return sym.pass;
|
|
585
|
+
}
|
|
586
|
+
function providerStatusMessage(p) {
|
|
587
|
+
if (!p.installed)
|
|
588
|
+
return "Not installed";
|
|
589
|
+
const parts = [];
|
|
590
|
+
if (p.version)
|
|
591
|
+
parts.push(p.version);
|
|
592
|
+
if (p.authenticated) {
|
|
593
|
+
parts.push("(authenticated)");
|
|
594
|
+
}
|
|
595
|
+
else if (p.authMessage) {
|
|
596
|
+
parts.push(`(${p.authMessage})`);
|
|
597
|
+
}
|
|
598
|
+
return parts.join(" ") || "Installed";
|
|
599
|
+
}
|
|
600
|
+
export function printReport(report) {
|
|
601
|
+
const isTTY = process.stdout.isTTY ?? false;
|
|
602
|
+
const sym = isTTY ? SYMBOLS_TTY : SYMBOLS_PLAIN;
|
|
603
|
+
const NAME_WIDTH = 22;
|
|
604
|
+
console.log("");
|
|
605
|
+
console.log("ccpocket-bridge doctor");
|
|
606
|
+
console.log("======================");
|
|
607
|
+
// Required checks
|
|
608
|
+
const required = report.results.filter((r) => r.category === "required");
|
|
609
|
+
if (required.length > 0) {
|
|
610
|
+
console.log("");
|
|
611
|
+
console.log("Required:");
|
|
612
|
+
for (const r of required) {
|
|
613
|
+
const icon = sym[r.status];
|
|
614
|
+
const nameCol = r.name.padEnd(NAME_WIDTH);
|
|
615
|
+
console.log(` ${icon} ${nameCol} ${r.message}`);
|
|
616
|
+
// Print provider sub-items for CLI providers check
|
|
617
|
+
if (r.providers) {
|
|
618
|
+
for (const p of r.providers) {
|
|
619
|
+
const pIcon = providerStatusIcon(p, sym);
|
|
620
|
+
const pName = p.name.padEnd(NAME_WIDTH);
|
|
621
|
+
console.log(` ${pIcon} ${pName} ${providerStatusMessage(p)}`);
|
|
622
|
+
if (p.remediation) {
|
|
623
|
+
console.log(` → ${p.remediation}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
else if (r.remediation && (r.status === "fail" || r.status === "warn")) {
|
|
628
|
+
console.log(` → ${r.remediation}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// Optional checks
|
|
633
|
+
const optional = report.results.filter((r) => r.category === "optional");
|
|
634
|
+
if (optional.length > 0) {
|
|
635
|
+
console.log("");
|
|
636
|
+
console.log("Optional:");
|
|
637
|
+
for (const r of optional) {
|
|
638
|
+
const icon = sym[r.status];
|
|
639
|
+
const nameCol = r.name.padEnd(NAME_WIDTH);
|
|
640
|
+
console.log(` ${icon} ${nameCol} ${r.message}`);
|
|
641
|
+
if (r.remediation && (r.status === "fail" || r.status === "warn" || r.status === "skip")) {
|
|
642
|
+
console.log(` → ${r.remediation}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
// Summary
|
|
647
|
+
console.log("");
|
|
648
|
+
const failCount = report.results.filter((r) => r.status === "fail").length;
|
|
649
|
+
const warnCount = report.results.filter((r) => r.status === "warn").length;
|
|
650
|
+
if (report.allRequiredPassed) {
|
|
651
|
+
const msg = "All required checks passed.";
|
|
652
|
+
console.log(isTTY ? `\x1b[32m${msg}\x1b[0m` : msg);
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
const msg = `${failCount} required check(s) failed.`;
|
|
656
|
+
console.log(isTTY ? `\x1b[31m${msg}\x1b[0m` : msg);
|
|
657
|
+
}
|
|
658
|
+
if (warnCount > 0) {
|
|
659
|
+
console.log(`${warnCount} warning(s).`);
|
|
660
|
+
}
|
|
661
|
+
console.log("");
|
|
662
|
+
}
|
|
663
|
+
//# sourceMappingURL=doctor.js.map
|