connectbase-client 3.50.0 → 3.51.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/dist/cli.js CHANGED
@@ -31,7 +31,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/cli.ts
32
32
  var cli_exports = {};
33
33
  __export(cli_exports, {
34
+ TunnelConnectSelector: () => TunnelConnectSelector,
34
35
  computeDeployDiff: () => computeDeployDiff,
36
+ getTunnelConnectCandidates: () => getTunnelConnectCandidates,
35
37
  guessMimeType: () => guessMimeType,
36
38
  isAllowedDeployExt: () => isAllowedDeployExt,
37
39
  isBinaryDeployExt: () => isBinaryDeployExt,
@@ -42,23 +44,33 @@ __export(cli_exports, {
42
44
  sha256Hex: () => sha256Hex
43
45
  });
44
46
  module.exports = __toCommonJS(cli_exports);
45
- var fs2 = __toESM(require("fs"));
46
- var path2 = __toESM(require("path"));
47
47
  var crypto = __toESM(require("crypto"));
48
- var https = __toESM(require("https"));
48
+ var fs2 = __toESM(require("fs"));
49
49
  var http = __toESM(require("http"));
50
+ var https = __toESM(require("https"));
51
+ var path2 = __toESM(require("path"));
50
52
  var readline = __toESM(require("readline"));
51
53
 
52
54
  // src/tunnel-utils.ts
53
55
  var fs = __toESM(require("fs"));
54
- var path = __toESM(require("path"));
55
56
  var os = __toESM(require("os"));
57
+ var path = __toESM(require("path"));
56
58
  function getTunnelLockDir() {
57
59
  const platform2 = os.platform();
58
60
  if (platform2 === "darwin") {
59
- return path.join(os.homedir(), "Library", "Application Support", "connectbase", "tunnel-locks");
61
+ return path.join(
62
+ os.homedir(),
63
+ "Library",
64
+ "Application Support",
65
+ "connectbase",
66
+ "tunnel-locks"
67
+ );
60
68
  } else if (platform2 === "win32") {
61
- return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "connectbase", "tunnel-locks");
69
+ return path.join(
70
+ process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"),
71
+ "connectbase",
72
+ "tunnel-locks"
73
+ );
62
74
  }
63
75
  const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
64
76
  return path.join(stateHome, "connectbase", "tunnel-locks");
@@ -95,7 +107,9 @@ function acquireTunnelLock(appID, port, force, version) {
95
107
  }
96
108
  }
97
109
  try {
98
- const existing = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
110
+ const existing = JSON.parse(
111
+ fs.readFileSync(lockPath, "utf-8")
112
+ );
99
113
  if (isProcessAlive(existing.pid)) {
100
114
  return `LOCKED:${existing.pid}:${existing.startedAt}:${existing.host}`;
101
115
  }
@@ -343,21 +357,35 @@ function validateFileSizes(files) {
343
357
  return { path: f.path, isBinary: f.isBinary, storedBytes, originalBytes };
344
358
  }).filter((f) => f.storedBytes > WEB_FILE_MAX_STORED_BYTES);
345
359
  if (oversized.length === 0) return;
346
- error(`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uD30C\uC77C\uB2F9 \uD06C\uAE30 \uD55C\uB3C4(\uC57D ${formatSize(WEB_FILE_MAX_STORED_BYTES)})\uB97C \uCD08\uACFC\uD55C \uD30C\uC77C\uC774 \uC788\uC5B4 \uBC30\uD3EC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4:`);
360
+ error(
361
+ `\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uD30C\uC77C\uB2F9 \uD06C\uAE30 \uD55C\uB3C4(\uC57D ${formatSize(WEB_FILE_MAX_STORED_BYTES)})\uB97C \uCD08\uACFC\uD55C \uD30C\uC77C\uC774 \uC788\uC5B4 \uBC30\uD3EC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4:`
362
+ );
347
363
  for (const f of oversized) {
348
364
  const detail = f.isBinary ? `${formatSize(f.originalBytes)} \u2192 base64 ${formatSize(f.storedBytes)}` : formatSize(f.storedBytes);
349
365
  error(` \u2022 ${f.path} (${detail})`);
350
366
  }
351
367
  log("");
352
- warn("\uC815\uC801 \uD638\uC2A4\uD305 \uD30C\uC77C\uC740 DB \uB2E8\uC77C \uC5D4\uD2B8\uB9AC(6 MiB)\uC5D0 \uC800\uC7A5\uB418\uBA70, \uBC14\uC774\uB108\uB9AC\uB294 base64(\uC57D 1.33x)\uB85C");
368
+ warn(
369
+ "\uC815\uC801 \uD638\uC2A4\uD305 \uD30C\uC77C\uC740 DB \uB2E8\uC77C \uC5D4\uD2B8\uB9AC(6 MiB)\uC5D0 \uC800\uC7A5\uB418\uBA70, \uBC14\uC774\uB108\uB9AC\uB294 base64(\uC57D 1.33x)\uB85C"
370
+ );
353
371
  warn("\uBD80\uD480\uC5B4 \uC6D0\uBCF8 \uAE30\uC900 \uC57D 4.5 MB \uAC00 \uC2E4\uC9C8 \uC0C1\uD55C\uC785\uB2C8\uB2E4.");
354
- warn(`\uB300\uC6A9\uB7C9 \uC5D0\uC14B(3D \uBAA8\uB378\xB7\uB3D9\uC601\uC0C1\xB7\uD070 \uD3F0\uD2B8 \uB4F1)\uC740 \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0(${colors.cyan}cb.storage${colors.reset}, presigned URL)\uC5D0`);
355
- warn("\uC62C\uB9AC\uACE0 URL \uB85C \uCC38\uC870\uD558\uC138\uC694. \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0\uB294 \uD30C\uC77C\uB2F9 \uACE0\uC815 \uC0C1\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
372
+ warn(
373
+ `\uB300\uC6A9\uB7C9 \uC5D0\uC14B(3D \uBAA8\uB378\xB7\uB3D9\uC601\uC0C1\xB7\uD070 \uD3F0\uD2B8 \uB4F1)\uC740 \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0(${colors.cyan}cb.storage${colors.reset}, presigned URL)\uC5D0`
374
+ );
375
+ warn(
376
+ "\uC62C\uB9AC\uACE0 URL \uB85C \uCC38\uC870\uD558\uC138\uC694. \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0\uB294 \uD30C\uC77C\uB2F9 \uACE0\uC815 \uC0C1\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."
377
+ );
356
378
  process.exit(1);
357
379
  }
358
380
  async function requestDeployPresign(baseStorageUrl, headers, files, timeoutMs, dev = false) {
359
381
  const body = JSON.stringify(dev ? { files, dev: true } : { files });
360
- const res = await makeRequest(`${baseStorageUrl}/deploy/presign`, "POST", headers, body, { timeoutMs });
382
+ const res = await makeRequest(
383
+ `${baseStorageUrl}/deploy/presign`,
384
+ "POST",
385
+ headers,
386
+ body,
387
+ { timeoutMs }
388
+ );
361
389
  if (res.status === 404) return null;
362
390
  if (res.status < 200 || res.status >= 300) {
363
391
  const data = res.data;
@@ -413,22 +441,33 @@ function putToPresignedUrl(uploadUrl, body, contentType, timeoutMs) {
413
441
  socket.setKeepAlive(true, 3e4);
414
442
  });
415
443
  req.setTimeout(timeoutMs, () => {
416
- req.destroy(new Error(`\uC5C5\uB85C\uB4DC \uC2DC\uAC04 \uCD08\uACFC (${Math.round(timeoutMs / 1e3)}\uCD08)`));
444
+ req.destroy(
445
+ new Error(`\uC5C5\uB85C\uB4DC \uC2DC\uAC04 \uCD08\uACFC (${Math.round(timeoutMs / 1e3)}\uCD08)`)
446
+ );
417
447
  });
418
448
  req.on("error", reject);
419
449
  req.end(body);
420
450
  });
421
451
  }
422
452
  async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, timeoutMs) {
423
- const oversized = diff.upsert.filter((f) => Buffer.byteLength(f.content, "utf8") > WEB_FILE_MAX_STORED_BYTES);
453
+ const oversized = diff.upsert.filter(
454
+ (f) => Buffer.byteLength(f.content, "utf8") > WEB_FILE_MAX_STORED_BYTES
455
+ );
424
456
  if (oversized.length === 0) return;
425
457
  const largeText = oversized.filter((f) => !f.is_binary);
426
458
  const largeBinaries = oversized.filter((f) => f.is_binary);
427
459
  if (largeText.length > 0) {
428
- error(`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uD30C\uC77C\uB2F9 \uD06C\uAE30 \uD55C\uB3C4(\uC57D ${formatSize(WEB_FILE_MAX_STORED_BYTES)})\uB97C \uCD08\uACFC\uD55C \uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC788\uC5B4 \uBC30\uD3EC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4:`);
429
- for (const f of largeText) error(` \u2022 ${f.path} (${formatSize(Buffer.byteLength(f.content, "utf8"))})`);
460
+ error(
461
+ `\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uD30C\uC77C\uB2F9 \uD06C\uAE30 \uD55C\uB3C4(\uC57D ${formatSize(WEB_FILE_MAX_STORED_BYTES)})\uB97C \uCD08\uACFC\uD55C \uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC788\uC5B4 \uBC30\uD3EC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4:`
462
+ );
463
+ for (const f of largeText)
464
+ error(
465
+ ` \u2022 ${f.path} (${formatSize(Buffer.byteLength(f.content, "utf8"))})`
466
+ );
430
467
  log("");
431
- warn("\uD070 JS/CSS \uB294 \uCF54\uB4DC \uC2A4\uD50C\uB9AC\uD305\uC73C\uB85C \uBD84\uD560\uD558\uC138\uC694. (\uBC14\uC774\uB108\uB9AC\uC640 \uB2EC\uB9AC \uD14D\uC2A4\uD2B8\uB294 \uC624\uBE0C\uC81D\uD2B8 \uC2A4\uD1A0\uB9AC\uC9C0\uB85C \uC624\uD504\uB85C\uB4DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.)");
468
+ warn(
469
+ "\uD070 JS/CSS \uB294 \uCF54\uB4DC \uC2A4\uD50C\uB9AC\uD305\uC73C\uB85C \uBD84\uD560\uD558\uC138\uC694. (\uBC14\uC774\uB108\uB9AC\uC640 \uB2EC\uB9AC \uD14D\uC2A4\uD2B8\uB294 \uC624\uBE0C\uC81D\uD2B8 \uC2A4\uD1A0\uB9AC\uC9C0\uB85C \uC624\uD504\uB85C\uB4DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.)"
470
+ );
432
471
  process.exit(1);
433
472
  }
434
473
  if (largeBinaries.length === 0) return;
@@ -442,20 +481,36 @@ async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, ti
442
481
  }));
443
482
  let presigned;
444
483
  try {
445
- presigned = await requestDeployPresign(baseStorageUrl, headers, presignFiles, timeoutMs);
484
+ presigned = await requestDeployPresign(
485
+ baseStorageUrl,
486
+ headers,
487
+ presignFiles,
488
+ timeoutMs
489
+ );
446
490
  } catch (err) {
447
- error(`\uB300\uC6A9\uB7C9 \uD30C\uC77C \uC5C5\uB85C\uB4DC \uC900\uBE44 \uC2E4\uD328: ${err instanceof Error ? err.message : err}`);
491
+ error(
492
+ `\uB300\uC6A9\uB7C9 \uD30C\uC77C \uC5C5\uB85C\uB4DC \uC900\uBE44 \uC2E4\uD328: ${err instanceof Error ? err.message : err}`
493
+ );
448
494
  process.exit(1);
449
495
  }
450
496
  if (presigned === null) {
451
- error(`\uC11C\uBC84\uAC00 \uB300\uC6A9\uB7C9 \uD30C\uC77C \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 \uB2E4\uC74C \uD30C\uC77C\uC744 \uBC30\uD3EC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4(\uC57D ${formatSize(WEB_FILE_MAX_STORED_BYTES)} \uCD08\uACFC):`);
452
- for (const f of largeBinaries) error(` \u2022 ${f.path} (${formatSize(Buffer.from(f.content, "base64").length)})`);
497
+ error(
498
+ `\uC11C\uBC84\uAC00 \uB300\uC6A9\uB7C9 \uD30C\uC77C \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 \uB2E4\uC74C \uD30C\uC77C\uC744 \uBC30\uD3EC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4(\uC57D ${formatSize(WEB_FILE_MAX_STORED_BYTES)} \uCD08\uACFC):`
499
+ );
500
+ for (const f of largeBinaries)
501
+ error(
502
+ ` \u2022 ${f.path} (${formatSize(Buffer.from(f.content, "base64").length)})`
503
+ );
453
504
  log("");
454
- warn(`\uB300\uC6A9\uB7C9 \uC5D0\uC14B\uC740 \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0(${colors.cyan}cb.storage${colors.reset})\uC5D0 \uC62C\uB9AC\uACE0 URL \uB85C \uCC38\uC870\uD558\uAC70\uB098, \uC11C\uBC84 \uC5C5\uB370\uC774\uD2B8 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`);
505
+ warn(
506
+ `\uB300\uC6A9\uB7C9 \uC5D0\uC14B\uC740 \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0(${colors.cyan}cb.storage${colors.reset})\uC5D0 \uC62C\uB9AC\uACE0 URL \uB85C \uCC38\uC870\uD558\uAC70\uB098, \uC11C\uBC84 \uC5C5\uB370\uC774\uD2B8 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`
507
+ );
455
508
  process.exit(1);
456
509
  }
457
510
  const entryByPath = new Map(presigned.map((p) => [p.path, p]));
458
- info(`\uB300\uC6A9\uB7C9 \uBC14\uC774\uB108\uB9AC ${largeBinaries.length}\uAC1C\uB97C Object Storage \uB85C \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uD569\uB2C8\uB2E4...`);
511
+ info(
512
+ `\uB300\uC6A9\uB7C9 \uBC14\uC774\uB108\uB9AC ${largeBinaries.length}\uAC1C\uB97C Object Storage \uB85C \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uD569\uB2C8\uB2E4...`
513
+ );
459
514
  for (const f of largeBinaries) {
460
515
  const entry = entryByPath.get(f.path);
461
516
  if (!entry) {
@@ -464,16 +519,25 @@ async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, ti
464
519
  }
465
520
  const bytes = Buffer.from(f.content, "base64");
466
521
  if (entry.already_exists) {
467
- success(`\uC5C5\uB85C\uB4DC \uC0DD\uB7B5(\uAE30\uC874 blob \uC7AC\uC0AC\uC6A9): ${f.path} (${formatSize(bytes.length)})`);
522
+ success(
523
+ `\uC5C5\uB85C\uB4DC \uC0DD\uB7B5(\uAE30\uC874 blob \uC7AC\uC0AC\uC6A9): ${f.path} (${formatSize(bytes.length)})`
524
+ );
468
525
  } else {
469
526
  if (!entry.upload_url) {
470
527
  error(`presign \uC751\uB2F5\uC5D0 \uC5C5\uB85C\uB4DC URL \uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${f.path}`);
471
528
  process.exit(1);
472
529
  }
473
530
  try {
474
- await putToPresignedUrl(entry.upload_url, bytes, guessMimeType(f.path), timeoutMs);
531
+ await putToPresignedUrl(
532
+ entry.upload_url,
533
+ bytes,
534
+ guessMimeType(f.path),
535
+ timeoutMs
536
+ );
475
537
  } catch (err) {
476
- error(`\uC5C5\uB85C\uB4DC \uC2E4\uD328 (${f.path}): ${err instanceof Error ? err.message : err}`);
538
+ error(
539
+ `\uC5C5\uB85C\uB4DC \uC2E4\uD328 (${f.path}): ${err instanceof Error ? err.message : err}`
540
+ );
477
541
  process.exit(1);
478
542
  }
479
543
  success(`\uC9C1\uC811 \uC5C5\uB85C\uB4DC: ${f.path} (${formatSize(bytes.length)})`);
@@ -484,7 +548,9 @@ async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, ti
484
548
  }
485
549
  }
486
550
  async function offloadDevLargeBinaries(baseStorageUrl, headers, files, timeoutMs) {
487
- const large = files.filter((f) => f.isBinary && Buffer.byteLength(f.content, "utf8") > WEB_FILE_MAX_STORED_BYTES);
551
+ const large = files.filter(
552
+ (f) => f.isBinary && Buffer.byteLength(f.content, "utf8") > WEB_FILE_MAX_STORED_BYTES
553
+ );
488
554
  if (large.length === 0) return files;
489
555
  const presignFiles = large.map((f) => ({
490
556
  path: f.path,
@@ -493,21 +559,35 @@ async function offloadDevLargeBinaries(baseStorageUrl, headers, files, timeoutMs
493
559
  }));
494
560
  let presigned;
495
561
  try {
496
- presigned = await requestDeployPresign(baseStorageUrl, headers, presignFiles, timeoutMs, true);
562
+ presigned = await requestDeployPresign(
563
+ baseStorageUrl,
564
+ headers,
565
+ presignFiles,
566
+ timeoutMs,
567
+ true
568
+ );
497
569
  } catch (err) {
498
- error(`dev \uB300\uC6A9\uB7C9 \uD30C\uC77C \uC5C5\uB85C\uB4DC \uC900\uBE44 \uC2E4\uD328: ${err instanceof Error ? err.message : err}`);
570
+ error(
571
+ `dev \uB300\uC6A9\uB7C9 \uD30C\uC77C \uC5C5\uB85C\uB4DC \uC900\uBE44 \uC2E4\uD328: ${err instanceof Error ? err.message : err}`
572
+ );
499
573
  process.exit(1);
500
574
  }
501
575
  if (presigned === null) {
502
- info("\uC11C\uBC84\uAC00 \uB300\uC6A9\uB7C9 \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uB97C \uBBF8\uC9C0\uC6D0 \u2014 dev \uB294 \uC804\uB7C9 \uC5C5\uB85C\uB4DC\uB85C \uC9C4\uD589\uD569\uB2C8\uB2E4.");
576
+ info(
577
+ "\uC11C\uBC84\uAC00 \uB300\uC6A9\uB7C9 \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uB97C \uBBF8\uC9C0\uC6D0 \u2014 dev \uB294 \uC804\uB7C9 \uC5C5\uB85C\uB4DC\uB85C \uC9C4\uD589\uD569\uB2C8\uB2E4."
578
+ );
503
579
  return files;
504
580
  }
505
581
  if (!presigned.every((p) => p.object_key.includes("/dev/"))) {
506
- info("\uC11C\uBC84\uAC00 dev \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uB97C \uBBF8\uC9C0\uC6D0(\uC751\uB2F5 \uD0A4\uAC00 dev \uACBD\uB85C \uC544\uB2D8) \u2014 dev \uB294 \uC804\uB7C9 \uC5C5\uB85C\uB4DC\uB85C \uC9C4\uD589\uD569\uB2C8\uB2E4.");
582
+ info(
583
+ "\uC11C\uBC84\uAC00 dev \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uB97C \uBBF8\uC9C0\uC6D0(\uC751\uB2F5 \uD0A4\uAC00 dev \uACBD\uB85C \uC544\uB2D8) \u2014 dev \uB294 \uC804\uB7C9 \uC5C5\uB85C\uB4DC\uB85C \uC9C4\uD589\uD569\uB2C8\uB2E4."
584
+ );
507
585
  return files;
508
586
  }
509
587
  const urlByPath = new Map(presigned.map((p) => [p.path, p.upload_url]));
510
- info(`\uB300\uC6A9\uB7C9 \uBC14\uC774\uB108\uB9AC ${large.length}\uAC1C\uB97C dev Object Storage \uB85C \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uD569\uB2C8\uB2E4...`);
588
+ info(
589
+ `\uB300\uC6A9\uB7C9 \uBC14\uC774\uB108\uB9AC ${large.length}\uAC1C\uB97C dev Object Storage \uB85C \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uD569\uB2C8\uB2E4...`
590
+ );
511
591
  const offloaded = /* @__PURE__ */ new Set();
512
592
  for (const f of large) {
513
593
  const uploadUrl = urlByPath.get(f.path);
@@ -517,9 +597,16 @@ async function offloadDevLargeBinaries(baseStorageUrl, headers, files, timeoutMs
517
597
  }
518
598
  const bytes = Buffer.from(f.content, "base64");
519
599
  try {
520
- await putToPresignedUrl(uploadUrl, bytes, guessMimeType(f.path), timeoutMs);
600
+ await putToPresignedUrl(
601
+ uploadUrl,
602
+ bytes,
603
+ guessMimeType(f.path),
604
+ timeoutMs
605
+ );
521
606
  } catch (err) {
522
- error(`dev \uC5C5\uB85C\uB4DC \uC2E4\uD328 (${f.path}): ${err instanceof Error ? err.message : err}`);
607
+ error(
608
+ `dev \uC5C5\uB85C\uB4DC \uC2E4\uD328 (${f.path}): ${err instanceof Error ? err.message : err}`
609
+ );
523
610
  process.exit(1);
524
611
  }
525
612
  success(`\uC9C1\uC811 \uC5C5\uB85C\uB4DC(dev): ${f.path} (${formatSize(bytes.length)})`);
@@ -603,8 +690,12 @@ async function deploy(directory, config, isDev = false, deployOpts = {}) {
603
690
  try {
604
691
  const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
605
692
  if (pkg.scripts?.build) {
606
- warn(`\uBC30\uD3EC \uC804 \uBE4C\uB4DC\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694: ${colors.cyan}npm run build && npx connectbase deploy${colors.reset}`);
607
- warn(`\uB610\uB294 package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uB4F1\uB85D\uD558\uC138\uC694: ${colors.cyan}npx connectbase init${colors.reset}`);
693
+ warn(
694
+ `\uBC30\uD3EC \uC804 \uBE4C\uB4DC\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694: ${colors.cyan}npm run build && npx connectbase deploy${colors.reset}`
695
+ );
696
+ warn(
697
+ `\uB610\uB294 package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uB4F1\uB85D\uD558\uC138\uC694: ${colors.cyan}npx connectbase init${colors.reset}`
698
+ );
608
699
  }
609
700
  } catch {
610
701
  }
@@ -628,37 +719,77 @@ async function deploy(directory, config, isDev = false, deployOpts = {}) {
628
719
  }
629
720
  const totalSize = files.reduce((sum, f) => sum + f.content.length, 0);
630
721
  const sizeKB = (totalSize / 1024).toFixed(1);
631
- log(`${colors.green}\u2713${colors.reset} ${files.length}\uAC1C \uD30C\uC77C \uBC1C\uACAC (${sizeKB} KB)`);
722
+ log(
723
+ `${colors.green}\u2713${colors.reset} ${files.length}\uAC1C \uD30C\uC77C \uBC1C\uACAC (${sizeKB} KB)`
724
+ );
632
725
  const envLabel = isDev ? "Dev" : "Prod";
633
726
  const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
634
727
  const headers = { "X-Public-Key": config.publicKey };
635
728
  const timeoutMs = computeDeployTimeout(totalSize, deployOpts.timeoutMs);
636
- log(`${colors.dim}\uD0C0\uC784\uC544\uC6C3: ${Math.round(timeoutMs / 1e3)}\uCD08${colors.reset}`);
729
+ log(
730
+ `${colors.dim}\uD0C0\uC784\uC544\uC6C3: ${Math.round(timeoutMs / 1e3)}\uCD08${colors.reset}`
731
+ );
637
732
  if (isDev) {
638
- const toSend = await offloadDevLargeBinaries(baseStorageUrl, headers, files, timeoutMs);
639
- await fullDeploy(baseStorageUrl, headers, toSend, envLabel, "deploy/dev", timeoutMs);
733
+ const toSend = await offloadDevLargeBinaries(
734
+ baseStorageUrl,
735
+ headers,
736
+ files,
737
+ timeoutMs
738
+ );
739
+ await fullDeploy(
740
+ baseStorageUrl,
741
+ headers,
742
+ toSend,
743
+ envLabel,
744
+ "deploy/dev",
745
+ timeoutMs
746
+ );
640
747
  return;
641
748
  }
642
749
  try {
643
750
  const manifest = await tryFetchManifest(baseStorageUrl, headers);
644
751
  if (!manifest) {
645
752
  validateFileSizes(files);
646
- await fullDeploy(baseStorageUrl, headers, files, envLabel, "deploy", timeoutMs);
753
+ await fullDeploy(
754
+ baseStorageUrl,
755
+ headers,
756
+ files,
757
+ envLabel,
758
+ "deploy",
759
+ timeoutMs
760
+ );
647
761
  return;
648
762
  }
649
763
  const diff = computeDeployDiff(files, manifest);
650
764
  if (diff.upsert.length === 0 && diff.delete.length === 0) {
651
765
  success(`\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C (${files.length}\uAC1C \uD30C\uC77C \uC77C\uCE58)`);
652
- log(`
766
+ log(
767
+ `
653
768
  ${colors.cyan}URL: https://${config.storageId}.web.connectbase.world${colors.reset}
654
- `);
769
+ `
770
+ );
655
771
  return;
656
772
  }
657
- await offloadLargeBinaries(baseStorageUrl, headers, diff, envLabel, timeoutMs);
658
- info(`\uBCC0\uACBD: ${colors.green}+${diff.upsert.length}${colors.reset} / ${colors.red}-${diff.delete.length}${colors.reset} (\uC804\uCCB4 ${files.length}\uAC1C \uC911)`);
773
+ await offloadLargeBinaries(
774
+ baseStorageUrl,
775
+ headers,
776
+ diff,
777
+ envLabel,
778
+ timeoutMs
779
+ );
780
+ info(
781
+ `\uBCC0\uACBD: ${colors.green}+${diff.upsert.length}${colors.reset} / ${colors.red}-${diff.delete.length}${colors.reset} (\uC804\uCCB4 ${files.length}\uAC1C \uC911)`
782
+ );
659
783
  const uploadSize = diff.upsert.reduce((s, f) => s + f.content.length, 0);
660
784
  info(`\uC5C5\uB85C\uB4DC \uD06C\uAE30: ${(uploadSize / 1024).toFixed(1)} KB`);
661
- await incrementalDeploy(baseStorageUrl, headers, diff, manifest.revision, envLabel, timeoutMs);
785
+ await incrementalDeploy(
786
+ baseStorageUrl,
787
+ headers,
788
+ diff,
789
+ manifest.revision,
790
+ envLabel,
791
+ timeoutMs
792
+ );
662
793
  } catch (err) {
663
794
  process.stdout.write("\r \r");
664
795
  error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
@@ -666,7 +797,11 @@ ${colors.cyan}URL: https://${config.storageId}.web.connectbase.world${colors.res
666
797
  }
667
798
  }
668
799
  async function tryFetchManifest(baseStorageUrl, headers) {
669
- const res = await makeRequest(`${baseStorageUrl}/deploy/manifest`, "GET", headers);
800
+ const res = await makeRequest(
801
+ `${baseStorageUrl}/deploy/manifest`,
802
+ "GET",
803
+ headers
804
+ );
670
805
  if (res.status === 404) return null;
671
806
  if (res.status < 200 || res.status >= 300) {
672
807
  const data = res.data;
@@ -707,18 +842,28 @@ function startUploadHeartbeat(envLabel, suffix = "\uBC30\uD3EC \uC911") {
707
842
  const startedAt = Date.now();
708
843
  return setInterval(() => {
709
844
  const elapsed = Math.floor((Date.now() - startedAt) / 1e3);
710
- process.stdout.write(`\r${colors.blue}\u27F3${colors.reset} ${envLabel} ${suffix}... ${colors.dim}(${elapsed}s)${colors.reset}`);
845
+ process.stdout.write(
846
+ `\r${colors.blue}\u27F3${colors.reset} ${envLabel} ${suffix}... ${colors.dim}(${elapsed}s)${colors.reset}`
847
+ );
711
848
  }, 1e3);
712
849
  }
713
850
  async function incrementalDeploy(baseStorageUrl, headers, diff, baseRevision, envLabel, timeoutMs) {
714
- process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC911...`);
851
+ process.stdout.write(
852
+ `${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC911...`
853
+ );
715
854
  let heartbeat = startUploadHeartbeat(envLabel, "\uC99D\uBD84 \uBC30\uD3EC \uC911");
716
855
  const body = JSON.stringify({
717
856
  upsert: diff.upsert,
718
857
  delete: diff.delete,
719
858
  base_revision: baseRevision
720
859
  });
721
- const res = await makeRequest(`${baseStorageUrl}/deploy/incremental`, "POST", headers, body, { timeoutMs });
860
+ const res = await makeRequest(
861
+ `${baseStorageUrl}/deploy/incremental`,
862
+ "POST",
863
+ headers,
864
+ body,
865
+ { timeoutMs }
866
+ );
722
867
  if (heartbeat) clearInterval(heartbeat);
723
868
  process.stdout.write("\r \r");
724
869
  if (res.status === 409) {
@@ -733,9 +878,17 @@ async function incrementalDeploy(baseStorageUrl, headers, diff, baseRevision, en
733
878
  delete: diff.delete,
734
879
  base_revision: manifest.revision
735
880
  });
736
- process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC7AC\uC2DC\uB3C4...`);
881
+ process.stdout.write(
882
+ `${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC7AC\uC2DC\uB3C4...`
883
+ );
737
884
  heartbeat = startUploadHeartbeat(envLabel, "\uC99D\uBD84 \uBC30\uD3EC \uC7AC\uC2DC\uB3C4");
738
- const res2 = await makeRequest(`${baseStorageUrl}/deploy/incremental`, "POST", headers, body2, { timeoutMs });
885
+ const res2 = await makeRequest(
886
+ `${baseStorageUrl}/deploy/incremental`,
887
+ "POST",
888
+ headers,
889
+ body2,
890
+ { timeoutMs }
891
+ );
739
892
  if (heartbeat) clearInterval(heartbeat);
740
893
  process.stdout.write("\r \r");
741
894
  handleDeployResponse(res2, envLabel);
@@ -753,7 +906,13 @@ async function fullDeploy(baseStorageUrl, headers, files, envLabel, endpoint, ti
753
906
  is_binary: f.isBinary
754
907
  }))
755
908
  });
756
- const res = await makeRequest(`${baseStorageUrl}/${endpoint}`, "POST", headers, body, { timeoutMs });
909
+ const res = await makeRequest(
910
+ `${baseStorageUrl}/${endpoint}`,
911
+ "POST",
912
+ headers,
913
+ body,
914
+ { timeoutMs }
915
+ );
757
916
  if (heartbeat) clearInterval(heartbeat);
758
917
  process.stdout.write("\r \r");
759
918
  handleDeployResponse(res, envLabel);
@@ -765,16 +924,25 @@ function handleDeployResponse(response, envLabel) {
765
924
  if (dep?.deployment_id) {
766
925
  if (dep.promoted) {
767
926
  success(`${envLabel} \uBC30\uD3EC \uC644\uB8CC! (release #${dep.seq})`);
768
- if (dep.production_url) log(`
769
- ${colors.cyan}Production: ${dep.production_url}${colors.reset}`);
770
- if (dep.qa_url) log(`${colors.dim}QA: ${dep.qa_url}${colors.reset}
927
+ if (dep.production_url)
928
+ log(
929
+ `
930
+ ${colors.cyan}Production: ${dep.production_url}${colors.reset}`
931
+ );
932
+ if (dep.qa_url)
933
+ log(`${colors.dim}QA: ${dep.qa_url}${colors.reset}
771
934
  `);
772
935
  } else {
773
- success(`QA \uD658\uACBD\uC5D0 \uBC30\uD3EC\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (release #${dep.seq}) \u2014 production \uC740 \uC544\uC9C1 \uC774\uC804 \uBC84\uC804\uC785\uB2C8\uB2E4`);
774
- if (dep.qa_url) log(`
936
+ success(
937
+ `QA \uD658\uACBD\uC5D0 \uBC30\uD3EC\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (release #${dep.seq}) \u2014 production \uC740 \uC544\uC9C1 \uC774\uC804 \uBC84\uC804\uC785\uB2C8\uB2E4`
938
+ );
939
+ if (dep.qa_url)
940
+ log(`
775
941
  ${colors.yellow}QA URL: ${dep.qa_url}${colors.reset}`);
776
- log(`${colors.dim}\uAC80\uC99D \uD6C4 \uBC18\uC601: ${colors.cyan}npx connectbase promote${colors.reset}
777
- `);
942
+ log(
943
+ `${colors.dim}\uAC80\uC99D \uD6C4 \uBC18\uC601: ${colors.cyan}npx connectbase promote${colors.reset}
944
+ `
945
+ );
778
946
  }
779
947
  return;
780
948
  }
@@ -798,11 +966,22 @@ ${colors.cyan}URL: ${data.url}${colors.reset}
798
966
  async function promote(config, deploymentId) {
799
967
  const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
800
968
  const headers = { "X-Public-Key": config.publicKey };
801
- info(deploymentId ? `release ${deploymentId} \uB97C production \uC73C\uB85C \uC2B9\uACA9\uD569\uB2C8\uB2E4...` : "\uCD5C\uC2E0 \uBC30\uD3EC\uB97C production \uC73C\uB85C \uC2B9\uACA9\uD569\uB2C8\uB2E4...");
802
- const body = JSON.stringify(deploymentId ? { deployment_id: deploymentId } : {});
803
- const res = await makeRequest(`${baseStorageUrl}/promote`, "POST", headers, body);
969
+ info(
970
+ deploymentId ? `release ${deploymentId} \uB97C production \uC73C\uB85C \uC2B9\uACA9\uD569\uB2C8\uB2E4...` : "\uCD5C\uC2E0 \uBC30\uD3EC\uB97C production \uC73C\uB85C \uC2B9\uACA9\uD569\uB2C8\uB2E4..."
971
+ );
972
+ const body = JSON.stringify(
973
+ deploymentId ? { deployment_id: deploymentId } : {}
974
+ );
975
+ const res = await makeRequest(
976
+ `${baseStorageUrl}/promote`,
977
+ "POST",
978
+ headers,
979
+ body
980
+ );
804
981
  if (res.status === 404 && !res.data?.error) {
805
- error("\uC11C\uBC84\uAC00 promote \uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (\uAD6C\uBC84\uC804 \uC11C\uBC84) \u2014 \uC11C\uBC84 \uC5C5\uB370\uC774\uD2B8 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694");
982
+ error(
983
+ "\uC11C\uBC84\uAC00 promote \uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (\uAD6C\uBC84\uC804 \uC11C\uBC84) \u2014 \uC11C\uBC84 \uC5C5\uB370\uC774\uD2B8 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694"
984
+ );
806
985
  process.exit(1);
807
986
  }
808
987
  if (res.status < 200 || res.status >= 300) {
@@ -822,7 +1001,11 @@ ${colors.cyan}Production: ${data.production_url}${colors.reset}
822
1001
  async function listReleases(config) {
823
1002
  const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
824
1003
  const headers = { "X-Public-Key": config.publicKey };
825
- const res = await makeRequest(`${baseStorageUrl}/deployments`, "GET", headers);
1004
+ const res = await makeRequest(
1005
+ `${baseStorageUrl}/deployments`,
1006
+ "GET",
1007
+ headers
1008
+ );
826
1009
  if (res.status < 200 || res.status >= 300) {
827
1010
  const data2 = res.data;
828
1011
  const msg = typeof data2 === "object" && data2 !== null ? data2.error || data2.message || JSON.stringify(data2) : `HTTP ${res.status}`;
@@ -834,12 +1017,15 @@ async function listReleases(config) {
834
1017
  info("\uBC30\uD3EC \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 deploy \uD558\uC138\uC694.");
835
1018
  return;
836
1019
  }
837
- log(`
1020
+ log(
1021
+ `
838
1022
  ${colors.cyan}\uBC30\uD3EC \uC774\uB825${colors.reset} (promote_mode: ${data.promote_mode || "auto"}, \uCD1D ${data.total}\uAC1C)
839
- `);
1023
+ `
1024
+ );
840
1025
  for (const d of data.deployments) {
841
1026
  const markers = [];
842
- if (d.is_production) markers.push(`${colors.green}production${colors.reset}`);
1027
+ if (d.is_production)
1028
+ markers.push(`${colors.green}production${colors.reset}`);
843
1029
  if (d.is_qa) markers.push(`${colors.yellow}qa${colors.reset}`);
844
1030
  const marker = markers.length > 0 ? ` \u2190 ${markers.join(", ")}` : "";
845
1031
  const statusColor = d.status === "ready" ? colors.green : d.status === "failed" ? colors.red : colors.yellow;
@@ -848,10 +1034,14 @@ ${colors.cyan}\uBC30\uD3EC \uC774\uB825${colors.reset} (promote_mode: ${data.pro
848
1034
  );
849
1035
  }
850
1036
  log("");
851
- if (data.production_url) log(`${colors.cyan}Production: ${data.production_url}${colors.reset}`);
852
- if (data.qa_url) log(`${colors.yellow}QA: ${data.qa_url}${colors.reset}`);
853
- log(`${colors.dim}\uB864\uBC31: npx connectbase promote <deployment_id>${colors.reset}
854
- `);
1037
+ if (data.production_url)
1038
+ log(`${colors.cyan}Production: ${data.production_url}${colors.reset}`);
1039
+ if (data.qa_url)
1040
+ log(`${colors.yellow}QA: ${data.qa_url}${colors.reset}`);
1041
+ log(
1042
+ `${colors.dim}\uB864\uBC31: npx connectbase promote <deployment_id>${colors.reset}
1043
+ `
1044
+ );
855
1045
  }
856
1046
  function prompt(question) {
857
1047
  const rl = readline.createInterface({
@@ -870,7 +1060,10 @@ function promptSecret(question) {
870
1060
  process.stdout.write(question);
871
1061
  const input = [];
872
1062
  if (!process.stdin.isTTY) {
873
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1063
+ const rl = readline.createInterface({
1064
+ input: process.stdin,
1065
+ output: process.stdout
1066
+ });
874
1067
  rl.question("", (answer) => {
875
1068
  rl.close();
876
1069
  resolve2(answer.trim());
@@ -956,7 +1149,9 @@ ${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC13
956
1149
  const spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
957
1150
  while (attempts < maxAttempts) {
958
1151
  const frame = spinnerFrames[attempts % spinnerFrames.length];
959
- process.stdout.write(`\r${colors.blue}${frame}${colors.reset} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC2B9\uC778 \uB300\uAE30 \uC911...`);
1152
+ process.stdout.write(
1153
+ `\r${colors.blue}${frame}${colors.reset} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC2B9\uC778 \uB300\uAE30 \uC911...`
1154
+ );
960
1155
  await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
961
1156
  try {
962
1157
  const pollRes = await makeRequest(
@@ -968,17 +1163,23 @@ ${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC13
968
1163
  consecutive404 = 0;
969
1164
  const data = pollRes.data;
970
1165
  if (data.status === "approved" && data.secret_key) {
971
- process.stdout.write("\r \r");
1166
+ process.stdout.write(
1167
+ "\r \r"
1168
+ );
972
1169
  success("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!");
973
1170
  return data.secret_key;
974
1171
  }
975
1172
  if (data.status === "expired") {
976
- process.stdout.write("\r \r");
1173
+ process.stdout.write(
1174
+ "\r \r"
1175
+ );
977
1176
  error("\uC778\uC99D \uC138\uC158\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
978
1177
  process.exit(1);
979
1178
  }
980
1179
  if (data.status === "denied") {
981
- process.stdout.write("\r \r");
1180
+ process.stdout.write(
1181
+ "\r \r"
1182
+ );
982
1183
  error("\uC778\uC99D\uC774 \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
983
1184
  process.exit(1);
984
1185
  }
@@ -986,7 +1187,9 @@ ${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC13
986
1187
  if (pollRes.status === 404) {
987
1188
  consecutive404++;
988
1189
  if (consecutive404 >= max404Retries) {
989
- process.stdout.write("\r \r");
1190
+ process.stdout.write(
1191
+ "\r \r"
1192
+ );
990
1193
  error("\uC778\uC99D \uC138\uC158\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
991
1194
  process.exit(1);
992
1195
  }
@@ -1007,14 +1210,20 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
1007
1210
  const projectRoot = getProjectRoot();
1008
1211
  if (cwd !== projectRoot) {
1009
1212
  info(`\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uAC10\uC9C0: ${projectRoot}`);
1010
- info(`MCP/\uBB38\uC11C\uB294 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0, \uBC30\uD3EC \uC124\uC815\uC740 \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0 \uC0DD\uC131\uB429\uB2C8\uB2E4
1011
- `);
1213
+ info(
1214
+ `MCP/\uBB38\uC11C\uB294 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0, \uBC30\uD3EC \uC124\uC815\uC740 \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0 \uC0DD\uC131\uB429\uB2C8\uB2E4
1215
+ `
1216
+ );
1012
1217
  }
1013
1218
  const nonInteractive = opts.yes === true;
1014
1219
  if (!process.stdin.isTTY && !nonInteractive) {
1015
1220
  error("\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC785\uB2C8\uB2E4. --yes \uD50C\uB798\uADF8\uC640 \uD568\uAED8 \uD544\uC694\uD55C \uAC12\uC744 \uC9C0\uC815\uD558\uC138\uC694");
1016
- log(`${colors.dim}\uC608: npx connectbase init --yes --secret-key cb_sk_... [--app <\uC571ID>] [--storage <\uC2A4\uD1A0\uB9AC\uC9C0ID>] [--deploy-dir ./dist]${colors.reset}`);
1017
- log(`${colors.dim}Secret Key\uB294 CONNECTBASE_SECRET_KEY \uD658\uACBD\uBCC0\uC218\uB85C\uB3C4 \uC804\uB2EC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4${colors.reset}`);
1221
+ log(
1222
+ `${colors.dim}\uC608: npx connectbase init --yes --secret-key cb_sk_... [--app <\uC571ID>] [--storage <\uC2A4\uD1A0\uB9AC\uC9C0ID>] [--deploy-dir ./dist]${colors.reset}`
1223
+ );
1224
+ log(
1225
+ `${colors.dim}Secret Key\uB294 CONNECTBASE_SECRET_KEY \uD658\uACBD\uBCC0\uC218\uB85C\uB3C4 \uC804\uB2EC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4${colors.reset}`
1226
+ );
1018
1227
  process.exit(1);
1019
1228
  }
1020
1229
  const rcPath = path2.join(cwd, ".connectbaserc");
@@ -1026,11 +1235,15 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
1026
1235
  }
1027
1236
  if (nonInteractive) {
1028
1237
  if (!opts.force) {
1029
- error(".connectbaserc\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force\uB97C \uD568\uAED8 \uC0AC\uC6A9\uD558\uC138\uC694");
1238
+ error(
1239
+ ".connectbaserc\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force\uB97C \uD568\uAED8 \uC0AC\uC6A9\uD558\uC138\uC694"
1240
+ );
1030
1241
  process.exit(1);
1031
1242
  }
1032
1243
  } else {
1033
- const overwrite = await prompt(`${colors.yellow}\u26A0${colors.reset} .connectbaserc\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? (y/N): `);
1244
+ const overwrite = await prompt(
1245
+ `${colors.yellow}\u26A0${colors.reset} .connectbaserc\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? (y/N): `
1246
+ );
1034
1247
  if (overwrite.toLowerCase() !== "y") {
1035
1248
  info("\uCD08\uAE30\uD654\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
1036
1249
  return;
@@ -1040,12 +1253,16 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
1040
1253
  let secretKey = config.secretKey || "";
1041
1254
  if (secretKey) {
1042
1255
  if (!secretKey.startsWith("cb_sk_")) {
1043
- error("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uD0A4 \uD615\uC2DD\uC785\uB2C8\uB2E4. cb_sk_\uB85C \uC2DC\uC791\uD558\uB294 Secret Key\uB97C \uC9C0\uC815\uD558\uC138\uC694");
1256
+ error(
1257
+ "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uD0A4 \uD615\uC2DD\uC785\uB2C8\uB2E4. cb_sk_\uB85C \uC2DC\uC791\uD558\uB294 Secret Key\uB97C \uC9C0\uC815\uD558\uC138\uC694"
1258
+ );
1044
1259
  process.exit(1);
1045
1260
  }
1046
1261
  info("Secret Key \uD655\uC778\uB428 (\uD50C\uB798\uADF8/\uD658\uACBD\uBCC0\uC218/.connectbaserc)");
1047
1262
  } else if (nonInteractive) {
1048
- error("Secret Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: --secret-key cb_sk_... \uB610\uB294 CONNECTBASE_SECRET_KEY \uD658\uACBD\uBCC0\uC218");
1263
+ error(
1264
+ "Secret Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: --secret-key cb_sk_... \uB610\uB294 CONNECTBASE_SECRET_KEY \uD658\uACBD\uBCC0\uC218"
1265
+ );
1049
1266
  process.exit(1);
1050
1267
  } else {
1051
1268
  log(`
@@ -1053,30 +1270,42 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
1053
1270
  log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
1054
1271
  log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
1055
1272
  `);
1056
- const authChoice = await prompt(`${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `);
1273
+ const authChoice = await prompt(
1274
+ `${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `
1275
+ );
1057
1276
  if (authChoice === "2") {
1058
1277
  try {
1059
1278
  secretKey = await browserAuthFlow();
1060
1279
  } catch (err) {
1061
1280
  error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
1062
- info("\uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD655\uC778\uD558\uAC70\uB098, Secret Key \uC9C1\uC811 \uC785\uB825(\uC635\uC158 1)\uC744 \uC2DC\uB3C4\uD558\uC138\uC694");
1281
+ info(
1282
+ "\uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD655\uC778\uD558\uAC70\uB098, Secret Key \uC9C1\uC811 \uC785\uB825(\uC635\uC158 1)\uC744 \uC2DC\uB3C4\uD558\uC138\uC694"
1283
+ );
1063
1284
  process.exit(1);
1064
1285
  }
1065
1286
  } else {
1066
- log(`${colors.dim}Secret Key (cb_sk_): \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uBC1C\uAE09${colors.reset}
1067
- `);
1068
- secretKey = await promptSecret(`${colors.blue}?${colors.reset} Secret Key: `);
1287
+ log(
1288
+ `${colors.dim}Secret Key (cb_sk_): \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uBC1C\uAE09${colors.reset}
1289
+ `
1290
+ );
1291
+ secretKey = await promptSecret(
1292
+ `${colors.blue}?${colors.reset} Secret Key: `
1293
+ );
1069
1294
  if (!secretKey) {
1070
1295
  error("Secret Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
1071
1296
  process.exit(1);
1072
1297
  }
1073
1298
  if (secretKey.startsWith("cb_pk_")) {
1074
1299
  error("Public Key\uAC00 \uC544\uB2CC Secret Key(cb_sk_)\uB97C \uC785\uB825\uD558\uC138\uC694");
1075
- info("Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
1300
+ info(
1301
+ "Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4"
1302
+ );
1076
1303
  process.exit(1);
1077
1304
  }
1078
1305
  if (!secretKey.startsWith("cb_sk_")) {
1079
- error("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uD0A4 \uD615\uC2DD\uC785\uB2C8\uB2E4. cb_sk_\uB85C \uC2DC\uC791\uD558\uB294 Secret Key\uB97C \uC785\uB825\uD558\uC138\uC694");
1306
+ error(
1307
+ "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uD0A4 \uD615\uC2DD\uC785\uB2C8\uB2E4. cb_sk_\uB85C \uC2DC\uC791\uD558\uB294 Secret Key\uB97C \uC785\uB825\uD558\uC138\uC694"
1308
+ );
1080
1309
  process.exit(1);
1081
1310
  }
1082
1311
  }
@@ -1145,11 +1374,15 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
1145
1374
  ${colors.dim}\uB0B4 \uC571 \uBAA9\uB85D:${colors.reset}`);
1146
1375
  apps.forEach((a, i) => {
1147
1376
  const date = a.created_at ? a.created_at.substring(0, 10) : "";
1148
- log(` ${colors.cyan}${i + 1}${colors.reset}) ${a.name} ${colors.dim}(${date})${colors.reset}`);
1377
+ log(
1378
+ ` ${colors.cyan}${i + 1}${colors.reset}) ${a.name} ${colors.dim}(${date})${colors.reset}`
1379
+ );
1149
1380
  });
1150
1381
  log(` ${colors.cyan}0${colors.reset}) \uC0C8 \uC571 \uB9CC\uB4E4\uAE30`);
1151
- const choice = await prompt(`
1152
- ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1382
+ const choice = await prompt(
1383
+ `
1384
+ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `
1385
+ );
1153
1386
  const num = parseInt(choice, 10);
1154
1387
  if (num > 0 && num <= apps.length) {
1155
1388
  appId = apps[num - 1].id;
@@ -1158,7 +1391,9 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1158
1391
  }
1159
1392
  }
1160
1393
  if (!appId) {
1161
- const appName = nonInteractive ? projectName : await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
1394
+ const appName = nonInteractive ? projectName : await prompt(
1395
+ `${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `
1396
+ ) || projectName;
1162
1397
  info("\uC571 \uC0DD\uC131 \uC911...");
1163
1398
  const createRes = await makeRequest(
1164
1399
  `${DEFAULT_BASE_URL}/v1/public/cli/apps`,
@@ -1186,19 +1421,25 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1186
1421
  } catch (err) {
1187
1422
  error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
1188
1423
  if (nonInteractive) {
1189
- info("\uB124\uD2B8\uC6CC\uD06C \uBCF5\uAD6C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uAC70\uB098, \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uD55C \uAC12\uC73C\uB85C .connectbaserc\uB97C \uC9C1\uC811 \uC791\uC131\uD558\uC138\uC694");
1424
+ info(
1425
+ "\uB124\uD2B8\uC6CC\uD06C \uBCF5\uAD6C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uAC70\uB098, \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uD55C \uAC12\uC73C\uB85C .connectbaserc\uB97C \uC9C1\uC811 \uC791\uC131\uD558\uC138\uC694"
1426
+ );
1190
1427
  process.exit(1);
1191
1428
  }
1192
1429
  log(`${colors.dim}\uC218\uB3D9\uC73C\uB85C \uC785\uB825\uD558\uC138\uC694${colors.reset}`);
1193
1430
  appId = await prompt(`${colors.blue}?${colors.reset} App ID: `);
1194
- publicKey = await prompt(`${colors.blue}?${colors.reset} Public Key (cb_pk_): `);
1431
+ publicKey = await prompt(
1432
+ `${colors.blue}?${colors.reset} Public Key (cb_pk_): `
1433
+ );
1195
1434
  if (!appId || !publicKey) {
1196
1435
  error("App ID\uC640 Public Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
1197
1436
  process.exit(1);
1198
1437
  }
1199
1438
  }
1200
1439
  if (!publicKey) {
1201
- error("Public Key\uAC00 \uC5C6\uC5B4 \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0\uB97C \uC870\uD68C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uCF58\uC194\uC5D0\uC11C Public Key\uB97C \uBC1C\uAE09\uBC1B\uC544 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694");
1440
+ error(
1441
+ "Public Key\uAC00 \uC5C6\uC5B4 \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0\uB97C \uC870\uD68C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uCF58\uC194\uC5D0\uC11C Public Key\uB97C \uBC1C\uAE09\uBC1B\uC544 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694"
1442
+ );
1202
1443
  process.exit(1);
1203
1444
  }
1204
1445
  const publicKeyForSdk = publicKey;
@@ -1227,11 +1468,15 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1227
1468
  log(`
1228
1469
  ${colors.dim}\uAE30\uC874 \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0:${colors.reset}`);
1229
1470
  storages.forEach((s, i) => {
1230
- log(` ${colors.cyan}${i + 1}${colors.reset}) ${s.name} (${colors.dim}${s.id}${colors.reset})`);
1471
+ log(
1472
+ ` ${colors.cyan}${i + 1}${colors.reset}) ${s.name} (${colors.dim}${s.id}${colors.reset})`
1473
+ );
1231
1474
  });
1232
1475
  log(` ${colors.cyan}0${colors.reset}) \uC0C8\uB85C \uC0DD\uC131`);
1233
- const choice = await prompt(`
1234
- ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1476
+ const choice = await prompt(
1477
+ `
1478
+ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `
1479
+ );
1235
1480
  const num = parseInt(choice, 10);
1236
1481
  if (num > 0 && num <= storages.length) {
1237
1482
  storageId = storages[num - 1].id;
@@ -1239,7 +1484,9 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1239
1484
  }
1240
1485
  }
1241
1486
  if (!storageId) {
1242
- const name = nonInteractive ? projectName : await prompt(`${colors.blue}?${colors.reset} \uC2A4\uD1A0\uB9AC\uC9C0 \uC774\uB984 (${projectName}): `) || projectName;
1487
+ const name = nonInteractive ? projectName : await prompt(
1488
+ `${colors.blue}?${colors.reset} \uC2A4\uD1A0\uB9AC\uC9C0 \uC774\uB984 (${projectName}): `
1489
+ ) || projectName;
1243
1490
  info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC911...");
1244
1491
  const createRes = await makeRequest(
1245
1492
  `${DEFAULT_BASE_URL}/v1/public/storages/webs`,
@@ -1249,7 +1496,9 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1249
1496
  );
1250
1497
  if (createRes.status !== 200) {
1251
1498
  const data = createRes.data;
1252
- error(`\uC0DD\uC131 \uC2E4\uD328: ${data?.error || data?.message || `HTTP ${createRes.status}`}`);
1499
+ error(
1500
+ `\uC0DD\uC131 \uC2E4\uD328: ${data?.error || data?.message || `HTTP ${createRes.status}`}`
1501
+ );
1253
1502
  process.exit(1);
1254
1503
  }
1255
1504
  const createData = createRes.data;
@@ -1262,7 +1511,9 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1262
1511
  } catch (err) {
1263
1512
  error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
1264
1513
  if (nonInteractive) {
1265
- info("\uB124\uD2B8\uC6CC\uD06C \uBCF5\uAD6C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uAC70\uB098, --storage <\uC2A4\uD1A0\uB9AC\uC9C0ID>\uC640 \uD568\uAED8 \uC2E4\uD589\uD558\uC138\uC694");
1514
+ info(
1515
+ "\uB124\uD2B8\uC6CC\uD06C \uBCF5\uAD6C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uAC70\uB098, --storage <\uC2A4\uD1A0\uB9AC\uC9C0ID>\uC640 \uD568\uAED8 \uC2E4\uD589\uD558\uC138\uC694"
1516
+ );
1266
1517
  process.exit(1);
1267
1518
  }
1268
1519
  log(`${colors.dim}\uC218\uB3D9\uC73C\uB85C Storage ID\uB97C \uC785\uB825\uD558\uC138\uC694${colors.reset}`);
@@ -1274,7 +1525,9 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1274
1525
  }
1275
1526
  }
1276
1527
  const defaultDir = opts.deployDir || detectBuildDir();
1277
- const deployDir = nonInteractive ? defaultDir : await prompt(`${colors.blue}?${colors.reset} \uBC30\uD3EC \uB514\uB809\uD1A0\uB9AC (${defaultDir}): `) || defaultDir;
1528
+ const deployDir = nonInteractive ? defaultDir : await prompt(
1529
+ `${colors.blue}?${colors.reset} \uBC30\uD3EC \uB514\uB809\uD1A0\uB9AC (${defaultDir}): `
1530
+ ) || defaultDir;
1278
1531
  const rcData = {
1279
1532
  appId,
1280
1533
  publicKey: publicKey || "",
@@ -1284,7 +1537,8 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1284
1537
  if (secretKey) {
1285
1538
  rcData.secretKey = secretKey;
1286
1539
  }
1287
- fs2.writeFileSync(rcPath, JSON.stringify(rcData, null, 2) + "\n");
1540
+ fs2.writeFileSync(rcPath, `${JSON.stringify(rcData, null, 2)}
1541
+ `);
1288
1542
  success(".connectbaserc \uC0DD\uC131 \uC644\uB8CC");
1289
1543
  addToGitignore(".connectbaserc");
1290
1544
  addDeployScript(deployDir);
@@ -1308,14 +1562,17 @@ function detectBuildDir() {
1308
1562
  try {
1309
1563
  const src = fs2.readFileSync(nextConfig, "utf-8");
1310
1564
  if (!/output\s*:\s*['"]export['"]/.test(src)) {
1311
- warn(`Next.js \uAC10\uC9C0: \uC815\uC801 \uBC30\uD3EC\uD558\uB824\uBA74 ${path2.basename(nextConfig)}\uC5D0 output: 'export' \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4`);
1565
+ warn(
1566
+ `Next.js \uAC10\uC9C0: \uC815\uC801 \uBC30\uD3EC\uD558\uB824\uBA74 ${path2.basename(nextConfig)}\uC5D0 output: 'export' \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4`
1567
+ );
1312
1568
  }
1313
1569
  } catch {
1314
1570
  }
1315
1571
  }
1316
1572
  return "./out";
1317
1573
  }
1318
- if (fs2.existsSync(path2.join(process.cwd(), "vite.config.ts")) || fs2.existsSync(path2.join(process.cwd(), "vite.config.js"))) return "./dist";
1574
+ if (fs2.existsSync(path2.join(process.cwd(), "vite.config.ts")) || fs2.existsSync(path2.join(process.cwd(), "vite.config.js")))
1575
+ return "./dist";
1319
1576
  return "./dist";
1320
1577
  }
1321
1578
  function addToGitignore(entry, basePath) {
@@ -1350,12 +1607,15 @@ function addDeployScript(deployDir) {
1350
1607
  const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
1351
1608
  if (!pkg.scripts) pkg.scripts = {};
1352
1609
  if (pkg.scripts.deploy) {
1353
- info(`package.json\uC5D0 \uC774\uBBF8 deploy \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC788\uC2B5\uB2C8\uB2E4: "${pkg.scripts.deploy}"`);
1610
+ info(
1611
+ `package.json\uC5D0 \uC774\uBBF8 deploy \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC788\uC2B5\uB2C8\uB2E4: "${pkg.scripts.deploy}"`
1612
+ );
1354
1613
  return;
1355
1614
  }
1356
1615
  const deployCmd = `connectbase deploy ${deployDir}`;
1357
1616
  pkg.scripts.deploy = pkg.scripts.build ? `${pkg.scripts.build} && ${deployCmd}` : deployCmd;
1358
- fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1617
+ fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
1618
+ `);
1359
1619
  success(`package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC`);
1360
1620
  } catch {
1361
1621
  warn("package.json \uC218\uC815\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4");
@@ -1364,14 +1624,20 @@ function addDeployScript(deployDir) {
1364
1624
  function getGitRoot() {
1365
1625
  try {
1366
1626
  const { execSync } = require("child_process");
1367
- return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
1627
+ return execSync("git rev-parse --show-toplevel", {
1628
+ encoding: "utf-8"
1629
+ }).trim();
1368
1630
  } catch {
1369
1631
  return null;
1370
1632
  }
1371
1633
  }
1372
1634
  function detectMonorepo(gitRoot) {
1373
1635
  const cwd = process.cwd();
1374
- const result = { type: "none", root: gitRoot, isSubPackage: false };
1636
+ const result = {
1637
+ type: "none",
1638
+ root: gitRoot,
1639
+ isSubPackage: false
1640
+ };
1375
1641
  if (fs2.existsSync(path2.join(gitRoot, "pnpm-workspace.yaml"))) {
1376
1642
  result.type = "pnpm";
1377
1643
  } else if (fs2.existsSync(path2.join(gitRoot, "turbo.json"))) {
@@ -1408,7 +1674,9 @@ function detectMonorepo(gitRoot) {
1408
1674
  var DOCS_MANIFEST_FILE = ".connectbase-manifest.json";
1409
1675
  function readDocsManifest(docsDir) {
1410
1676
  try {
1411
- const parsed = JSON.parse(fs2.readFileSync(path2.join(docsDir, DOCS_MANIFEST_FILE), "utf-8"));
1677
+ const parsed = JSON.parse(
1678
+ fs2.readFileSync(path2.join(docsDir, DOCS_MANIFEST_FILE), "utf-8")
1679
+ );
1412
1680
  if (parsed && Array.isArray(parsed.files)) return parsed;
1413
1681
  } catch {
1414
1682
  }
@@ -1467,7 +1735,9 @@ async function downloadDocs(baseDir, opts = {}) {
1467
1735
  if (!quiet && monorepo.type !== "none") {
1468
1736
  info(`\uBAA8\uB178\uB808\uD3EC \uAC10\uC9C0: ${monorepo.type} (\uB8E8\uD2B8: ${gitRoot})`);
1469
1737
  if (monorepo.isSubPackage) {
1470
- info(`\uD604\uC7AC \uC11C\uBE0C \uD328\uD0A4\uC9C0: ${monorepo.subPackageName || path2.basename(process.cwd())}`);
1738
+ info(
1739
+ `\uD604\uC7AC \uC11C\uBE0C \uD328\uD0A4\uC9C0: ${monorepo.subPackageName || path2.basename(process.cwd())}`
1740
+ );
1471
1741
  }
1472
1742
  }
1473
1743
  const docsDir = path2.join(gitRoot, ".claude", "docs");
@@ -1496,12 +1766,18 @@ async function downloadDocs(baseDir, opts = {}) {
1496
1766
  }
1497
1767
  const contentHash = hasher.digest("hex");
1498
1768
  const prevManifest = readDocsManifest(docsDir);
1499
- const allPresent = newFiles.every((f) => fs2.existsSync(path2.join(docsDir, f)));
1769
+ const allPresent = newFiles.every(
1770
+ (f) => fs2.existsSync(path2.join(docsDir, f))
1771
+ );
1500
1772
  if (prevManifest && prevManifest.contentHash === contentHash && allPresent) {
1501
1773
  if (!quiet) success("SDK \uBB38\uC11C\uAC00 \uC774\uBBF8 \uCD5C\uC2E0\uC785\uB2C8\uB2E4");
1502
1774
  updateRootClaudeMd(rootClaudeMd, quiet);
1503
1775
  if (monorepo.isSubPackage) {
1504
- createSubPackageClaudeMd(path2.join(process.cwd(), "CLAUDE.md"), gitRoot, quiet);
1776
+ createSubPackageClaudeMd(
1777
+ path2.join(process.cwd(), "CLAUDE.md"),
1778
+ gitRoot,
1779
+ quiet
1780
+ );
1505
1781
  }
1506
1782
  return "unchanged";
1507
1783
  }
@@ -1530,13 +1806,27 @@ async function downloadDocs(baseDir, opts = {}) {
1530
1806
  contentHash,
1531
1807
  files: newFiles
1532
1808
  };
1533
- fs2.writeFileSync(path2.join(docsDir, DOCS_MANIFEST_FILE), JSON.stringify(manifest, null, 2) + "\n");
1534
- if (!quiet) success(`${gitRoot}/.claude/docs/ \uC5D0 ${sections.length}\uAC1C \uBB38\uC11C \uC800\uC7A5 \uC644\uB8CC`);
1809
+ fs2.writeFileSync(
1810
+ path2.join(docsDir, DOCS_MANIFEST_FILE),
1811
+ `${JSON.stringify(manifest, null, 2)}
1812
+ `
1813
+ );
1814
+ if (!quiet)
1815
+ success(
1816
+ `${gitRoot}/.claude/docs/ \uC5D0 ${sections.length}\uAC1C \uBB38\uC11C \uC800\uC7A5 \uC644\uB8CC`
1817
+ );
1535
1818
  updateRootClaudeMd(rootClaudeMd, quiet);
1536
1819
  if (monorepo.isSubPackage) {
1537
- createSubPackageClaudeMd(path2.join(process.cwd(), "CLAUDE.md"), gitRoot, quiet);
1820
+ createSubPackageClaudeMd(
1821
+ path2.join(process.cwd(), "CLAUDE.md"),
1822
+ gitRoot,
1823
+ quiet
1824
+ );
1538
1825
  }
1539
- if (!quiet) log(`${colors.dim}\u203B SDK \uBB38\uC11C\uB294 MCP search_sdk_docs\uB85C \uAC80\uC0C9\uD558\uC138\uC694${colors.reset}`);
1826
+ if (!quiet)
1827
+ log(
1828
+ `${colors.dim}\u203B SDK \uBB38\uC11C\uB294 MCP search_sdk_docs\uB85C \uAC80\uC0C9\uD558\uC138\uC694${colors.reset}`
1829
+ );
1540
1830
  return "updated";
1541
1831
  } catch {
1542
1832
  if (quiet) return "failed";
@@ -1580,7 +1870,9 @@ ${endMarker}`;
1580
1870
  if (startIdx !== -1 && endIdx !== -1) {
1581
1871
  content = content.substring(0, startIdx) + sdkBlock + content.substring(endIdx + endMarker.length);
1582
1872
  } else {
1583
- content = sdkBlock + "\n\n" + content.trimStart();
1873
+ content = `${sdkBlock}
1874
+
1875
+ ${content.trimStart()}`;
1584
1876
  }
1585
1877
  fs2.writeFileSync(claudeMdPath, content);
1586
1878
  if (!quiet) info("CLAUDE.md\uC5D0 ConnectBase \uCC38\uC870 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
@@ -1614,16 +1906,22 @@ ${endMarker}`;
1614
1906
  if (startIdx !== -1 && endIdx !== -1) {
1615
1907
  content = content.substring(0, startIdx) + subBlock + content.substring(endIdx + endMarker.length);
1616
1908
  } else if (content.indexOf("<!-- CONNECTBASE_START -->") === -1) {
1617
- content = content.trimEnd() + "\n\n" + subBlock + "\n";
1909
+ content = `${content.trimEnd()}
1910
+
1911
+ ${subBlock}
1912
+ `;
1618
1913
  } else {
1619
1914
  return;
1620
1915
  }
1621
1916
  fs2.writeFileSync(subClaudeMdPath, content);
1622
1917
  } else {
1623
- fs2.writeFileSync(subClaudeMdPath, `# ${path2.basename(path2.dirname(subClaudeMdPath))}
1918
+ fs2.writeFileSync(
1919
+ subClaudeMdPath,
1920
+ `# ${path2.basename(path2.dirname(subClaudeMdPath))}
1624
1921
 
1625
1922
  ${subBlock}
1626
- `);
1923
+ `
1924
+ );
1627
1925
  }
1628
1926
  if (!quiet) success(`\uC11C\uBE0C \uD328\uD0A4\uC9C0 CLAUDE.md \uC0DD\uC131 \uC644\uB8CC: ${subClaudeMdPath}`);
1629
1927
  }
@@ -1642,8 +1940,12 @@ async function setupMcp(secretKey) {
1642
1940
  if (!process.stdin.isTTY) {
1643
1941
  error("Secret Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (\uBE44\uB300\uD654\uD615 \uD658\uACBD)");
1644
1942
  log(`${colors.dim}\uB2E4\uC74C \uC911 \uD558\uB098\uB85C \uD0A4\uB97C \uC804\uB2EC\uD558\uC138\uC694:${colors.reset}`);
1645
- log(` ${colors.cyan}npx connectbase mcp --secret-key cb_sk_...${colors.reset}`);
1646
- log(` ${colors.cyan}CONNECTBASE_SECRET_KEY=cb_sk_... npx connectbase mcp${colors.reset}`);
1943
+ log(
1944
+ ` ${colors.cyan}npx connectbase mcp --secret-key cb_sk_...${colors.reset}`
1945
+ );
1946
+ log(
1947
+ ` ${colors.cyan}CONNECTBASE_SECRET_KEY=cb_sk_... npx connectbase mcp${colors.reset}`
1948
+ );
1647
1949
  process.exit(1);
1648
1950
  }
1649
1951
  log(`
@@ -1651,7 +1953,9 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
1651
1953
  log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
1652
1954
  log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
1653
1955
  `);
1654
- const authChoice = await prompt(`${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `);
1956
+ const authChoice = await prompt(
1957
+ `${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `
1958
+ );
1655
1959
  if (authChoice === "2") {
1656
1960
  try {
1657
1961
  secretKey = await browserAuthFlow();
@@ -1664,7 +1968,8 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
1664
1968
  }
1665
1969
  }
1666
1970
  rcData.secretKey = secretKey;
1667
- fs2.writeFileSync(skRcPath, JSON.stringify(rcData, null, 2) + "\n");
1971
+ fs2.writeFileSync(skRcPath, `${JSON.stringify(rcData, null, 2)}
1972
+ `);
1668
1973
  addToGitignore(".connectbaserc", root);
1669
1974
  success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
1670
1975
  } catch (err) {
@@ -1672,7 +1977,9 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
1672
1977
  process.exit(1);
1673
1978
  }
1674
1979
  } else {
1675
- secretKey = await promptSecret(`${colors.blue}?${colors.reset} Secret Key: `);
1980
+ secretKey = await promptSecret(
1981
+ `${colors.blue}?${colors.reset} Secret Key: `
1982
+ );
1676
1983
  }
1677
1984
  }
1678
1985
  const mcpEntry = {
@@ -1690,14 +1997,15 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
1690
1997
  mcpConfig.mcpServers = {};
1691
1998
  }
1692
1999
  } catch {
1693
- const backupPath = mcpConfigPath + ".backup";
2000
+ const backupPath = `${mcpConfigPath}.backup`;
1694
2001
  fs2.copyFileSync(mcpConfigPath, backupPath);
1695
2002
  warn(`.mcp.json \uD30C\uC2F1 \uC2E4\uD328, \uBC31\uC5C5 \uC0DD\uC131: ${backupPath}`);
1696
2003
  mcpConfig = { mcpServers: {} };
1697
2004
  }
1698
2005
  }
1699
2006
  mcpConfig.mcpServers["connect-base"] = mcpEntry;
1700
- fs2.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
2007
+ fs2.writeFileSync(mcpConfigPath, `${JSON.stringify(mcpConfig, null, 2)}
2008
+ `);
1701
2009
  success(`${mcpConfigPath} \uC0DD\uC131 \uC644\uB8CC`);
1702
2010
  addToGitignore(".mcp.json", root);
1703
2011
  if (secretKey && secretKey !== "YOUR_SECRET_KEY_HERE") {
@@ -1706,8 +2014,10 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
1706
2014
  warn("MCP \uC11C\uBC84\uB294 Secret Key (cb_sk_)\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4.");
1707
2015
  info(".mcp.json \uD30C\uC77C\uC758 YOUR_SECRET_KEY_HERE\uB97C Secret Key\uB85C \uAD50\uCCB4\uD558\uC138\uC694.");
1708
2016
  }
1709
- log(`
1710
- ${colors.dim}Claude Code\uC5D0\uC11C ConnectBase MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.${colors.reset}`);
2017
+ log(
2018
+ `
2019
+ ${colors.dim}Claude Code\uC5D0\uC11C ConnectBase MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.${colors.reset}`
2020
+ );
1711
2021
  }
1712
2022
  async function setupClaudeCode(secretKey, projectRoot) {
1713
2023
  const root = projectRoot || getProjectRoot();
@@ -1764,7 +2074,8 @@ function findSubPackagesWithConfig(gitRoot) {
1764
2074
  try {
1765
2075
  const entries = fs2.readdirSync(gitRoot, { withFileTypes: true });
1766
2076
  for (const entry of entries) {
1767
- if (!entry.isDirectory() || entry.name.startsWith(".") || candidates.includes(entry.name)) continue;
2077
+ if (!entry.isDirectory() || entry.name.startsWith(".") || candidates.includes(entry.name))
2078
+ continue;
1768
2079
  if (entry.name === "node_modules") continue;
1769
2080
  const subDir = path2.join(gitRoot, entry.name);
1770
2081
  if (fs2.existsSync(path2.join(subDir, ".connectbaserc")) && !results.includes(subDir)) {
@@ -1789,8 +2100,12 @@ ${colors.cyan}ConnectBase Update${colors.reset}
1789
2100
  if (cmp >= 0) {
1790
2101
  success(`\uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4 (v${VERSION})`);
1791
2102
  } else {
1792
- warn(`\uC0C8 \uBC84\uC804\uC774 \uC788\uC2B5\uB2C8\uB2E4: v${VERSION} \u2192 v${colors.green}${latestVersion}${colors.reset}`);
1793
- log(` ${colors.cyan}npm install connectbase-client@latest${colors.reset}`);
2103
+ warn(
2104
+ `\uC0C8 \uBC84\uC804\uC774 \uC788\uC2B5\uB2C8\uB2E4: v${VERSION} \u2192 v${colors.green}${latestVersion}${colors.reset}`
2105
+ );
2106
+ log(
2107
+ ` ${colors.cyan}npm install connectbase-client@latest${colors.reset}`
2108
+ );
1794
2109
  log(` ${colors.cyan}pnpm add connectbase-client@latest${colors.reset}`);
1795
2110
  log("");
1796
2111
  }
@@ -1806,7 +2121,9 @@ ${colors.cyan}ConnectBase Update${colors.reset}
1806
2121
  if (subPackages.length > 0) {
1807
2122
  info(`ConnectBase\uB97C \uC0AC\uC6A9\uD558\uB294 \uC11C\uBE0C \uD328\uD0A4\uC9C0 ${subPackages.length}\uAC1C \uBC1C\uACAC:`);
1808
2123
  for (const sp of subPackages) {
1809
- log(` ${colors.dim}\u2022 ${path2.relative(projectRoot, sp)}${colors.reset}`);
2124
+ log(
2125
+ ` ${colors.dim}\u2022 ${path2.relative(projectRoot, sp)}${colors.reset}`
2126
+ );
1810
2127
  }
1811
2128
  }
1812
2129
  }
@@ -1834,7 +2151,9 @@ ${colors.cyan}ConnectBase Update${colors.reset}
1834
2151
  const subPackages = findSubPackagesWithConfig(projectRoot);
1835
2152
  for (const sp of subPackages) {
1836
2153
  try {
1837
- const rc = JSON.parse(fs2.readFileSync(path2.join(sp, ".connectbaserc"), "utf-8"));
2154
+ const rc = JSON.parse(
2155
+ fs2.readFileSync(path2.join(sp, ".connectbaserc"), "utf-8")
2156
+ );
1838
2157
  if (rc.secretKey) {
1839
2158
  secretKey = rc.secretKey;
1840
2159
  break;
@@ -1854,10 +2173,16 @@ ${colors.cyan}ConnectBase Update${colors.reset}
1854
2173
  headers: { Authorization: `Bearer ${secretKey}` }
1855
2174
  };
1856
2175
  mcpConfig.mcpServers = servers;
1857
- fs2.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
2176
+ fs2.writeFileSync(
2177
+ mcpConfigPath,
2178
+ `${JSON.stringify(mcpConfig, null, 2)}
2179
+ `
2180
+ );
1858
2181
  success("MCP \uC124\uC815 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
1859
2182
  } else {
1860
- info('.mcp.json\uC5D0 connect-base \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. "connectbase mcp"\uB85C \uC124\uC815\uD558\uC138\uC694');
2183
+ info(
2184
+ '.mcp.json\uC5D0 connect-base \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. "connectbase mcp"\uB85C \uC124\uC815\uD558\uC138\uC694'
2185
+ );
1861
2186
  }
1862
2187
  } catch {
1863
2188
  warn(".mcp.json \uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328");
@@ -1880,7 +2205,9 @@ ${colors.green}\uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!${colors.reset}
1880
2205
  function setupMonorepoRoot(projectRoot, monorepo) {
1881
2206
  const rootPkgPath = path2.join(projectRoot, "package.json");
1882
2207
  if (!fs2.existsSync(rootPkgPath)) {
1883
- warn("\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 package.json\uC774 \uC5C6\uC5B4 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
2208
+ warn(
2209
+ "\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 package.json\uC774 \uC5C6\uC5B4 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
2210
+ );
1884
2211
  return;
1885
2212
  }
1886
2213
  try {
@@ -1900,12 +2227,19 @@ function setupMonorepoRoot(projectRoot, monorepo) {
1900
2227
  changed = true;
1901
2228
  }
1902
2229
  if (changed) {
1903
- fs2.writeFileSync(rootPkgPath, JSON.stringify(pkg, null, 2) + "\n");
2230
+ fs2.writeFileSync(rootPkgPath, `${JSON.stringify(pkg, null, 2)}
2231
+ `);
1904
2232
  success("\uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC");
1905
2233
  log(` ${colors.dim}\uC0AC\uC6A9\uBC95:${colors.reset}`);
1906
- log(` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:update${colors.reset} \u2014 SDK \uC5C5\uB370\uC774\uD2B8 + \uBB38\uC11C \uAC31\uC2E0`);
1907
- log(` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:docs${colors.reset} \u2014 \uBB38\uC11C\uB9CC \uAC31\uC2E0`);
1908
- log(` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:mcp${colors.reset} \u2014 MCP \uC124\uC815`);
2234
+ log(
2235
+ ` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:update${colors.reset} \u2014 SDK \uC5C5\uB370\uC774\uD2B8 + \uBB38\uC11C \uAC31\uC2E0`
2236
+ );
2237
+ log(
2238
+ ` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:docs${colors.reset} \u2014 \uBB38\uC11C\uB9CC \uAC31\uC2E0`
2239
+ );
2240
+ log(
2241
+ ` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:mcp${colors.reset} \u2014 MCP \uC124\uC815`
2242
+ );
1909
2243
  } else {
1910
2244
  info("\uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uC774\uBBF8 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4");
1911
2245
  }
@@ -2030,7 +2364,9 @@ var UpstreamWsFrameParser = class {
2030
2364
  }
2031
2365
  if (this.buffer.length < offset + payloadLen) return;
2032
2366
  if (rsv1 && (opcode === 1 || opcode === 2 || opcode === 0)) {
2033
- throw new Error("upstream sent compressed (RSV1) frame without negotiation");
2367
+ throw new Error(
2368
+ "upstream sent compressed (RSV1) frame without negotiation"
2369
+ );
2034
2370
  }
2035
2371
  let payload = this.buffer.subarray(offset, offset + payloadLen);
2036
2372
  if (maskKey) {
@@ -2045,9 +2381,14 @@ var UpstreamWsFrameParser = class {
2045
2381
  switch (opcode) {
2046
2382
  case 0:
2047
2383
  if (this.fragmentBuffer == null) {
2048
- throw new Error("continuation frame without preceding non-final data frame");
2384
+ throw new Error(
2385
+ "continuation frame without preceding non-final data frame"
2386
+ );
2049
2387
  }
2050
- this.fragmentBuffer = Buffer.concat([this.fragmentBuffer, payloadCopy]);
2388
+ this.fragmentBuffer = Buffer.concat([
2389
+ this.fragmentBuffer,
2390
+ payloadCopy
2391
+ ]);
2051
2392
  if (fin) {
2052
2393
  const out = this.fragmentBuffer;
2053
2394
  this.fragmentBuffer = null;
@@ -2189,6 +2530,61 @@ function getTunnelServerUrl(baseUrl) {
2189
2530
  }
2190
2531
  return baseUrl.replace(/:\d+/, ":8090");
2191
2532
  }
2533
+ function getTunnelConnectCandidates(baseUrl) {
2534
+ const override = process.env.CB_TUNNEL_CONNECT_URL;
2535
+ if (override) return [override];
2536
+ const proxyHost = getTunnelServerUrl(baseUrl);
2537
+ if (proxyHost.includes("tunnel.connectbase.world")) {
2538
+ return [
2539
+ proxyHost.replace(
2540
+ "tunnel.connectbase.world",
2541
+ "tunnel-direct.connectbase.world"
2542
+ ),
2543
+ proxyHost
2544
+ ];
2545
+ }
2546
+ return [proxyHost];
2547
+ }
2548
+ var TunnelConnectSelector = class {
2549
+ constructor(candidates) {
2550
+ this.candidates = candidates;
2551
+ this.idx = 0;
2552
+ this.failures = 0;
2553
+ if (candidates.length === 0) {
2554
+ throw new Error("connect candidate\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4");
2555
+ }
2556
+ }
2557
+ current() {
2558
+ return this.candidates[this.idx];
2559
+ }
2560
+ // 연결이 수립되면 호출한다. 현 후보의 실패 카운트만 리셋하고 후보는 유지한다 —
2561
+ // 동작하는 경로를 계속 쓰되, 나중에 그 경로가 죽으면 다시 셀 수 있게 한다.
2562
+ recordSuccess() {
2563
+ this.failures = 0;
2564
+ }
2565
+ // 실패를 기록한다.
2566
+ //
2567
+ // retry — 아직 시도할 여지가 있는가. false 면 마지막 후보에서도 실패한
2568
+ // 것이므로 호출자가 포기(프로세스 종료)해도 된다.
2569
+ // switchedFrom — 이번 호출에서 후보를 넘겼다면 직전 후보 URL, 아니면 null.
2570
+ //
2571
+ // 전환을 2회 연속 실패부터 하는 이유는 일시적 네트워크 요동으로 느린 경로에
2572
+ // 눌러앉지 않기 위해서다. 다만 그 사이에도 retry 는 true 여야 한다 — 첫 실패에서
2573
+ // 호출자가 포기해버리면 폴백이 영영 동작하지 않는다.
2574
+ recordFailure() {
2575
+ this.failures++;
2576
+ if (this.idx >= this.candidates.length - 1) {
2577
+ return { retry: false, switchedFrom: null };
2578
+ }
2579
+ if (this.failures < 2) {
2580
+ return { retry: true, switchedFrom: null };
2581
+ }
2582
+ const switchedFrom = this.candidates[this.idx];
2583
+ this.idx++;
2584
+ this.failures = 0;
2585
+ return { retry: true, switchedFrom };
2586
+ }
2587
+ };
2192
2588
  async function resolveApp(secretKey, baseUrl, appIdOption) {
2193
2589
  if (appIdOption) {
2194
2590
  return { appId: appIdOption };
@@ -2196,11 +2592,9 @@ async function resolveApp(secretKey, baseUrl, appIdOption) {
2196
2592
  let apps = [];
2197
2593
  try {
2198
2594
  info("\uC571 \uBAA9\uB85D \uC870\uD68C \uC911...");
2199
- const appsRes = await makeRequest(
2200
- `${baseUrl}/v1/public/cli/apps`,
2201
- "GET",
2202
- { "X-Public-Key": secretKey }
2203
- );
2595
+ const appsRes = await makeRequest(`${baseUrl}/v1/public/cli/apps`, "GET", {
2596
+ "X-Public-Key": secretKey
2597
+ });
2204
2598
  if (appsRes.status === 401) {
2205
2599
  error("Secret Key\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uCF58\uC194\uC5D0\uC11C \uD0A4\uB97C \uD655\uC778\uD558\uC138\uC694");
2206
2600
  process.exit(1);
@@ -2223,19 +2617,25 @@ async function resolveApp(secretKey, baseUrl, appIdOption) {
2223
2617
  ${colors.dim}\uB0B4 \uC571 \uBAA9\uB85D:${colors.reset}`);
2224
2618
  apps.forEach((a, i) => {
2225
2619
  const date = a.created_at ? a.created_at.substring(0, 10) : "";
2226
- log(` ${colors.cyan}${i + 1}${colors.reset}) ${a.name} ${colors.dim}(${date})${colors.reset}`);
2620
+ log(
2621
+ ` ${colors.cyan}${i + 1}${colors.reset}) ${a.name} ${colors.dim}(${date})${colors.reset}`
2622
+ );
2227
2623
  });
2228
2624
  }
2229
2625
  log(` ${colors.cyan}0${colors.reset}) \uC0C8 \uC571 \uB9CC\uB4E4\uAE30`);
2230
- const choice = await prompt(`
2231
- ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
2626
+ const choice = await prompt(
2627
+ `
2628
+ ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `
2629
+ );
2232
2630
  const num = parseInt(choice, 10);
2233
2631
  if (num > 0 && num <= apps.length) {
2234
2632
  success(`\uC120\uD0DD\uB428: ${apps[num - 1].name}`);
2235
2633
  return { appId: apps[num - 1].id };
2236
2634
  }
2237
2635
  const projectName = path2.basename(process.cwd());
2238
- const appName = await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
2636
+ const appName = await prompt(
2637
+ `${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `
2638
+ ) || projectName;
2239
2639
  info("\uC571 \uC0DD\uC131 \uC911...");
2240
2640
  const createRes = await makeRequest(
2241
2641
  `${baseUrl}/v1/public/cli/apps`,
@@ -2259,39 +2659,52 @@ ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
2259
2659
  async function registerEndpointBinding(baseUrl, appId, secretKey, tunnelId, label, description) {
2260
2660
  try {
2261
2661
  const apiBase = baseUrl.replace(/\/+$/, "");
2262
- const res = await fetch(`${apiBase}/v1/apps/${encodeURIComponent(appId)}/endpoints/cli`, {
2263
- method: "POST",
2264
- headers: {
2265
- "Content-Type": "application/json",
2266
- "X-Public-Key": secretKey
2267
- },
2268
- body: JSON.stringify({
2269
- label,
2270
- tunnel_id: tunnelId,
2271
- description: description ?? `CLI tunnel start (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)})`
2272
- })
2273
- });
2662
+ const res = await fetch(
2663
+ `${apiBase}/v1/apps/${encodeURIComponent(appId)}/endpoints/cli`,
2664
+ {
2665
+ method: "POST",
2666
+ headers: {
2667
+ "Content-Type": "application/json",
2668
+ "X-Public-Key": secretKey
2669
+ },
2670
+ body: JSON.stringify({
2671
+ label,
2672
+ tunnel_id: tunnelId,
2673
+ description: description ?? `CLI tunnel start (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)})`
2674
+ })
2675
+ }
2676
+ );
2274
2677
  if (res.status === 201) {
2275
2678
  success(`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC644\uB8CC`);
2276
- log(`${colors.green}\u2192${colors.reset} SDK: ${colors.cyan}cb.endpoint.call("${label}", { path: "/...", method: "POST", body: ... })${colors.reset}`);
2679
+ log(
2680
+ `${colors.green}\u2192${colors.reset} SDK: ${colors.cyan}cb.endpoint.call("${label}", { path: "/...", method: "POST", body: ... })${colors.reset}`
2681
+ );
2277
2682
  } else if (res.status === 409) {
2278
- warn(`"${label}" \uB77C\uBCA8\uC774 \uB2E4\uB978 \uD130\uB110\uC5D0 \uC774\uBBF8 \uBB36\uC5EC \uC788\uC2B5\uB2C8\uB2E4. \uC774 \uD130\uB110\uB85C \uC62E\uAE30\uB824\uBA74 \uCF58\uC194\uC5D0\uC11C endpoint \uC758 tunnel_id \uB97C \uC218\uC815(PATCH)\uD558\uC138\uC694.`);
2683
+ warn(
2684
+ `"${label}" \uB77C\uBCA8\uC774 \uB2E4\uB978 \uD130\uB110\uC5D0 \uC774\uBBF8 \uBB36\uC5EC \uC788\uC2B5\uB2C8\uB2E4. \uC774 \uD130\uB110\uB85C \uC62E\uAE30\uB824\uBA74 \uCF58\uC194\uC5D0\uC11C endpoint \uC758 tunnel_id \uB97C \uC218\uC815(PATCH)\uD558\uC138\uC694.`
2685
+ );
2279
2686
  } else {
2280
2687
  const text = await res.text().catch(() => "");
2281
- error(`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC2E4\uD328 (status ${res.status}): ${text || "(\uBE48 \uC751\uB2F5)"}`);
2688
+ error(
2689
+ `Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC2E4\uD328 (status ${res.status}): ${text || "(\uBE48 \uC751\uB2F5)"}`
2690
+ );
2282
2691
  if (res.status === 401) {
2283
2692
  info("cb_sk_ Secret Key \uC778\uC9C0, \uCF58\uC194\uC5D0\uC11C \uD68C\uC218\uB418\uC9C0 \uC54A\uC558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.");
2284
2693
  }
2285
2694
  }
2286
2695
  } catch (err) {
2287
- error(`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC911 \uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
2696
+ error(
2697
+ `Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC911 \uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`
2698
+ );
2288
2699
  }
2289
2700
  }
2290
2701
  function acquireTunnelLock2(appID, port, force) {
2291
2702
  const result = acquireTunnelLock(appID, port, force, VERSION);
2292
- if (result && result.startsWith("LOCKED:")) {
2703
+ if (result?.startsWith("LOCKED:")) {
2293
2704
  const [, pid, startedAt, host] = result.split(":");
2294
- error(`\uC774\uBBF8 \uC2E4\uD589 \uC911\uC778 \uD130\uB110\uC774 \uC788\uC2B5\uB2C8\uB2E4: PID ${pid}, \uC2DC\uC791 ${startedAt}, \uD638\uC2A4\uD2B8 ${host}`);
2705
+ error(
2706
+ `\uC774\uBBF8 \uC2E4\uD589 \uC911\uC778 \uD130\uB110\uC774 \uC788\uC2B5\uB2C8\uB2E4: PID ${pid}, \uC2DC\uC791 ${startedAt}, \uD638\uC2A4\uD2B8 ${host}`
2707
+ );
2295
2708
  info("\uC911\uBCF5 \uC2E4\uD589\uC744 \uBB34\uC2DC\uD558\uB824\uBA74 --force \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694");
2296
2709
  process.exit(2);
2297
2710
  }
@@ -2312,7 +2725,8 @@ async function startTunnel(port, config, tunnelOpts) {
2312
2725
  }
2313
2726
  }
2314
2727
  rcData.secretKey = sk;
2315
- fs2.writeFileSync(rcPath2, JSON.stringify(rcData, null, 2) + "\n");
2728
+ fs2.writeFileSync(rcPath2, `${JSON.stringify(rcData, null, 2)}
2729
+ `);
2316
2730
  addToGitignore(".connectbaserc");
2317
2731
  success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
2318
2732
  tunnelKey = sk;
@@ -2345,14 +2759,30 @@ async function startTunnel(port, config, tunnelOpts) {
2345
2759
  }
2346
2760
  if (rcData.tunnelAppId !== appId) {
2347
2761
  rcData.tunnelAppId = appId;
2348
- fs2.writeFileSync(rcPath, JSON.stringify(rcData, null, 2) + "\n");
2762
+ fs2.writeFileSync(rcPath, `${JSON.stringify(rcData, null, 2)}
2763
+ `);
2349
2764
  }
2350
2765
  } catch {
2351
2766
  }
2352
2767
  const lockPath = acquireTunnelLock2(appId, port, tunnelOpts?.force ?? false);
2353
- const tunnelServerUrl = getTunnelServerUrl(config.baseUrl);
2354
- const parsedUrl = new URL(tunnelServerUrl);
2355
- const isHttps = parsedUrl.protocol === "https:";
2768
+ const selector = new TunnelConnectSelector(
2769
+ getTunnelConnectCandidates(config.baseUrl)
2770
+ );
2771
+ let announcedFallback = false;
2772
+ const currentUrl = () => new URL(selector.current());
2773
+ const advanceCandidate = (reason) => {
2774
+ const { retry, switchedFrom } = selector.recordFailure();
2775
+ if (switchedFrom && !announcedFallback) {
2776
+ announcedFallback = true;
2777
+ warn(
2778
+ `\uC9C1\uACB0 \uACBD\uB85C(${new URL(switchedFrom).hostname}) \uC5F0\uACB0 \uC2E4\uD328 \u2014 ${reason}`
2779
+ );
2780
+ warn(
2781
+ `Cloudflare \uACBD\uC720(${new URL(selector.current()).hostname})\uB85C \uC804\uD658\uD569\uB2C8\uB2E4. \uB3D9\uC791\uD558\uC9C0\uB9CC \uC9C0\uC5F0\uC774 \uCEE4\uC9D1\uB2C8\uB2E4.`
2782
+ );
2783
+ }
2784
+ return retry;
2785
+ };
2356
2786
  let wsPath = `/v2/tunnel/connect?app_id=${encodeURIComponent(appId)}&local_port=${port}&ws_opcode=1`;
2357
2787
  if (tunnelOpts?.timeout) {
2358
2788
  wsPath += `&timeout=${tunnelOpts.timeout}`;
@@ -2403,21 +2833,23 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2403
2833
  log(`${colors.dim}\uB85C\uCEEC \uD3EC\uD2B8 ${port}\uB97C \uC778\uD130\uB137\uC5D0 \uB178\uCD9C\uD569\uB2C8\uB2E4${colors.reset}
2404
2834
  `);
2405
2835
  function connect() {
2406
- const lib = isHttps ? https : http;
2836
+ const target = currentUrl();
2837
+ const targetHttps = target.protocol === "https:";
2838
+ const lib = targetHttps ? https : http;
2407
2839
  const wsKey = crypto.randomBytes(16).toString("base64");
2408
2840
  const reqOptions = {
2409
- hostname: parsedUrl.hostname,
2410
- port: parsedUrl.port || (isHttps ? 443 : 80),
2841
+ hostname: target.hostname,
2842
+ port: target.port || (targetHttps ? 443 : 80),
2411
2843
  path: wsPath,
2412
2844
  method: "GET",
2413
2845
  family: 4,
2414
2846
  autoSelectFamily: false,
2415
2847
  headers: {
2416
- "Upgrade": "websocket",
2417
- "Connection": "Upgrade",
2848
+ Upgrade: "websocket",
2849
+ Connection: "Upgrade",
2418
2850
  "Sec-WebSocket-Key": wsKey,
2419
2851
  "Sec-WebSocket-Version": "13",
2420
- "Authorization": `Bearer ${config.secretKey ?? config.publicKey}`
2852
+ Authorization: `Bearer ${config.secretKey ?? config.publicKey}`
2421
2853
  }
2422
2854
  };
2423
2855
  const req = lib.request(reqOptions);
@@ -2425,6 +2857,7 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2425
2857
  socket = sock;
2426
2858
  reconnectAttempts = 0;
2427
2859
  everConnected = true;
2860
+ selector.recordSuccess();
2428
2861
  sock.setNoDelay(true);
2429
2862
  sock.setKeepAlive(true, 3e4);
2430
2863
  const idleTimeoutMs = 135e3;
@@ -2432,7 +2865,9 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2432
2865
  const armIdleTimer = () => {
2433
2866
  if (idleTimer) clearTimeout(idleTimer);
2434
2867
  idleTimer = setTimeout(() => {
2435
- warn(`${idleTimeoutMs / 1e3}\uCD08\uAC04 \uC11C\uBC84 \uC218\uC2E0 \uC5C6\uC74C \u2014 \uB04A\uAE34 \uC5F0\uACB0(half-open)\uB85C \uD310\uB2E8\uD558\uACE0 \uC7AC\uC5F0\uACB0\uD569\uB2C8\uB2E4`);
2868
+ warn(
2869
+ `${idleTimeoutMs / 1e3}\uCD08\uAC04 \uC11C\uBC84 \uC218\uC2E0 \uC5C6\uC74C \u2014 \uB04A\uAE34 \uC5F0\uACB0(half-open)\uB85C \uD310\uB2E8\uD558\uACE0 \uC7AC\uC5F0\uACB0\uD569\uB2C8\uB2E4`
2870
+ );
2436
2871
  sock.destroy();
2437
2872
  }, idleTimeoutMs);
2438
2873
  };
@@ -2442,7 +2877,9 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2442
2877
  try {
2443
2878
  const msg = JSON.parse(data);
2444
2879
  handleMessage(msg, sock, port).catch((e) => {
2445
- error(`\uBA54\uC2DC\uC9C0 \uCC98\uB9AC \uC911 \uC608\uC678: ${e instanceof Error ? e.message : e}`);
2880
+ error(
2881
+ `\uBA54\uC2DC\uC9C0 \uCC98\uB9AC \uC911 \uC608\uC678: ${e instanceof Error ? e.message : e}`
2882
+ );
2446
2883
  });
2447
2884
  } catch (e) {
2448
2885
  warn(`\uBA54\uC2DC\uC9C0 \uD30C\uC2F1 \uC2E4\uD328: ${e}`);
@@ -2486,6 +2923,7 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2486
2923
  });
2487
2924
  });
2488
2925
  req.on("error", (err) => {
2926
+ advanceCandidate(err.message);
2489
2927
  if (reconnectAttempts === 0) {
2490
2928
  error(`\uD130\uB110 \uC11C\uBC84 \uC5F0\uACB0 \uC2E4\uD328: ${err.message}`);
2491
2929
  }
@@ -2495,7 +2933,9 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2495
2933
  });
2496
2934
  req.on("response", (res) => {
2497
2935
  let body = "";
2498
- res.on("data", (chunk) => body += chunk.toString());
2936
+ res.on("data", (chunk) => {
2937
+ body += chunk.toString();
2938
+ });
2499
2939
  res.on("end", () => {
2500
2940
  const status = res.statusCode ?? 0;
2501
2941
  let serverMsg = body;
@@ -2514,10 +2954,16 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2514
2954
  process.exit(1);
2515
2955
  } else if (status === 403) {
2516
2956
  error(`\uC811\uADFC \uAC70\uBD80: ${serverMsg}`);
2517
- error("\u2192 app_id \uAC00 \uCF58\uC194\uC758 \uC571/\uD504\uB85C\uC81D\uD2B8 ID \uC778\uC9C0 \uD655\uC778\uD558\uC138\uC694 (\uACF5\uAC1C\uD0A4 UUID \uB098 storageId \uAC00 \uC544\uB2D9\uB2C8\uB2E4).");
2957
+ error(
2958
+ "\u2192 app_id \uAC00 \uCF58\uC194\uC758 \uC571/\uD504\uB85C\uC81D\uD2B8 ID \uC778\uC9C0 \uD655\uC778\uD558\uC138\uC694 (\uACF5\uAC1C\uD0A4 UUID \uB098 storageId \uAC00 \uC544\uB2D9\uB2C8\uB2E4)."
2959
+ );
2518
2960
  shouldReconnect = false;
2519
2961
  process.exit(1);
2520
2962
  } else if (status >= 400 && status < 500 && status !== 429) {
2963
+ if (advanceCandidate(`HTTP ${status}`) && shouldReconnect) {
2964
+ scheduleReconnect();
2965
+ return;
2966
+ }
2521
2967
  error(`\uD130\uB110 \uC5F0\uACB0 \uAC70\uBD80 (${status}): ${serverMsg}`);
2522
2968
  shouldReconnect = false;
2523
2969
  process.exit(1);
@@ -2539,7 +2985,10 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2539
2985
  error("\uCD5C\uB300 \uC7AC\uC5F0\uACB0 \uC2DC\uB3C4 \uD69F\uC218 \uCD08\uACFC");
2540
2986
  process.exit(1);
2541
2987
  }
2542
- const delay = Math.min(2e3 * Math.pow(2, Math.min(reconnectAttempts - 1, 8)), 3e4);
2988
+ const delay = Math.min(
2989
+ 2e3 * 2 ** Math.min(reconnectAttempts - 1, 8),
2990
+ 3e4
2991
+ );
2543
2992
  const attemptLabel = everConnected ? `\uC2DC\uB3C4 ${reconnectAttempts}` : `${reconnectAttempts}/${maxReconnectAttempts}`;
2544
2993
  info(`${(delay / 1e3).toFixed(0)}\uCD08 \uD6C4 \uC7AC\uC5F0\uACB0 \uC2DC\uB3C4... (${attemptLabel})`);
2545
2994
  reconnectTimer = setTimeout(() => {
@@ -2578,11 +3027,18 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2578
3027
  for (const [k, v] of Object.entries(headers)) {
2579
3028
  if (k.toLowerCase() !== "host") localHeaders[k] = v;
2580
3029
  }
2581
- localHeaders["host"] = `localhost:${localPort}`;
3030
+ localHeaders.host = `localhost:${localPort}`;
2582
3031
  let started = false;
2583
3032
  let cancelled = false;
2584
3033
  const localReq = http.request(
2585
- { hostname: "127.0.0.1", port: localPort, path: fullPath, method, headers: localHeaders, agent: localAgent },
3034
+ {
3035
+ hostname: "127.0.0.1",
3036
+ port: localPort,
3037
+ path: fullPath,
3038
+ method,
3039
+ headers: localHeaders,
3040
+ agent: localAgent
3041
+ },
2586
3042
  (res) => {
2587
3043
  const respHeaders = {};
2588
3044
  for (const [k, v] of Object.entries(res.headers)) {
@@ -2597,19 +3053,29 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2597
3053
  headers: respHeaders
2598
3054
  });
2599
3055
  const methodColor = method === "GET" ? colors.green : method === "POST" ? colors.blue : colors.yellow;
2600
- log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode}`);
3056
+ log(
3057
+ `${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode}`
3058
+ );
2601
3059
  res.on("data", (chunk) => {
2602
3060
  if (cancelled) return;
2603
3061
  sendBinary(sock, streamId, chunk);
2604
3062
  });
2605
3063
  res.on("end", () => {
2606
3064
  if (cancelled) return;
2607
- sendControl(sock, { type: "stream_eof", stream_id: streamId, side: "upstream" });
3065
+ sendControl(sock, {
3066
+ type: "stream_eof",
3067
+ stream_id: streamId,
3068
+ side: "upstream"
3069
+ });
2608
3070
  sendControl(sock, { type: "stream_close", stream_id: streamId });
2609
3071
  streams.delete(streamId);
2610
3072
  });
2611
3073
  res.on("error", (err) => {
2612
- sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
3074
+ sendControl(sock, {
3075
+ type: "stream_close",
3076
+ stream_id: streamId,
3077
+ error: `upstream_error: ${err.message}`
3078
+ });
2613
3079
  streams.delete(streamId);
2614
3080
  });
2615
3081
  }
@@ -2629,9 +3095,19 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2629
3095
  status: 502,
2630
3096
  headers: { "content-type": "application/json" }
2631
3097
  });
2632
- sendBinary(sock, streamId, Buffer.from(JSON.stringify({ error: `Local server error: ${err.message}` })));
3098
+ sendBinary(
3099
+ sock,
3100
+ streamId,
3101
+ Buffer.from(
3102
+ JSON.stringify({ error: `Local server error: ${err.message}` })
3103
+ )
3104
+ );
2633
3105
  }
2634
- sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
3106
+ sendControl(sock, {
3107
+ type: "stream_close",
3108
+ stream_id: streamId,
3109
+ error: `upstream_error: ${err.message}`
3110
+ });
2635
3111
  streams.delete(streamId);
2636
3112
  });
2637
3113
  const forwarder = {
@@ -2664,9 +3140,9 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2664
3140
  if (lk === "sec-websocket-extensions") continue;
2665
3141
  localHeaders[k] = v;
2666
3142
  }
2667
- localHeaders["host"] = `localhost:${localPort}`;
2668
- localHeaders["connection"] = "Upgrade";
2669
- localHeaders["upgrade"] = "websocket";
3143
+ localHeaders.host = `localhost:${localPort}`;
3144
+ localHeaders.connection = "Upgrade";
3145
+ localHeaders.upgrade = "websocket";
2670
3146
  let cancelled = false;
2671
3147
  let upstream = null;
2672
3148
  const req = http.request({
@@ -2694,12 +3170,18 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2694
3170
  headers: respHeaders,
2695
3171
  websocket: true
2696
3172
  });
2697
- log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${colors.cyan}WS${colors.reset} ${reqPath} \u2192 101`);
3173
+ log(
3174
+ `${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${colors.cyan}WS${colors.reset} ${reqPath} \u2192 101`
3175
+ );
2698
3176
  const upstreamParser = new UpstreamWsFrameParser({
2699
3177
  onPayload: (payload, opcode) => {
2700
3178
  if (cancelled) return;
2701
3179
  if (negotiatedWsOpcode) {
2702
- sendBinary(sock, streamId, Buffer.concat([Buffer.from([opcode & 15]), payload]));
3180
+ sendBinary(
3181
+ sock,
3182
+ streamId,
3183
+ Buffer.concat([Buffer.from([opcode & 15]), payload])
3184
+ );
2703
3185
  } else {
2704
3186
  sendBinary(sock, streamId, payload);
2705
3187
  }
@@ -2714,7 +3196,11 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2714
3196
  streams.delete(streamId);
2715
3197
  },
2716
3198
  onError: (err) => {
2717
- sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_frame_error: ${err.message}` });
3199
+ sendControl(sock, {
3200
+ type: "stream_close",
3201
+ stream_id: streamId,
3202
+ error: `upstream_frame_error: ${err.message}`
3203
+ });
2718
3204
  streams.delete(streamId);
2719
3205
  }
2720
3206
  });
@@ -2728,7 +3214,11 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2728
3214
  streams.delete(streamId);
2729
3215
  });
2730
3216
  sk.on("error", (err) => {
2731
- sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
3217
+ sendControl(sock, {
3218
+ type: "stream_close",
3219
+ stream_id: streamId,
3220
+ error: `upstream_error: ${err.message}`
3221
+ });
2732
3222
  streams.delete(streamId);
2733
3223
  });
2734
3224
  });
@@ -2749,7 +3239,11 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2749
3239
  streams.delete(streamId);
2750
3240
  });
2751
3241
  req.on("error", (err) => {
2752
- sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
3242
+ sendControl(sock, {
3243
+ type: "stream_close",
3244
+ stream_id: streamId,
3245
+ error: `upstream_error: ${err.message}`
3246
+ });
2753
3247
  streams.delete(streamId);
2754
3248
  });
2755
3249
  req.end();
@@ -2799,30 +3293,52 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2799
3293
  const readyFeatures = Array.isArray(msg.features) ? msg.features : [];
2800
3294
  negotiatedWsOpcode = readyFeatures.includes("ws_opcode");
2801
3295
  success(`\uD130\uB110 \uD65C\uC131\uD654!`);
2802
- log(`${colors.green}\u2192${colors.reset} URL: ${colors.cyan}${tunnelUrl}${colors.reset}`);
2803
- log(`${colors.green}\u2192${colors.reset} \uB85C\uCEEC: ${colors.cyan}http://localhost:${localPort}${colors.reset}`);
3296
+ log(
3297
+ `${colors.green}\u2192${colors.reset} URL: ${colors.cyan}${tunnelUrl}${colors.reset}`
3298
+ );
3299
+ log(
3300
+ `${colors.green}\u2192${colors.reset} \uB85C\uCEEC: ${colors.cyan}http://localhost:${localPort}${colors.reset}`
3301
+ );
2804
3302
  if (msg.timeout || msg.max_body) {
2805
- log(`${colors.green}\u2192${colors.reset} \uC124\uC815: timeout=${colors.cyan}${msg.timeout}s${colors.reset}, max-body=${colors.cyan}${msg.max_body}MB${colors.reset}`);
3303
+ log(
3304
+ `${colors.green}\u2192${colors.reset} \uC124\uC815: timeout=${colors.cyan}${msg.timeout}s${colors.reset}, max-body=${colors.cyan}${msg.max_body}MB${colors.reset}`
3305
+ );
2806
3306
  }
2807
3307
  if (isPublic) {
2808
3308
  log("");
2809
- log(`${colors.yellow}\u26A0 \uACF5\uAC1C \uBAA8\uB4DC (--public):${colors.reset} proxy_token \uAC80\uC99D\uC774 \uBE44\uD65C\uC131\uD654\uB410\uC2B5\uB2C8\uB2E4.`);
2810
- log(` ${colors.dim}tunnelID \uB97C \uC544\uB294 \uB204\uAD6C\uB098 \uC774 URL \uB85C \uB85C\uCEEC \uC11C\uBE44\uC2A4\uC5D0 \uC811\uADFC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.${colors.reset}`);
2811
- log(` ${colors.dim}\uC6F9\uD6C5 \uC218\uC2E0 / \uC678\uBD80 \uC11C\uBE44\uC2A4 \uC5F0\uB3D9 \uBAA9\uC801\uC5D0 \uD55C\uD574 \uC0AC\uC6A9\uD558\uACE0, \uBBFC\uAC10 \uB370\uC774\uD130\uB294 \uCDE8\uAE09\uD558\uC9C0 \uB9C8\uC138\uC694.${colors.reset}`);
3309
+ log(
3310
+ `${colors.yellow}\u26A0 \uACF5\uAC1C \uBAA8\uB4DC (--public):${colors.reset} proxy_token \uAC80\uC99D\uC774 \uBE44\uD65C\uC131\uD654\uB410\uC2B5\uB2C8\uB2E4.`
3311
+ );
3312
+ log(
3313
+ ` ${colors.dim}tunnelID \uB97C \uC544\uB294 \uB204\uAD6C\uB098 \uC774 URL \uB85C \uB85C\uCEEC \uC11C\uBE44\uC2A4\uC5D0 \uC811\uADFC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.${colors.reset}`
3314
+ );
3315
+ log(
3316
+ ` ${colors.dim}\uC6F9\uD6C5 \uC218\uC2E0 / \uC678\uBD80 \uC11C\uBE44\uC2A4 \uC5F0\uB3D9 \uBAA9\uC801\uC5D0 \uD55C\uD574 \uC0AC\uC6A9\uD558\uACE0, \uBBFC\uAC10 \uB370\uC774\uD130\uB294 \uCDE8\uAE09\uD558\uC9C0 \uB9C8\uC138\uC694.${colors.reset}`
3317
+ );
2812
3318
  log("");
2813
3319
  log(`${colors.dim} $ curl ${tunnelUrl}/${colors.reset}`);
2814
3320
  } else if (tunnelOpts?.showToken && proxyToken && tunnelUrl) {
2815
3321
  log("");
2816
- log(`${colors.green}\u2192${colors.reset} \uD1A0\uD070: ${colors.yellow}${proxyToken}${colors.reset}`);
2817
- log(`
2818
- ${colors.dim}\uC678\uBD80 \uC9C1\uC811 \uD638\uCD9C \uC2DC \uB2E4\uC74C \uD5E4\uB354 \uB610\uB294 \uCFFC\uB9AC\uB85C \uD1A0\uD070\uC744 \uC804\uB2EC\uD558\uC138\uC694:${colors.reset}`);
2819
- log(`${colors.dim} $ curl -H "X-Proxy-Token: ${proxyToken}" ${tunnelUrl}/${colors.reset}`);
2820
- log(`${colors.dim} $ curl "${tunnelUrl}/?proxy_token=${proxyToken}"${colors.reset}`);
3322
+ log(
3323
+ `${colors.green}\u2192${colors.reset} \uD1A0\uD070: ${colors.yellow}${proxyToken}${colors.reset}`
3324
+ );
3325
+ log(
3326
+ `
3327
+ ${colors.dim}\uC678\uBD80 \uC9C1\uC811 \uD638\uCD9C \uC2DC \uB2E4\uC74C \uD5E4\uB354 \uB610\uB294 \uCFFC\uB9AC\uB85C \uD1A0\uD070\uC744 \uC804\uB2EC\uD558\uC138\uC694:${colors.reset}`
3328
+ );
3329
+ log(
3330
+ `${colors.dim} $ curl -H "X-Proxy-Token: ${proxyToken}" ${tunnelUrl}/${colors.reset}`
3331
+ );
3332
+ log(
3333
+ `${colors.dim} $ curl "${tunnelUrl}/?proxy_token=${proxyToken}"${colors.reset}`
3334
+ );
2821
3335
  }
2822
3336
  if (tunnelOpts?.label) {
2823
3337
  const tunnelId = typeof msg.tunnel_id === "string" ? msg.tunnel_id : "";
2824
3338
  if (!tunnelId) {
2825
- error(`tunnel_ready \uBA54\uC2DC\uC9C0\uC5D0 tunnel_id \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 endpoint "${tunnelOpts.label}" \uC790\uB3D9 \uB4F1\uB85D skip`);
3339
+ error(
3340
+ `tunnel_ready \uBA54\uC2DC\uC9C0\uC5D0 tunnel_id \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 endpoint "${tunnelOpts.label}" \uC790\uB3D9 \uB4F1\uB85D skip`
3341
+ );
2826
3342
  } else {
2827
3343
  await registerEndpointBinding(
2828
3344
  config.baseUrl,
@@ -3098,25 +3614,34 @@ async function main() {
3098
3614
  } else if (parsed.command === "deploy") {
3099
3615
  const directory = parsed.args[0] || fileConfig.deployDir || ".";
3100
3616
  if (!config.publicKey) {
3101
- error('Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694');
3617
+ error(
3618
+ 'Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3619
+ );
3102
3620
  process.exit(1);
3103
3621
  }
3104
3622
  if (!config.storageId) {
3105
- error('\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694');
3623
+ error(
3624
+ '\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3625
+ );
3106
3626
  process.exit(1);
3107
3627
  }
3108
3628
  const isDev = parsed.options.dev === "true";
3109
3629
  const deployOpts = {};
3110
3630
  if (parsed.options.timeout) {
3111
3631
  const t = parseInt(parsed.options.timeout, 10);
3112
- if (!isNaN(t) && t > 0) deployOpts.timeoutMs = t * 1e3;
3113
- else warn(`--timeout \uAC12\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544 \uBB34\uC2DC\uB429\uB2C8\uB2E4: ${parsed.options.timeout}`);
3632
+ if (!Number.isNaN(t) && t > 0) deployOpts.timeoutMs = t * 1e3;
3633
+ else
3634
+ warn(
3635
+ `--timeout \uAC12\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544 \uBB34\uC2DC\uB429\uB2C8\uB2E4: ${parsed.options.timeout}`
3636
+ );
3114
3637
  }
3115
3638
  await deploy(directory, config, isDev, deployOpts);
3116
3639
  if (parsed.options.skipDocs !== "true") {
3117
3640
  try {
3118
3641
  const docsRoot = getGitRoot() || getProjectRoot();
3119
- if (fs2.existsSync(path2.join(docsRoot, ".claude", "docs", DOCS_MANIFEST_FILE))) {
3642
+ if (fs2.existsSync(
3643
+ path2.join(docsRoot, ".claude", "docs", DOCS_MANIFEST_FILE)
3644
+ )) {
3120
3645
  const refreshed = await downloadDocs(docsRoot, { quiet: true });
3121
3646
  if (refreshed === "updated") {
3122
3647
  info("SDK \uBB38\uC11C\uAC00 \uC0C8 \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (.claude/docs/)");
@@ -3127,11 +3652,15 @@ async function main() {
3127
3652
  }
3128
3653
  } else if (parsed.command === "promote" || parsed.command === "releases") {
3129
3654
  if (!config.publicKey) {
3130
- error('Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694');
3655
+ error(
3656
+ 'Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3657
+ );
3131
3658
  process.exit(1);
3132
3659
  }
3133
3660
  if (!config.storageId) {
3134
- error('\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694');
3661
+ error(
3662
+ '\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3663
+ );
3135
3664
  process.exit(1);
3136
3665
  }
3137
3666
  if (parsed.command === "promote") {
@@ -3146,18 +3675,18 @@ async function main() {
3146
3675
  process.exit(1);
3147
3676
  }
3148
3677
  const port = parseInt(portStr, 10);
3149
- if (isNaN(port) || port < 1 || port > 65535) {
3678
+ if (Number.isNaN(port) || port < 1 || port > 65535) {
3150
3679
  error("\uC720\uD6A8\uD55C \uD3EC\uD2B8 \uBC88\uD638\uB97C \uC785\uB825\uD558\uC138\uC694 (1-65535)");
3151
3680
  process.exit(1);
3152
3681
  }
3153
3682
  const tunnelOpts = {};
3154
3683
  if (parsed.options.timeout) {
3155
3684
  const t = parseInt(parsed.options.timeout, 10);
3156
- if (!isNaN(t) && t > 0) tunnelOpts.timeout = t;
3685
+ if (!Number.isNaN(t) && t > 0) tunnelOpts.timeout = t;
3157
3686
  }
3158
3687
  if (parsed.options.maxBody) {
3159
3688
  const m = parseInt(parsed.options.maxBody, 10);
3160
- if (!isNaN(m) && m > 0) tunnelOpts.maxBody = m;
3689
+ if (!Number.isNaN(m) && m > 0) tunnelOpts.maxBody = m;
3161
3690
  }
3162
3691
  if (parsed.options.appId) {
3163
3692
  tunnelOpts.appId = parsed.options.appId;
@@ -3197,7 +3726,9 @@ main().catch((err) => {
3197
3726
  });
3198
3727
  // Annotate the CommonJS export names for ESM import in node:
3199
3728
  0 && (module.exports = {
3729
+ TunnelConnectSelector,
3200
3730
  computeDeployDiff,
3731
+ getTunnelConnectCandidates,
3201
3732
  guessMimeType,
3202
3733
  isAllowedDeployExt,
3203
3734
  isBinaryDeployExt,