@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.
Files changed (89) hide show
  1. package/README.md +67 -0
  2. package/dist/archive-store.d.ts +28 -0
  3. package/dist/archive-store.js +68 -0
  4. package/dist/archive-store.js.map +1 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +82 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/codex-process.d.ts +171 -0
  9. package/dist/codex-process.js +1928 -0
  10. package/dist/codex-process.js.map +1 -0
  11. package/dist/debug-trace-store.d.ts +15 -0
  12. package/dist/debug-trace-store.js +78 -0
  13. package/dist/debug-trace-store.js.map +1 -0
  14. package/dist/doctor.d.ts +58 -0
  15. package/dist/doctor.js +663 -0
  16. package/dist/doctor.js.map +1 -0
  17. package/dist/firebase-auth.d.ts +35 -0
  18. package/dist/firebase-auth.js +132 -0
  19. package/dist/firebase-auth.js.map +1 -0
  20. package/dist/gallery-store.d.ts +67 -0
  21. package/dist/gallery-store.js +333 -0
  22. package/dist/gallery-store.js.map +1 -0
  23. package/dist/image-store.d.ts +23 -0
  24. package/dist/image-store.js +142 -0
  25. package/dist/image-store.js.map +1 -0
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +191 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/mdns.d.ts +7 -0
  30. package/dist/mdns.js +49 -0
  31. package/dist/mdns.js.map +1 -0
  32. package/dist/parser.d.ts +465 -0
  33. package/dist/parser.js +251 -0
  34. package/dist/parser.js.map +1 -0
  35. package/dist/project-history.d.ts +10 -0
  36. package/dist/project-history.js +73 -0
  37. package/dist/project-history.js.map +1 -0
  38. package/dist/prompt-history-backup.d.ts +15 -0
  39. package/dist/prompt-history-backup.js +46 -0
  40. package/dist/prompt-history-backup.js.map +1 -0
  41. package/dist/proxy.d.ts +15 -0
  42. package/dist/proxy.js +95 -0
  43. package/dist/proxy.js.map +1 -0
  44. package/dist/push-i18n.d.ts +7 -0
  45. package/dist/push-i18n.js +75 -0
  46. package/dist/push-i18n.js.map +1 -0
  47. package/dist/push-relay.d.ts +29 -0
  48. package/dist/push-relay.js +70 -0
  49. package/dist/push-relay.js.map +1 -0
  50. package/dist/recording-store.d.ts +51 -0
  51. package/dist/recording-store.js +158 -0
  52. package/dist/recording-store.js.map +1 -0
  53. package/dist/screenshot.d.ts +28 -0
  54. package/dist/screenshot.js +98 -0
  55. package/dist/screenshot.js.map +1 -0
  56. package/dist/sdk-process.d.ts +180 -0
  57. package/dist/sdk-process.js +937 -0
  58. package/dist/sdk-process.js.map +1 -0
  59. package/dist/session.d.ts +142 -0
  60. package/dist/session.js +615 -0
  61. package/dist/session.js.map +1 -0
  62. package/dist/sessions-index.d.ts +128 -0
  63. package/dist/sessions-index.js +1767 -0
  64. package/dist/sessions-index.js.map +1 -0
  65. package/dist/setup-launchd.d.ts +8 -0
  66. package/dist/setup-launchd.js +109 -0
  67. package/dist/setup-launchd.js.map +1 -0
  68. package/dist/setup-systemd.d.ts +8 -0
  69. package/dist/setup-systemd.js +118 -0
  70. package/dist/setup-systemd.js.map +1 -0
  71. package/dist/startup-info.d.ts +8 -0
  72. package/dist/startup-info.js +92 -0
  73. package/dist/startup-info.js.map +1 -0
  74. package/dist/usage.d.ts +69 -0
  75. package/dist/usage.js +545 -0
  76. package/dist/usage.js.map +1 -0
  77. package/dist/version.d.ts +13 -0
  78. package/dist/version.js +43 -0
  79. package/dist/version.js.map +1 -0
  80. package/dist/websocket.d.ts +127 -0
  81. package/dist/websocket.js +2482 -0
  82. package/dist/websocket.js.map +1 -0
  83. package/dist/worktree-store.d.ts +25 -0
  84. package/dist/worktree-store.js +59 -0
  85. package/dist/worktree-store.js.map +1 -0
  86. package/dist/worktree.d.ts +47 -0
  87. package/dist/worktree.js +313 -0
  88. package/dist/worktree.js.map +1 -0
  89. 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