connectbase-client 0.14.0 → 0.15.1

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
@@ -24,13 +24,90 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/cli.ts
27
- var fs = __toESM(require("fs"));
28
- var path = __toESM(require("path"));
29
- var os = __toESM(require("os"));
27
+ var fs2 = __toESM(require("fs"));
28
+ var path2 = __toESM(require("path"));
30
29
  var crypto = __toESM(require("crypto"));
31
30
  var https = __toESM(require("https"));
32
31
  var http = __toESM(require("http"));
33
32
  var readline = __toESM(require("readline"));
33
+
34
+ // src/tunnel-utils.ts
35
+ var fs = __toESM(require("fs"));
36
+ var path = __toESM(require("path"));
37
+ var os = __toESM(require("os"));
38
+ function getTunnelLockDir() {
39
+ const platform2 = os.platform();
40
+ if (platform2 === "darwin") {
41
+ return path.join(os.homedir(), "Library", "Application Support", "connectbase", "tunnel-locks");
42
+ } else if (platform2 === "win32") {
43
+ return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "connectbase", "tunnel-locks");
44
+ }
45
+ const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
46
+ return path.join(stateHome, "connectbase", "tunnel-locks");
47
+ }
48
+ function isProcessAlive(pid) {
49
+ try {
50
+ process.kill(pid, 0);
51
+ return true;
52
+ } catch (e) {
53
+ return e.code === "EPERM";
54
+ }
55
+ }
56
+ function acquireTunnelLock(appID, port, force, version) {
57
+ const lockDir = getTunnelLockDir();
58
+ fs.mkdirSync(lockDir, { recursive: true });
59
+ const lockPath = path.join(lockDir, `${appID}-${port}.lock`);
60
+ const lockData = {
61
+ pid: process.pid,
62
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
63
+ appID,
64
+ port,
65
+ host: os.hostname(),
66
+ version
67
+ };
68
+ if (!force) {
69
+ try {
70
+ const fd = fs.openSync(lockPath, "wx");
71
+ fs.writeSync(fd, JSON.stringify(lockData, null, 2));
72
+ fs.closeSync(fd);
73
+ return lockPath;
74
+ } catch (e) {
75
+ if (e.code !== "EEXIST") {
76
+ return null;
77
+ }
78
+ }
79
+ try {
80
+ const existing = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
81
+ if (isProcessAlive(existing.pid)) {
82
+ return `LOCKED:${existing.pid}:${existing.startedAt}:${existing.host}`;
83
+ }
84
+ } catch {
85
+ }
86
+ }
87
+ fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
88
+ return lockPath;
89
+ }
90
+ function releaseTunnelLock(lockPath) {
91
+ try {
92
+ fs.unlinkSync(lockPath);
93
+ } catch {
94
+ }
95
+ }
96
+ function handleTunnelError(msg, appId, localPort) {
97
+ if (msg.code === "replaced") {
98
+ return {
99
+ action: "exit",
100
+ message: `\u26A0 \uB2E4\uB978 \uC138\uC158\uC774 \uAC19\uC740 \uC571+\uD3EC\uD2B8(${appId}:${localPort})\uB85C \uC5F0\uACB0\uB418\uC5B4 \uC774 \uC138\uC158\uC774 \uC885\uB8CC\uB429\uB2C8\uB2E4. \uB3D9\uC77C \uBA38\uC2E0\uC5D0\uC11C \uB450 \uBC88 \uC2E4\uD589\uD558\uC9C0 \uB9C8\uC138\uC694.`,
101
+ exitCode: 1
102
+ };
103
+ }
104
+ return {
105
+ action: "warn",
106
+ message: `\uD130\uB110 \uC5D0\uB7EC: ${msg.error || msg.message || "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uB7EC"}`
107
+ };
108
+ }
109
+
110
+ // src/cli.ts
34
111
  var VERSION = "0.10.6";
35
112
  var DEFAULT_BASE_URL = "https://api.connectbase.world";
36
113
  var colors = {
@@ -119,10 +196,10 @@ function loadConfig() {
119
196
  storageId: process.env.CONNECTBASE_STORAGE_ID,
120
197
  baseUrl: process.env.CONNECTBASE_BASE_URL || DEFAULT_BASE_URL
121
198
  };
122
- const rcPath = path.join(process.cwd(), ".connectbaserc");
123
- if (fs.existsSync(rcPath)) {
199
+ const rcPath = path2.join(process.cwd(), ".connectbaserc");
200
+ if (fs2.existsSync(rcPath)) {
124
201
  try {
125
- const rcContent = JSON.parse(fs.readFileSync(rcPath, "utf-8"));
202
+ const rcContent = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
126
203
  if (rcContent.publicKey) config.publicKey = rcContent.publicKey;
127
204
  if (rcContent.secretKey) config.secretKey = rcContent.secretKey;
128
205
  if (rcContent.storageId) config.storageId = rcContent.storageId;
@@ -136,16 +213,16 @@ function loadConfig() {
136
213
  }
137
214
  function collectFiles(dir, baseDir = dir) {
138
215
  const files = [];
139
- const entries = fs.readdirSync(dir, { withFileTypes: true });
216
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
140
217
  for (const entry of entries) {
141
- const fullPath = path.join(dir, entry.name);
142
- const relativePath = path.relative(baseDir, fullPath);
218
+ const fullPath = path2.join(dir, entry.name);
219
+ const relativePath = path2.relative(baseDir, fullPath);
143
220
  if (entry.isDirectory()) {
144
221
  if (!entry.name.startsWith(".")) {
145
222
  files.push(...collectFiles(fullPath, baseDir));
146
223
  }
147
224
  } else if (entry.isFile()) {
148
- const ext = path.extname(entry.name).toLowerCase();
225
+ const ext = path2.extname(entry.name).toLowerCase();
149
226
  if (!ALLOWED_EXTENSIONS.has(ext)) {
150
227
  continue;
151
228
  }
@@ -155,9 +232,9 @@ function collectFiles(dir, baseDir = dir) {
155
232
  const isBinary = BINARY_EXTENSIONS.has(ext);
156
233
  let content;
157
234
  if (isBinary) {
158
- content = fs.readFileSync(fullPath).toString("base64");
235
+ content = fs2.readFileSync(fullPath).toString("base64");
159
236
  } else {
160
- content = fs.readFileSync(fullPath, "utf-8");
237
+ content = fs2.readFileSync(fullPath, "utf-8");
161
238
  }
162
239
  files.push({
163
240
  path: relativePath.replace(/\\/g, "/"),
@@ -213,15 +290,15 @@ async function makeRequest(url, method, headers, body) {
213
290
  });
214
291
  }
215
292
  async function deploy(directory, config, isDev = false) {
216
- const dir = path.resolve(directory);
217
- if (!fs.existsSync(dir)) {
293
+ const dir = path2.resolve(directory);
294
+ if (!fs2.existsSync(dir)) {
218
295
  error(`\uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${dir}`);
219
296
  process.exit(1);
220
297
  }
221
- const pkgPath = path.join(process.cwd(), "package.json");
222
- if (fs.existsSync(pkgPath)) {
298
+ const pkgPath = path2.join(process.cwd(), "package.json");
299
+ if (fs2.existsSync(pkgPath)) {
223
300
  try {
224
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
301
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
225
302
  if (pkg.scripts?.build) {
226
303
  warn(`\uBC30\uD3EC \uC804 \uBE4C\uB4DC\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694: ${colors.cyan}npm run build && npx connectbase-client deploy${colors.reset}`);
227
304
  warn(`\uB610\uB294 package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uB4F1\uB85D\uD558\uC138\uC694: ${colors.cyan}npx connectbase-client init${colors.reset}`);
@@ -229,12 +306,12 @@ async function deploy(directory, config, isDev = false) {
229
306
  } catch {
230
307
  }
231
308
  }
232
- if (!fs.statSync(dir).isDirectory()) {
309
+ if (!fs2.statSync(dir).isDirectory()) {
233
310
  error(`${dir}\uB294 \uB514\uB809\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4`);
234
311
  process.exit(1);
235
312
  }
236
- const indexPath = path.join(dir, "index.html");
237
- if (!fs.existsSync(indexPath)) {
313
+ const indexPath = path2.join(dir, "index.html");
314
+ if (!fs2.existsSync(indexPath)) {
238
315
  error("index.html \uD30C\uC77C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
239
316
  process.exit(1);
240
317
  }
@@ -450,8 +527,8 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
450
527
  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
451
528
  `);
452
529
  }
453
- const rcPath = path.join(cwd, ".connectbaserc");
454
- if (fs.existsSync(rcPath)) {
530
+ const rcPath = path2.join(cwd, ".connectbaserc");
531
+ if (fs2.existsSync(rcPath)) {
455
532
  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): `);
456
533
  if (overwrite.toLowerCase() !== "y") {
457
534
  info("\uCD08\uAE30\uD654\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
@@ -540,7 +617,7 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
540
617
  }
541
618
  }
542
619
  if (!appId) {
543
- const projectName = path.basename(cwd);
620
+ const projectName = path2.basename(cwd);
544
621
  const appName = await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
545
622
  info("\uC571 \uC0DD\uC131 \uC911...");
546
623
  const createRes = await makeRequest(
@@ -608,7 +685,7 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
608
685
  }
609
686
  }
610
687
  if (!storageId) {
611
- const projectName = path.basename(cwd);
688
+ const projectName = path2.basename(cwd);
612
689
  const name = await prompt(`${colors.blue}?${colors.reset} \uC2A4\uD1A0\uB9AC\uC9C0 \uC774\uB984 (${projectName}): `) || projectName;
613
690
  info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC911...");
614
691
  const createRes = await makeRequest(
@@ -648,7 +725,7 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
648
725
  if (secretKey) {
649
726
  config.secretKey = secretKey;
650
727
  }
651
- fs.writeFileSync(rcPath, JSON.stringify(config, null, 2) + "\n");
728
+ fs2.writeFileSync(rcPath, JSON.stringify(config, null, 2) + "\n");
652
729
  success(".connectbaserc \uC0DD\uC131 \uC644\uB8CC");
653
730
  addToGitignore(".connectbaserc");
654
731
  addDeployScript(deployDir);
@@ -663,21 +740,21 @@ ${colors.dim}Claude Code\uC5D0\uC11C MCP\uAC00 \uC790\uB3D9 \uC5F0\uACB0\uB429\u
663
740
  `);
664
741
  }
665
742
  function detectBuildDir() {
666
- if (fs.existsSync(path.join(process.cwd(), "dist"))) return "./dist";
667
- if (fs.existsSync(path.join(process.cwd(), "build"))) return "./build";
668
- if (fs.existsSync(path.join(process.cwd(), "out"))) return "./out";
669
- if (fs.existsSync(path.join(process.cwd(), ".next"))) return "./out";
670
- if (fs.existsSync(path.join(process.cwd(), "vite.config.ts")) || fs.existsSync(path.join(process.cwd(), "vite.config.js"))) return "./dist";
671
- if (fs.existsSync(path.join(process.cwd(), "next.config.js")) || fs.existsSync(path.join(process.cwd(), "next.config.mjs"))) return "./out";
743
+ if (fs2.existsSync(path2.join(process.cwd(), "dist"))) return "./dist";
744
+ if (fs2.existsSync(path2.join(process.cwd(), "build"))) return "./build";
745
+ if (fs2.existsSync(path2.join(process.cwd(), "out"))) return "./out";
746
+ if (fs2.existsSync(path2.join(process.cwd(), ".next"))) return "./out";
747
+ if (fs2.existsSync(path2.join(process.cwd(), "vite.config.ts")) || fs2.existsSync(path2.join(process.cwd(), "vite.config.js"))) return "./dist";
748
+ if (fs2.existsSync(path2.join(process.cwd(), "next.config.js")) || fs2.existsSync(path2.join(process.cwd(), "next.config.mjs"))) return "./out";
672
749
  return "./dist";
673
750
  }
674
751
  function addToGitignore(entry, basePath) {
675
752
  const dir = basePath || process.cwd();
676
- const gitignorePath = path.join(dir, ".gitignore");
677
- if (fs.existsSync(gitignorePath)) {
678
- const content = fs.readFileSync(gitignorePath, "utf-8");
753
+ const gitignorePath = path2.join(dir, ".gitignore");
754
+ if (fs2.existsSync(gitignorePath)) {
755
+ const content = fs2.readFileSync(gitignorePath, "utf-8");
679
756
  if (!content.includes(entry)) {
680
- fs.appendFileSync(gitignorePath, `
757
+ fs2.appendFileSync(gitignorePath, `
681
758
  # Connect Base
682
759
  ${entry}
683
760
  `);
@@ -686,20 +763,20 @@ ${entry}
686
763
  info(`.gitignore\uC5D0 \uC774\uBBF8 ${entry}\uAC00 \uD3EC\uD568\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4`);
687
764
  }
688
765
  } else {
689
- fs.writeFileSync(gitignorePath, `# Connect Base
766
+ fs2.writeFileSync(gitignorePath, `# Connect Base
690
767
  ${entry}
691
768
  `);
692
769
  success(".gitignore \uC0DD\uC131 \uC644\uB8CC");
693
770
  }
694
771
  }
695
772
  function addDeployScript(deployDir) {
696
- const pkgPath = path.join(process.cwd(), "package.json");
697
- if (!fs.existsSync(pkgPath)) {
773
+ const pkgPath = path2.join(process.cwd(), "package.json");
774
+ if (!fs2.existsSync(pkgPath)) {
698
775
  warn("package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
699
776
  return;
700
777
  }
701
778
  try {
702
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
779
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
703
780
  if (!pkg.scripts) pkg.scripts = {};
704
781
  if (pkg.scripts.deploy) {
705
782
  info(`package.json\uC5D0 \uC774\uBBF8 deploy \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC788\uC2B5\uB2C8\uB2E4: "${pkg.scripts.deploy}"`);
@@ -707,7 +784,7 @@ function addDeployScript(deployDir) {
707
784
  }
708
785
  const deployCmd = `connectbase-client deploy ${deployDir}`;
709
786
  pkg.scripts.deploy = pkg.scripts.build ? `${pkg.scripts.build} && ${deployCmd}` : deployCmd;
710
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
787
+ fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
711
788
  success(`package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC`);
712
789
  } catch {
713
790
  warn("package.json \uC218\uC815\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4");
@@ -724,32 +801,32 @@ function getGitRoot() {
724
801
  function detectMonorepo(gitRoot) {
725
802
  const cwd = process.cwd();
726
803
  const result = { type: "none", root: gitRoot, isSubPackage: false };
727
- if (fs.existsSync(path.join(gitRoot, "pnpm-workspace.yaml"))) {
804
+ if (fs2.existsSync(path2.join(gitRoot, "pnpm-workspace.yaml"))) {
728
805
  result.type = "pnpm";
729
- } else if (fs.existsSync(path.join(gitRoot, "turbo.json"))) {
806
+ } else if (fs2.existsSync(path2.join(gitRoot, "turbo.json"))) {
730
807
  result.type = "turborepo";
731
- } else if (fs.existsSync(path.join(gitRoot, "nx.json"))) {
808
+ } else if (fs2.existsSync(path2.join(gitRoot, "nx.json"))) {
732
809
  result.type = "nx";
733
- } else if (fs.existsSync(path.join(gitRoot, "lerna.json"))) {
810
+ } else if (fs2.existsSync(path2.join(gitRoot, "lerna.json"))) {
734
811
  result.type = "lerna";
735
812
  } else {
736
- const rootPkgPath = path.join(gitRoot, "package.json");
737
- if (fs.existsSync(rootPkgPath)) {
813
+ const rootPkgPath = path2.join(gitRoot, "package.json");
814
+ if (fs2.existsSync(rootPkgPath)) {
738
815
  try {
739
- const pkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
816
+ const pkg = JSON.parse(fs2.readFileSync(rootPkgPath, "utf-8"));
740
817
  if (pkg.workspaces) {
741
- result.type = fs.existsSync(path.join(gitRoot, "yarn.lock")) ? "yarn" : "npm";
818
+ result.type = fs2.existsSync(path2.join(gitRoot, "yarn.lock")) ? "yarn" : "npm";
742
819
  }
743
820
  } catch {
744
821
  }
745
822
  }
746
823
  }
747
824
  if (result.type !== "none" && cwd !== gitRoot) {
748
- const cwdPkgPath = path.join(cwd, "package.json");
749
- if (fs.existsSync(cwdPkgPath)) {
825
+ const cwdPkgPath = path2.join(cwd, "package.json");
826
+ if (fs2.existsSync(cwdPkgPath)) {
750
827
  result.isSubPackage = true;
751
828
  try {
752
- const pkg = JSON.parse(fs.readFileSync(cwdPkgPath, "utf-8"));
829
+ const pkg = JSON.parse(fs2.readFileSync(cwdPkgPath, "utf-8"));
753
830
  result.subPackageName = pkg.name;
754
831
  } catch {
755
832
  }
@@ -766,13 +843,13 @@ async function downloadDocs(publicKey, templates, baseDir) {
766
843
  if (monorepo.type !== "none") {
767
844
  info(`\uBAA8\uB178\uB808\uD3EC \uAC10\uC9C0: ${monorepo.type} (\uB8E8\uD2B8: ${gitRoot})`);
768
845
  if (monorepo.isSubPackage) {
769
- info(`\uD604\uC7AC \uC11C\uBE0C \uD328\uD0A4\uC9C0: ${monorepo.subPackageName || path.basename(process.cwd())}`);
846
+ info(`\uD604\uC7AC \uC11C\uBE0C \uD328\uD0A4\uC9C0: ${monorepo.subPackageName || path2.basename(process.cwd())}`);
770
847
  }
771
848
  }
772
- const docsDir = path.join(gitRoot, ".claude", "docs");
773
- const rootClaudeMd = path.join(gitRoot, "CLAUDE.md");
774
- if (!fs.existsSync(docsDir)) {
775
- fs.mkdirSync(docsDir, { recursive: true });
849
+ const docsDir = path2.join(gitRoot, ".claude", "docs");
850
+ const rootClaudeMd = path2.join(gitRoot, "CLAUDE.md");
851
+ if (!fs2.existsSync(docsDir)) {
852
+ fs2.mkdirSync(docsDir, { recursive: true });
776
853
  }
777
854
  if (!templates) {
778
855
  templates = ["rules"];
@@ -791,13 +868,13 @@ async function downloadDocs(publicKey, templates, baseDir) {
791
868
  const data = typeof res.data === "string" ? JSON.parse(res.data) : res.data;
792
869
  const sections = data.sections || [];
793
870
  for (const section of sections) {
794
- const filePath = path.join(docsDir, section.filename);
795
- fs.writeFileSync(filePath, section.content);
871
+ const filePath = path2.join(docsDir, section.filename);
872
+ fs2.writeFileSync(filePath, section.content);
796
873
  }
797
874
  success(`${gitRoot}/.claude/docs/ \uC5D0 ${sections.length}\uAC1C \uBB38\uC11C \uC800\uC7A5 \uC644\uB8CC`);
798
875
  updateRootClaudeMd(rootClaudeMd);
799
876
  if (monorepo.isSubPackage) {
800
- const subClaudeMd = path.join(process.cwd(), "CLAUDE.md");
877
+ const subClaudeMd = path2.join(process.cwd(), "CLAUDE.md");
801
878
  createSubPackageClaudeMd(subClaudeMd, gitRoot);
802
879
  }
803
880
  log(`${colors.dim}\u203B SDK \uBB38\uC11C\uB294 MCP search_sdk_docs\uB85C \uAC80\uC0C9\uD558\uC138\uC694${colors.reset}`);
@@ -809,10 +886,10 @@ async function downloadDocs(publicKey, templates, baseDir) {
809
886
 
810
887
  \uC0C1\uC138 \uAD6C\uD604\uBC95\uC740 MCP \`search_sdk_docs\` \uB3C4\uAD6C\uB85C \uAC80\uC0C9\uD558\uC138\uC694.
811
888
  `;
812
- fs.writeFileSync(path.join(docsDir, "project-rules.md"), fallbackContent);
889
+ fs2.writeFileSync(path2.join(docsDir, "project-rules.md"), fallbackContent);
813
890
  updateRootClaudeMd(rootClaudeMd);
814
891
  if (monorepo.isSubPackage) {
815
- const subClaudeMd = path.join(process.cwd(), "CLAUDE.md");
892
+ const subClaudeMd = path2.join(process.cwd(), "CLAUDE.md");
816
893
  createSubPackageClaudeMd(subClaudeMd, gitRoot);
817
894
  }
818
895
  success(`.claude/docs/ \uC0DD\uC131 \uC644\uB8CC (\uAE30\uBCF8)`);
@@ -832,8 +909,8 @@ function updateRootClaudeMd(claudeMdPath) {
832
909
  2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
833
910
  3. \uAC80\uC0C9 \uACB0\uACFC\uC758 \uCF54\uB4DC \uD328\uD134\uC744 \uB530\uB77C \uAD6C\uD604\uD558\uC138\uC694
834
911
  ${endMarker}`;
835
- if (fs.existsSync(claudeMdPath)) {
836
- let content = fs.readFileSync(claudeMdPath, "utf-8");
912
+ if (fs2.existsSync(claudeMdPath)) {
913
+ let content = fs2.readFileSync(claudeMdPath, "utf-8");
837
914
  const startIdx = content.indexOf(startMarker);
838
915
  const endIdx = content.indexOf(endMarker);
839
916
  if (startIdx !== -1 && endIdx !== -1) {
@@ -841,10 +918,10 @@ ${endMarker}`;
841
918
  } else {
842
919
  content = sdkBlock + "\n\n" + content.trimStart();
843
920
  }
844
- fs.writeFileSync(claudeMdPath, content);
921
+ fs2.writeFileSync(claudeMdPath, content);
845
922
  info("CLAUDE.md\uC5D0 ConnectBase \uCC38\uC870 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
846
923
  } else {
847
- fs.writeFileSync(claudeMdPath, `# \uD504\uB85C\uC81D\uD2B8
924
+ fs2.writeFileSync(claudeMdPath, `# \uD504\uB85C\uC81D\uD2B8
848
925
 
849
926
  ${sdkBlock}
850
927
  `);
@@ -854,7 +931,7 @@ ${sdkBlock}
854
931
  function createSubPackageClaudeMd(subClaudeMdPath, gitRoot) {
855
932
  const startMarker = "<!-- CONNECTBASE_SUB_START -->";
856
933
  const endMarker = "<!-- CONNECTBASE_SUB_END -->";
857
- const relPath = path.relative(path.dirname(subClaudeMdPath), gitRoot);
934
+ const relPath = path2.relative(path2.dirname(subClaudeMdPath), gitRoot);
858
935
  const subBlock = `${startMarker}
859
936
  ## ConnectBase SDK
860
937
 
@@ -866,8 +943,8 @@ function createSubPackageClaudeMd(subClaudeMdPath, gitRoot) {
866
943
  2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C SDK \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
867
944
  3. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC758 \`${relPath}/CLAUDE.md\`\uB3C4 \uCC38\uACE0\uD558\uC138\uC694
868
945
  ${endMarker}`;
869
- if (fs.existsSync(subClaudeMdPath)) {
870
- let content = fs.readFileSync(subClaudeMdPath, "utf-8");
946
+ if (fs2.existsSync(subClaudeMdPath)) {
947
+ let content = fs2.readFileSync(subClaudeMdPath, "utf-8");
871
948
  const startIdx = content.indexOf(startMarker);
872
949
  const endIdx = content.indexOf(endMarker);
873
950
  if (startIdx !== -1 && endIdx !== -1) {
@@ -877,9 +954,9 @@ ${endMarker}`;
877
954
  } else {
878
955
  return;
879
956
  }
880
- fs.writeFileSync(subClaudeMdPath, content);
957
+ fs2.writeFileSync(subClaudeMdPath, content);
881
958
  } else {
882
- fs.writeFileSync(subClaudeMdPath, `# ${path.basename(path.dirname(subClaudeMdPath))}
959
+ fs2.writeFileSync(subClaudeMdPath, `# ${path2.basename(path2.dirname(subClaudeMdPath))}
883
960
 
884
961
  ${subBlock}
885
962
  `);
@@ -896,7 +973,7 @@ async function setupMcp(secretKey) {
896
973
  info(`MCP \uC124\uC815\uC740 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8(${root})\uC5D0 \uC0DD\uC131\uB429\uB2C8\uB2E4`);
897
974
  }
898
975
  }
899
- const mcpConfigPath = path.join(root, ".mcp.json");
976
+ const mcpConfigPath = path2.join(root, ".mcp.json");
900
977
  if (!secretKey) {
901
978
  log(`
902
979
  ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
@@ -907,16 +984,16 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
907
984
  if (authChoice === "2") {
908
985
  try {
909
986
  secretKey = await browserAuthFlow();
910
- const skRcPath = path.join(process.cwd(), ".connectbaserc");
987
+ const skRcPath = path2.join(process.cwd(), ".connectbaserc");
911
988
  let rcData = {};
912
- if (fs.existsSync(skRcPath)) {
989
+ if (fs2.existsSync(skRcPath)) {
913
990
  try {
914
- rcData = JSON.parse(fs.readFileSync(skRcPath, "utf-8"));
991
+ rcData = JSON.parse(fs2.readFileSync(skRcPath, "utf-8"));
915
992
  } catch {
916
993
  }
917
994
  }
918
995
  rcData.secretKey = secretKey;
919
- fs.writeFileSync(skRcPath, JSON.stringify(rcData, null, 2) + "\n");
996
+ fs2.writeFileSync(skRcPath, JSON.stringify(rcData, null, 2) + "\n");
920
997
  addToGitignore(".connectbaserc", root);
921
998
  success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
922
999
  } catch (err) {
@@ -935,21 +1012,21 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
935
1012
  }
936
1013
  };
937
1014
  let mcpConfig = { mcpServers: {} };
938
- if (fs.existsSync(mcpConfigPath)) {
1015
+ if (fs2.existsSync(mcpConfigPath)) {
939
1016
  try {
940
- mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, "utf-8"));
1017
+ mcpConfig = JSON.parse(fs2.readFileSync(mcpConfigPath, "utf-8"));
941
1018
  if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== "object") {
942
1019
  mcpConfig.mcpServers = {};
943
1020
  }
944
1021
  } catch {
945
1022
  const backupPath = mcpConfigPath + ".backup";
946
- fs.copyFileSync(mcpConfigPath, backupPath);
1023
+ fs2.copyFileSync(mcpConfigPath, backupPath);
947
1024
  warn(`.mcp.json \uD30C\uC2F1 \uC2E4\uD328, \uBC31\uC5C5 \uC0DD\uC131: ${backupPath}`);
948
1025
  mcpConfig = { mcpServers: {} };
949
1026
  }
950
1027
  }
951
1028
  mcpConfig.mcpServers["connect-base"] = mcpEntry;
952
- fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
1029
+ fs2.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
953
1030
  success(`${mcpConfigPath} \uC0DD\uC131 \uC644\uB8CC`);
954
1031
  addToGitignore(".mcp.json", root);
955
1032
  if (secretKey && secretKey !== "YOUR_SECRET_KEY_HERE") {
@@ -1125,7 +1202,7 @@ ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
1125
1202
  success(`\uC120\uD0DD\uB428: ${apps[num - 1].name}`);
1126
1203
  return apps[num - 1].id;
1127
1204
  }
1128
- const projectName = path.basename(process.cwd());
1205
+ const projectName = path2.basename(process.cwd());
1129
1206
  const appName = await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
1130
1207
  info("\uC571 \uC0DD\uC131 \uC911...");
1131
1208
  const createRes = await makeRequest(
@@ -1147,67 +1224,15 @@ ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
1147
1224
  success(`\uC571 \uC0DD\uC131 \uC644\uB8CC: ${createData.app_name}`);
1148
1225
  return createData.app_id;
1149
1226
  }
1150
- function getTunnelLockDir() {
1151
- const platform2 = os.platform();
1152
- if (platform2 === "darwin") {
1153
- return path.join(os.homedir(), "Library", "Application Support", "connectbase", "tunnel-locks");
1154
- } else if (platform2 === "win32") {
1155
- return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "connectbase", "tunnel-locks");
1156
- }
1157
- const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
1158
- return path.join(stateHome, "connectbase", "tunnel-locks");
1159
- }
1160
- function isProcessAlive(pid) {
1161
- try {
1162
- process.kill(pid, 0);
1163
- return true;
1164
- } catch (e) {
1165
- return e.code === "EPERM";
1166
- }
1167
- }
1168
- function acquireTunnelLock(appID, port, force) {
1169
- const lockDir = getTunnelLockDir();
1170
- fs.mkdirSync(lockDir, { recursive: true });
1171
- const lockPath = path.join(lockDir, `${appID}-${port}.lock`);
1172
- const lockData = {
1173
- pid: process.pid,
1174
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1175
- appID,
1176
- port,
1177
- host: os.hostname(),
1178
- version: VERSION
1179
- };
1180
- if (!force) {
1181
- try {
1182
- const fd = fs.openSync(lockPath, "wx");
1183
- fs.writeSync(fd, JSON.stringify(lockData, null, 2));
1184
- fs.closeSync(fd);
1185
- return lockPath;
1186
- } catch (e) {
1187
- if (e.code !== "EEXIST") {
1188
- warn(`lockfile \uC0DD\uC131 \uC2E4\uD328: ${e.message}`);
1189
- return null;
1190
- }
1191
- }
1192
- try {
1193
- const existing = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
1194
- if (isProcessAlive(existing.pid)) {
1195
- error(`\uC774\uBBF8 \uC2E4\uD589 \uC911\uC778 \uD130\uB110\uC774 \uC788\uC2B5\uB2C8\uB2E4: PID ${existing.pid}, \uC2DC\uC791 ${existing.startedAt}, \uD638\uC2A4\uD2B8 ${existing.host}`);
1196
- info("\uC911\uBCF5 \uC2E4\uD589\uC744 \uBB34\uC2DC\uD558\uB824\uBA74 --force \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694");
1197
- process.exit(2);
1198
- }
1199
- warn(`stale lockfile \uBC1C\uACAC (PID ${existing.pid} \uC885\uB8CC\uB428), \uB36E\uC5B4\uC501\uB2C8\uB2E4`);
1200
- } catch {
1201
- }
1202
- }
1203
- fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
1204
- return lockPath;
1205
- }
1206
- function releaseTunnelLock(lockPath) {
1207
- try {
1208
- fs.unlinkSync(lockPath);
1209
- } catch {
1227
+ function acquireTunnelLock2(appID, port, force) {
1228
+ const result = acquireTunnelLock(appID, port, force, VERSION);
1229
+ if (result && result.startsWith("LOCKED:")) {
1230
+ const [, pid, startedAt, host] = result.split(":");
1231
+ error(`\uC774\uBBF8 \uC2E4\uD589 \uC911\uC778 \uD130\uB110\uC774 \uC788\uC2B5\uB2C8\uB2E4: PID ${pid}, \uC2DC\uC791 ${startedAt}, \uD638\uC2A4\uD2B8 ${host}`);
1232
+ info("\uC911\uBCF5 \uC2E4\uD589\uC744 \uBB34\uC2DC\uD558\uB824\uBA74 --force \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694");
1233
+ process.exit(2);
1210
1234
  }
1235
+ return result;
1211
1236
  }
1212
1237
  async function startTunnel(port, config, tunnelOpts) {
1213
1238
  let tunnelKey = config.secretKey || (config.publicKey?.startsWith("cb_sk_") ? config.publicKey : "");
@@ -1215,16 +1240,16 @@ async function startTunnel(port, config, tunnelOpts) {
1215
1240
  info("Secret Key\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uBC1C\uAE09\uD569\uB2C8\uB2E4...");
1216
1241
  try {
1217
1242
  const sk = await browserAuthFlow();
1218
- const rcPath2 = path.join(process.cwd(), ".connectbaserc");
1243
+ const rcPath2 = path2.join(process.cwd(), ".connectbaserc");
1219
1244
  let rcData = {};
1220
- if (fs.existsSync(rcPath2)) {
1245
+ if (fs2.existsSync(rcPath2)) {
1221
1246
  try {
1222
- rcData = JSON.parse(fs.readFileSync(rcPath2, "utf-8"));
1247
+ rcData = JSON.parse(fs2.readFileSync(rcPath2, "utf-8"));
1223
1248
  } catch {
1224
1249
  }
1225
1250
  }
1226
1251
  rcData.secretKey = sk;
1227
- fs.writeFileSync(rcPath2, JSON.stringify(rcData, null, 2) + "\n");
1252
+ fs2.writeFileSync(rcPath2, JSON.stringify(rcData, null, 2) + "\n");
1228
1253
  addToGitignore(".connectbaserc");
1229
1254
  success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
1230
1255
  tunnelKey = sk;
@@ -1237,11 +1262,11 @@ async function startTunnel(port, config, tunnelOpts) {
1237
1262
  } else {
1238
1263
  config.secretKey = tunnelKey;
1239
1264
  }
1240
- const rcPath = path.join(process.cwd(), ".connectbaserc");
1265
+ const rcPath = path2.join(process.cwd(), ".connectbaserc");
1241
1266
  let savedAppId = tunnelOpts?.appId || "";
1242
1267
  if (!savedAppId) {
1243
1268
  try {
1244
- const rc = JSON.parse(fs.readFileSync(rcPath, "utf-8"));
1269
+ const rc = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
1245
1270
  if (rc.tunnelAppId) savedAppId = rc.tunnelAppId;
1246
1271
  } catch {
1247
1272
  }
@@ -1249,19 +1274,19 @@ async function startTunnel(port, config, tunnelOpts) {
1249
1274
  const appId = await resolveAppForTunnel(tunnelKey, config.baseUrl, savedAppId);
1250
1275
  try {
1251
1276
  let rcData = {};
1252
- if (fs.existsSync(rcPath)) {
1277
+ if (fs2.existsSync(rcPath)) {
1253
1278
  try {
1254
- rcData = JSON.parse(fs.readFileSync(rcPath, "utf-8"));
1279
+ rcData = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
1255
1280
  } catch {
1256
1281
  }
1257
1282
  }
1258
1283
  if (rcData.tunnelAppId !== appId) {
1259
1284
  rcData.tunnelAppId = appId;
1260
- fs.writeFileSync(rcPath, JSON.stringify(rcData, null, 2) + "\n");
1285
+ fs2.writeFileSync(rcPath, JSON.stringify(rcData, null, 2) + "\n");
1261
1286
  }
1262
1287
  } catch {
1263
1288
  }
1264
- const lockPath = acquireTunnelLock(appId, port, tunnelOpts?.force ?? false);
1289
+ const lockPath = acquireTunnelLock2(appId, port, tunnelOpts?.force ?? false);
1265
1290
  const tunnelServerUrl = getTunnelServerUrl(config.baseUrl);
1266
1291
  const parsedUrl = new URL(tunnelServerUrl);
1267
1292
  const isHttps = parsedUrl.protocol === "https:";
@@ -1416,15 +1441,15 @@ ${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
1416
1441
  case "http_request":
1417
1442
  forwardRequest(msg, sock, localPort);
1418
1443
  break;
1419
- case "tunnel_error":
1420
- if (msg.code === "replaced") {
1421
- error(`\u26A0 \uB2E4\uB978 \uC138\uC158\uC774 \uAC19\uC740 \uC571+\uD3EC\uD2B8(${appId}:${localPort})\uB85C \uC5F0\uACB0\uB418\uC5B4 \uC774 \uC138\uC158\uC774 \uC885\uB8CC\uB429\uB2C8\uB2E4. \uB3D9\uC77C \uBA38\uC2E0\uC5D0\uC11C \uB450 \uBC88 \uC2E4\uD589\uD558\uC9C0 \uB9C8\uC138\uC694.`);
1444
+ case "tunnel_error": {
1445
+ const result = handleTunnelError(msg, appId, localPort);
1446
+ error(result.message);
1447
+ if (result.action === "exit") {
1422
1448
  shouldReconnect = false;
1423
- setTimeout(() => process.exit(1), 500);
1424
- } else {
1425
- error(`\uD130\uB110 \uC5D0\uB7EC: ${msg.error || msg.message || "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uB7EC"}`);
1449
+ setTimeout(() => process.exit(result.exitCode ?? 1), 500);
1426
1450
  }
1427
1451
  break;
1452
+ }
1428
1453
  case "ping":
1429
1454
  sock.write(createWsTextFrame(JSON.stringify({ type: "pong" })));
1430
1455
  break;