connectbase-client 3.24.0 → 3.25.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
@@ -6,10 +6,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __export = (target, all) => {
10
- for (var name in all)
11
- __defProp(target, name, { get: all[name], enumerable: true });
12
- };
13
9
  var __copyProps = (to, from, except, desc) => {
14
10
  if (from && typeof from === "object" || typeof from === "function") {
15
11
  for (let key of __getOwnPropNames(from))
@@ -26,114 +22,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
22
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
23
  mod
28
24
  ));
29
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
25
 
31
26
  // src/cli.ts
32
- var cli_exports = {};
33
- __export(cli_exports, {
34
- computeDeployDiff: () => computeDeployDiff,
35
- normalizeRelativePath: () => normalizeRelativePath,
36
- parseArgs: () => parseArgs,
37
- registerEndpointBinding: () => registerEndpointBinding,
38
- sha256Hex: () => sha256Hex
39
- });
40
- module.exports = __toCommonJS(cli_exports);
41
- var fs2 = __toESM(require("fs"));
42
- var path2 = __toESM(require("path"));
27
+ var fs = __toESM(require("fs"));
28
+ var path = __toESM(require("path"));
43
29
  var crypto = __toESM(require("crypto"));
44
30
  var https = __toESM(require("https"));
45
31
  var http = __toESM(require("http"));
46
32
  var readline = __toESM(require("readline"));
47
-
48
- // src/tunnel-utils.ts
49
- var fs = __toESM(require("fs"));
50
- var path = __toESM(require("path"));
51
- var os = __toESM(require("os"));
52
- function getTunnelLockDir() {
53
- const platform2 = os.platform();
54
- if (platform2 === "darwin") {
55
- return path.join(os.homedir(), "Library", "Application Support", "connectbase", "tunnel-locks");
56
- } else if (platform2 === "win32") {
57
- return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "connectbase", "tunnel-locks");
58
- }
59
- const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
60
- return path.join(stateHome, "connectbase", "tunnel-locks");
61
- }
62
- function isProcessAlive(pid) {
63
- try {
64
- process.kill(pid, 0);
65
- return true;
66
- } catch (e) {
67
- return e.code === "EPERM";
68
- }
69
- }
70
- function acquireTunnelLock(appID, port, force, version) {
71
- const lockDir = getTunnelLockDir();
72
- fs.mkdirSync(lockDir, { recursive: true });
73
- const lockPath = path.join(lockDir, `${appID}-${port}.lock`);
74
- const lockData = {
75
- pid: process.pid,
76
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
77
- appID,
78
- port,
79
- host: os.hostname(),
80
- version
81
- };
82
- if (!force) {
83
- try {
84
- const fd = fs.openSync(lockPath, "wx");
85
- fs.writeSync(fd, JSON.stringify(lockData, null, 2));
86
- fs.closeSync(fd);
87
- return lockPath;
88
- } catch (e) {
89
- if (e.code !== "EEXIST") {
90
- return null;
91
- }
92
- }
93
- try {
94
- const existing = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
95
- if (isProcessAlive(existing.pid)) {
96
- return `LOCKED:${existing.pid}:${existing.startedAt}:${existing.host}`;
97
- }
98
- } catch {
99
- }
100
- }
101
- fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
102
- return lockPath;
103
- }
104
- function releaseTunnelLock(lockPath) {
105
- try {
106
- fs.unlinkSync(lockPath);
107
- } catch {
108
- }
109
- }
110
- function handleTunnelError(msg, appId, localPort) {
111
- if (msg.code === "replaced") {
112
- return {
113
- action: "exit",
114
- 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.`,
115
- exitCode: 1
116
- };
117
- }
118
- return {
119
- action: "warn",
120
- message: `\uD130\uB110 \uC5D0\uB7EC: ${msg.error || msg.message || "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uB7EC"}`
121
- };
122
- }
123
-
124
- // src/cli.ts
125
- function getPackageVersion() {
126
- try {
127
- const pkgPath = path2.join(__dirname, "..", "package.json");
128
- if (fs2.existsSync(pkgPath)) {
129
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
130
- return pkg.version || "0.0.0";
131
- }
132
- } catch {
133
- }
134
- return "0.15.1";
135
- }
136
- var VERSION = getPackageVersion();
33
+ var VERSION = "3.25.0";
137
34
  var DEFAULT_BASE_URL = "https://api.connectbase.world";
138
35
  var colors = {
139
36
  reset: "\x1B[0m",
@@ -214,27 +111,17 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
214
111
  ".zip",
215
112
  ".wasm"
216
113
  ]);
217
- function sha256Hex(value) {
218
- return crypto.createHash("sha256").update(value, "utf8").digest("hex");
219
- }
220
- function normalizeRelativePath(relativePath) {
221
- let p = relativePath.replace(/\\/g, "/");
222
- if (!p.startsWith("/")) p = `/${p}`;
223
- return p;
224
- }
225
114
  function loadConfig() {
226
115
  const config = {
227
- publicKey: process.env.CONNECTBASE_PUBLIC_KEY,
228
- secretKey: process.env.CONNECTBASE_SECRET_KEY,
116
+ apiKey: process.env.CONNECTBASE_API_KEY,
229
117
  storageId: process.env.CONNECTBASE_STORAGE_ID,
230
118
  baseUrl: process.env.CONNECTBASE_BASE_URL || DEFAULT_BASE_URL
231
119
  };
232
- const rcPath = path2.join(process.cwd(), ".connectbaserc");
233
- if (fs2.existsSync(rcPath)) {
120
+ const rcPath = path.join(process.cwd(), ".connectbaserc");
121
+ if (fs.existsSync(rcPath)) {
234
122
  try {
235
- const rcContent = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
236
- if (rcContent.publicKey) config.publicKey = rcContent.publicKey;
237
- if (rcContent.secretKey) config.secretKey = rcContent.secretKey;
123
+ const rcContent = JSON.parse(fs.readFileSync(rcPath, "utf-8"));
124
+ if (rcContent.apiKey) config.apiKey = rcContent.apiKey;
238
125
  if (rcContent.storageId) config.storageId = rcContent.storageId;
239
126
  if (rcContent.baseUrl) config.baseUrl = rcContent.baseUrl;
240
127
  if (rcContent.deployDir) config.deployDir = rcContent.deployDir;
@@ -246,16 +133,16 @@ function loadConfig() {
246
133
  }
247
134
  function collectFiles(dir, baseDir = dir) {
248
135
  const files = [];
249
- const entries = fs2.readdirSync(dir, { withFileTypes: true });
136
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
250
137
  for (const entry of entries) {
251
- const fullPath = path2.join(dir, entry.name);
252
- const relativePath = path2.relative(baseDir, fullPath);
138
+ const fullPath = path.join(dir, entry.name);
139
+ const relativePath = path.relative(baseDir, fullPath);
253
140
  if (entry.isDirectory()) {
254
141
  if (!entry.name.startsWith(".")) {
255
142
  files.push(...collectFiles(fullPath, baseDir));
256
143
  }
257
144
  } else if (entry.isFile()) {
258
- const ext = path2.extname(entry.name).toLowerCase();
145
+ const ext = path.extname(entry.name).toLowerCase();
259
146
  if (!ALLOWED_EXTENSIONS.has(ext)) {
260
147
  continue;
261
148
  }
@@ -265,22 +152,22 @@ function collectFiles(dir, baseDir = dir) {
265
152
  const isBinary = BINARY_EXTENSIONS.has(ext);
266
153
  let content;
267
154
  if (isBinary) {
268
- content = fs2.readFileSync(fullPath).toString("base64");
155
+ content = fs.readFileSync(fullPath).toString("base64");
269
156
  } else {
270
- content = fs2.readFileSync(fullPath, "utf-8");
157
+ content = fs.readFileSync(fullPath, "utf-8");
271
158
  }
272
- const normalized = normalizeRelativePath(relativePath);
273
159
  files.push({
274
- path: normalized,
160
+ path: relativePath.replace(/\\/g, "/"),
161
+ // Windows 경로 변환
275
162
  content,
276
- isBinary,
277
- hash: sha256Hex(content)
163
+ isBinary
278
164
  });
279
165
  }
280
166
  }
281
167
  return files;
282
168
  }
283
- async function makeRequest(url, method, headers, body) {
169
+ var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
170
+ async function makeRequest(url, method, headers, body, reqOpts = {}) {
284
171
  return new Promise((resolve2, reject) => {
285
172
  const parsedUrl = new URL(url);
286
173
  const isHttps = parsedUrl.protocol === "https:";
@@ -313,8 +200,16 @@ async function makeRequest(url, method, headers, body) {
313
200
  }
314
201
  });
315
202
  });
316
- req.setTimeout(6e4, () => {
317
- req.destroy(new Error("\uC694\uCCAD \uC2DC\uAC04 \uCD08\uACFC (60\uCD08)"));
203
+ req.on("socket", (socket) => {
204
+ socket.setKeepAlive(true, 3e4);
205
+ });
206
+ const timeoutMs = reqOpts.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
207
+ req.setTimeout(timeoutMs, () => {
208
+ req.destroy(
209
+ new Error(
210
+ `\uC694\uCCAD \uC2DC\uAC04 \uCD08\uACFC (${Math.round(timeoutMs / 1e3)}\uCD08). \uD070 \uC0AC\uC774\uD2B8\uB294 --timeout <\uCD08> \uB610\uB294 CONNECTBASE_DEPLOY_TIMEOUT \uD658\uACBD\uBCC0\uC218\uB85C \uB298\uB9AC\uC138\uC694.`
211
+ )
212
+ );
318
213
  });
319
214
  req.on("error", reject);
320
215
  if (bodyBuffer) {
@@ -323,29 +218,39 @@ async function makeRequest(url, method, headers, body) {
323
218
  req.end();
324
219
  });
325
220
  }
326
- async function deploy(directory, config, isDev = false) {
327
- const dir = path2.resolve(directory);
328
- if (!fs2.existsSync(dir)) {
221
+ var DEPLOY_TIMEOUT_BASE_MS = 5 * 6e4;
222
+ var DEPLOY_TIMEOUT_PER_10MB_MS = 6e4;
223
+ var DEPLOY_TIMEOUT_MIN_MS = 10 * 6e4;
224
+ function computeDeployTimeout(totalBytes, override) {
225
+ if (override && override > 0) return override;
226
+ const envOverride = process.env.CONNECTBASE_DEPLOY_TIMEOUT ? parseInt(process.env.CONNECTBASE_DEPLOY_TIMEOUT, 10) * 1e3 : 0;
227
+ if (envOverride > 0) return envOverride;
228
+ const sizeBased = Math.ceil(totalBytes / (10 * 1024 * 1024)) * DEPLOY_TIMEOUT_PER_10MB_MS;
229
+ return Math.max(DEPLOY_TIMEOUT_MIN_MS, DEPLOY_TIMEOUT_BASE_MS + sizeBased);
230
+ }
231
+ async function deploy(directory, config, isDev = false, deployOpts = {}) {
232
+ const dir = path.resolve(directory);
233
+ if (!fs.existsSync(dir)) {
329
234
  error(`\uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${dir}`);
330
235
  process.exit(1);
331
236
  }
332
- const pkgPath = path2.join(process.cwd(), "package.json");
333
- if (fs2.existsSync(pkgPath)) {
237
+ const pkgPath = path.join(process.cwd(), "package.json");
238
+ if (fs.existsSync(pkgPath)) {
334
239
  try {
335
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
240
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
336
241
  if (pkg.scripts?.build) {
337
- warn(`\uBC30\uD3EC \uC804 \uBE4C\uB4DC\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694: ${colors.cyan}npm run build && npx connectbase deploy${colors.reset}`);
338
- warn(`\uB610\uB294 package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uB4F1\uB85D\uD558\uC138\uC694: ${colors.cyan}npx connectbase init${colors.reset}`);
242
+ 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}`);
243
+ 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}`);
339
244
  }
340
245
  } catch {
341
246
  }
342
247
  }
343
- if (!fs2.statSync(dir).isDirectory()) {
248
+ if (!fs.statSync(dir).isDirectory()) {
344
249
  error(`${dir}\uB294 \uB514\uB809\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4`);
345
250
  process.exit(1);
346
251
  }
347
- const indexPath = path2.join(dir, "index.html");
348
- if (!fs2.existsSync(indexPath)) {
252
+ const indexPath = path.join(dir, "index.html");
253
+ if (!fs.existsSync(indexPath)) {
349
254
  error("index.html \uD30C\uC77C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
350
255
  process.exit(1);
351
256
  }
@@ -360,133 +265,60 @@ async function deploy(directory, config, isDev = false) {
360
265
  const totalSize = files.reduce((sum, f) => sum + f.content.length, 0);
361
266
  const sizeKB = (totalSize / 1024).toFixed(1);
362
267
  log(`${colors.green}\u2713${colors.reset} ${files.length}\uAC1C \uD30C\uC77C \uBC1C\uACAC (${sizeKB} KB)`);
268
+ const deployPath = isDev ? "deploy/dev" : "deploy";
269
+ const url = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}/${deployPath}`;
363
270
  const envLabel = isDev ? "Dev" : "Prod";
364
- const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
365
- const headers = { "X-Public-Key": config.publicKey };
366
- if (isDev) {
367
- await fullDeploy(baseStorageUrl, headers, files, envLabel, "deploy/dev");
368
- return;
271
+ const timeoutMs = computeDeployTimeout(totalSize, deployOpts.timeoutMs);
272
+ log(`${colors.dim}\uD0C0\uC784\uC544\uC6C3: ${Math.round(timeoutMs / 1e3)}\uCD08${colors.reset}`);
273
+ process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uBC30\uD3EC \uC911...`);
274
+ let heartbeat = null;
275
+ if (process.stdout.isTTY) {
276
+ const startedAt = Date.now();
277
+ heartbeat = setInterval(() => {
278
+ const elapsed = Math.floor((Date.now() - startedAt) / 1e3);
279
+ process.stdout.write(`\r${colors.blue}\u27F3${colors.reset} ${envLabel} \uBC30\uD3EC \uC911... ${colors.dim}(${elapsed}s)${colors.reset}`);
280
+ }, 1e3);
369
281
  }
370
282
  try {
371
- const manifest = await tryFetchManifest(baseStorageUrl, headers);
372
- if (!manifest) {
373
- await fullDeploy(baseStorageUrl, headers, files, envLabel, "deploy");
374
- return;
375
- }
376
- const diff = computeDeployDiff(files, manifest);
377
- if (diff.upsert.length === 0 && diff.delete.length === 0) {
378
- success(`\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C (${files.length}\uAC1C \uD30C\uC77C \uC77C\uCE58)`);
379
- log(`
380
- ${colors.cyan}URL: https://${config.storageId}.web.connectbase.world${colors.reset}
381
- `);
382
- return;
383
- }
384
- info(`\uBCC0\uACBD: ${colors.green}+${diff.upsert.length}${colors.reset} / ${colors.red}-${diff.delete.length}${colors.reset} (\uC804\uCCB4 ${files.length}\uAC1C \uC911)`);
385
- const uploadSize = diff.upsert.reduce((s, f) => s + f.content.length, 0);
386
- info(`\uC5C5\uB85C\uB4DC \uD06C\uAE30: ${(uploadSize / 1024).toFixed(1)} KB`);
387
- await incrementalDeploy(baseStorageUrl, headers, diff, manifest.revision, envLabel);
388
- } catch (err) {
389
- process.stdout.write("\r \r");
390
- error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
391
- process.exit(1);
392
- }
393
- }
394
- async function tryFetchManifest(baseStorageUrl, headers) {
395
- const res = await makeRequest(`${baseStorageUrl}/deploy/manifest`, "GET", headers);
396
- if (res.status === 404) return null;
397
- if (res.status < 200 || res.status >= 300) {
398
- const data = res.data;
399
- const msg = typeof data === "object" && data !== null ? data.message || data.error || JSON.stringify(data) : `HTTP ${res.status}`;
400
- throw new Error(`manifest \uC870\uD68C \uC2E4\uD328: ${msg}`);
401
- }
402
- const parsed = res.data;
403
- if (!parsed || !Array.isArray(parsed.files)) {
404
- return null;
405
- }
406
- return parsed;
407
- }
408
- function computeDeployDiff(local, manifest) {
409
- const localByPath = /* @__PURE__ */ new Map();
410
- for (const f of local) localByPath.set(f.path, f);
411
- const remoteByPath = /* @__PURE__ */ new Map();
412
- for (const m of manifest.files) remoteByPath.set(m.path, m);
413
- const upsert = [];
414
- for (const f of local) {
415
- const remote = remoteByPath.get(f.path);
416
- if (!remote || remote.hash !== f.hash || !remote.hash) {
417
- upsert.push({
418
- path: f.path,
419
- content: f.content,
420
- is_binary: f.isBinary,
421
- hash: f.hash
422
- });
423
- }
424
- }
425
- const toDelete = [];
426
- for (const m of manifest.files) {
427
- if (!localByPath.has(m.path)) toDelete.push(m.path);
428
- }
429
- return { upsert, delete: toDelete };
430
- }
431
- async function incrementalDeploy(baseStorageUrl, headers, diff, baseRevision, envLabel) {
432
- process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC911...`);
433
- const body = JSON.stringify({
434
- upsert: diff.upsert,
435
- delete: diff.delete,
436
- base_revision: baseRevision
437
- });
438
- const res = await makeRequest(`${baseStorageUrl}/deploy/incremental`, "POST", headers, body);
439
- process.stdout.write("\r \r");
440
- if (res.status === 409) {
441
- warn("\uC11C\uBC84 revision \uC774 \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. manifest \uC7AC\uC870\uD68C \uD6C4 \uC7AC\uC2DC\uB3C4\uD569\uB2C8\uB2E4.");
442
- const manifest = await tryFetchManifest(baseStorageUrl, headers);
443
- if (!manifest) {
444
- error("\uC7AC\uC2DC\uB3C4 manifest \uC870\uD68C \uC2E4\uD328");
445
- process.exit(1);
446
- }
447
- const body2 = JSON.stringify({
448
- upsert: diff.upsert,
449
- delete: diff.delete,
450
- base_revision: manifest.revision
451
- });
452
- process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC7AC\uC2DC\uB3C4...`);
453
- const res2 = await makeRequest(`${baseStorageUrl}/deploy/incremental`, "POST", headers, body2);
454
- process.stdout.write("\r \r");
455
- handleDeployResponse(res2, envLabel);
456
- return;
457
- }
458
- handleDeployResponse(res, envLabel);
459
- }
460
- async function fullDeploy(baseStorageUrl, headers, files, envLabel, endpoint) {
461
- process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uBC30\uD3EC \uC911...`);
462
- const body = JSON.stringify({
463
- files: files.map((f) => ({
464
- path: f.path,
465
- content: f.content,
466
- is_binary: f.isBinary
467
- }))
468
- });
469
- const res = await makeRequest(`${baseStorageUrl}/${endpoint}`, "POST", headers, body);
470
- process.stdout.write("\r \r");
471
- handleDeployResponse(res, envLabel);
472
- }
473
- function handleDeployResponse(response, envLabel) {
474
- if (response.status >= 200 && response.status < 300) {
475
- success(`${envLabel} \uBC30\uD3EC \uC644\uB8CC!`);
476
- const data = response.data;
477
- if (data?.dev_url) {
478
- log(`
283
+ const response = await makeRequest(
284
+ url,
285
+ "POST",
286
+ {
287
+ "X-API-Key": config.apiKey
288
+ },
289
+ JSON.stringify({
290
+ files: files.map((f) => ({
291
+ path: f.path,
292
+ content: f.content,
293
+ is_binary: f.isBinary
294
+ }))
295
+ }),
296
+ { timeoutMs }
297
+ );
298
+ if (heartbeat) clearInterval(heartbeat);
299
+ process.stdout.write("\r \r");
300
+ if (response.status >= 200 && response.status < 300) {
301
+ success(`${envLabel} \uBC30\uD3EC \uC644\uB8CC!`);
302
+ const data = response.data;
303
+ if (data?.dev_url) {
304
+ log(`
479
305
  ${colors.yellow}Dev URL: ${data.dev_url}${colors.reset}
480
306
  `);
481
- } else if (data?.url) {
482
- log(`
307
+ } else if (data?.url) {
308
+ log(`
483
309
  ${colors.cyan}URL: ${data.url}${colors.reset}
484
310
  `);
311
+ }
312
+ } else {
313
+ const data = response.data;
314
+ const errorMsg = typeof data === "object" && data !== null ? data.message || data.error || JSON.stringify(data) : typeof data === "string" ? data : `HTTP ${response.status}`;
315
+ error(`\uBC30\uD3EC \uC2E4\uD328: ${errorMsg}`);
316
+ process.exit(1);
485
317
  }
486
- } else {
487
- const data = response.data;
488
- const errorMsg = typeof data === "object" && data !== null ? data.message || data.error || JSON.stringify(data) : typeof data === "string" ? data : `HTTP ${response.status}`;
489
- error(`\uBC30\uD3EC \uC2E4\uD328: ${errorMsg}`);
318
+ } catch (err) {
319
+ if (heartbeat) clearInterval(heartbeat);
320
+ process.stdout.write("\r \r");
321
+ error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
490
322
  process.exit(1);
491
323
  }
492
324
  }
@@ -502,329 +334,77 @@ function prompt(question) {
502
334
  });
503
335
  });
504
336
  }
505
- function promptSecret(question) {
506
- return new Promise((resolve2) => {
507
- process.stdout.write(question);
508
- const input = [];
509
- if (!process.stdin.isTTY) {
510
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
511
- rl.question("", (answer) => {
512
- rl.close();
513
- resolve2(answer.trim());
514
- });
515
- return;
516
- }
517
- process.stdin.setRawMode(true);
518
- process.stdin.resume();
519
- process.stdin.setEncoding("utf-8");
520
- const onData = (data) => {
521
- for (const char of data) {
522
- const code = char.charCodeAt(0);
523
- if (char === "\r" || char === "\n") {
524
- process.stdin.setRawMode(false);
525
- process.stdin.pause();
526
- process.stdin.removeListener("data", onData);
527
- process.stdout.write("\n");
528
- resolve2(input.join("").trim());
529
- return;
530
- } else if (code === 3) {
531
- process.stdin.setRawMode(false);
532
- process.stdin.pause();
533
- process.stdout.write("\n");
534
- process.exit(0);
535
- } else if (code === 127 || code === 8) {
536
- if (input.length > 0) {
537
- input.pop();
538
- process.stdout.write("\b \b");
539
- }
540
- } else if (code >= 32) {
541
- input.push(char);
542
- process.stdout.write("*");
543
- }
544
- }
545
- };
546
- process.stdin.on("data", onData);
547
- });
548
- }
549
- function getProjectRoot() {
550
- const gitRoot = getGitRoot();
551
- return gitRoot || process.cwd();
552
- }
553
- var CONSOLE_URL = DEFAULT_BASE_URL.replace("api.", "");
554
- function openBrowser(url) {
555
- const { exec } = require("child_process");
556
- const platform2 = process.platform;
557
- let command;
558
- if (platform2 === "darwin") {
559
- command = `open "${url}"`;
560
- } else if (platform2 === "win32") {
561
- command = `start "" "${url}"`;
562
- } else {
563
- command = `xdg-open "${url}"`;
564
- }
565
- exec(command, () => {
566
- });
567
- }
568
- async function browserAuthFlow() {
569
- info("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D \uC138\uC158\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4...");
570
- const startRes = await makeRequest(
571
- `${DEFAULT_BASE_URL}/v1/public/cli-auth/start`,
572
- "POST",
573
- {}
574
- );
575
- if (startRes.status !== 200) {
576
- const errData = startRes.data;
577
- const detail = errData?.error || errData?.message || `HTTP ${startRes.status}`;
578
- error(`\uC778\uC99D \uC138\uC158 \uC2DC\uC791\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: ${detail}`);
579
- process.exit(1);
580
- }
581
- const { session_id } = startRes.data;
582
- const authUrl = `${CONSOLE_URL}/auth/cli-auth?session=${session_id}`;
583
- log(`
584
- ${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC138\uC694:${colors.reset}`);
585
- log(`${colors.dim}${authUrl}${colors.reset}
586
- `);
587
- openBrowser(authUrl);
588
- const pollInterval = 3e3;
589
- const maxAttempts = 100;
590
- let attempts = 0;
591
- let consecutive404 = 0;
592
- const max404Retries = 5;
593
- const spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
594
- while (attempts < maxAttempts) {
595
- const frame = spinnerFrames[attempts % spinnerFrames.length];
596
- process.stdout.write(`\r${colors.blue}${frame}${colors.reset} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC2B9\uC778 \uB300\uAE30 \uC911...`);
597
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
598
- try {
599
- const pollRes = await makeRequest(
600
- `${DEFAULT_BASE_URL}/v1/public/cli-auth/poll/${session_id}`,
601
- "GET",
602
- {}
603
- );
604
- if (pollRes.status === 200) {
605
- consecutive404 = 0;
606
- const data = pollRes.data;
607
- if (data.status === "approved" && data.secret_key) {
608
- process.stdout.write("\r \r");
609
- success("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!");
610
- return data.secret_key;
611
- }
612
- if (data.status === "expired") {
613
- process.stdout.write("\r \r");
614
- error("\uC778\uC99D \uC138\uC158\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
615
- process.exit(1);
616
- }
617
- if (data.status === "denied") {
618
- process.stdout.write("\r \r");
619
- error("\uC778\uC99D\uC774 \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
620
- process.exit(1);
621
- }
622
- }
623
- if (pollRes.status === 404) {
624
- consecutive404++;
625
- if (consecutive404 >= max404Retries) {
626
- process.stdout.write("\r \r");
627
- error("\uC778\uC99D \uC138\uC158\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
628
- process.exit(1);
629
- }
630
- }
631
- } catch {
632
- }
633
- attempts++;
634
- }
635
- process.stdout.write("\r \r");
636
- error("\uC778\uC99D \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
637
- process.exit(1);
638
- }
639
337
  async function init() {
640
338
  log(`
641
339
  ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.reset}
642
340
  `);
643
- const cwd = process.cwd();
644
- const projectRoot = getProjectRoot();
645
- if (cwd !== projectRoot) {
646
- info(`\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uAC10\uC9C0: ${projectRoot}`);
647
- 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
648
- `);
649
- }
650
- const rcPath = path2.join(cwd, ".connectbaserc");
651
- if (fs2.existsSync(rcPath)) {
341
+ const rcPath = path.join(process.cwd(), ".connectbaserc");
342
+ if (fs.existsSync(rcPath)) {
652
343
  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): `);
653
344
  if (overwrite.toLowerCase() !== "y") {
654
345
  info("\uCD08\uAE30\uD654\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
655
346
  return;
656
347
  }
657
348
  }
658
- log(`
659
- ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
660
- log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
661
- log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
662
- `);
663
- const authChoice = await prompt(`${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `);
664
- let secretKey = "";
665
- if (authChoice === "2") {
666
- try {
667
- secretKey = await browserAuthFlow();
668
- } catch (err) {
669
- error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
670
- 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");
671
- process.exit(1);
672
- }
673
- } else {
674
- log(`${colors.dim}Secret Key (cb_sk_): \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uBC1C\uAE09${colors.reset}
349
+ log(`${colors.dim}Public Key (cb_pk_): SDK\uC6A9 \u2014 \uC6F9/\uC571\uC5D0\uC11C \uC0AC\uC6A9${colors.reset}`);
350
+ log(`${colors.dim}Secret Key (cb_sk_): MCP/\uAD00\uB9AC\uC790\uC6A9 \u2014 \uC808\uB300 \uB178\uCD9C \uAE08\uC9C0${colors.reset}
675
351
  `);
676
- secretKey = await promptSecret(`${colors.blue}?${colors.reset} Secret Key: `);
677
- if (!secretKey) {
678
- error("Secret Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
679
- process.exit(1);
680
- }
681
- if (secretKey.startsWith("cb_pk_")) {
682
- error("Public Key\uAC00 \uC544\uB2CC Secret Key(cb_sk_)\uB97C \uC785\uB825\uD558\uC138\uC694");
683
- info("Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
684
- process.exit(1);
685
- }
686
- if (!secretKey.startsWith("cb_sk_")) {
687
- 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");
688
- process.exit(1);
689
- }
352
+ const apiKey = await prompt(`${colors.blue}?${colors.reset} API Key (Public Key \uAD8C\uC7A5): `);
353
+ if (!apiKey) {
354
+ error("API Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
355
+ process.exit(1);
690
356
  }
691
- let appId = "";
692
- let publicKey = "";
357
+ if (apiKey.startsWith("cb_sk_")) {
358
+ warn("Secret Key\uAC00 \uC785\uB825\uB418\uC5C8\uC2B5\uB2C8\uB2E4. SDK\uC5D0\uB294 Public Key (cb_pk_) \uC0AC\uC6A9\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.");
359
+ warn("Secret Key\uB294 \uC804\uCCB4 \uAD8C\uD55C\uC744 \uAC00\uC9C0\uBBC0\uB85C \uCF54\uB4DC\uC5D0 \uB178\uCD9C\uB418\uC9C0 \uC54A\uB3C4\uB85D \uC8FC\uC758\uD558\uC138\uC694.");
360
+ }
361
+ let storageId = "";
693
362
  try {
694
- info("\uC571 \uBAA9\uB85D \uC870\uD68C \uC911...");
695
- const appsRes = await makeRequest(
696
- `${DEFAULT_BASE_URL}/v1/public/cli/apps`,
363
+ info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC870\uD68C \uC911...");
364
+ const listRes = await makeRequest(
365
+ `${DEFAULT_BASE_URL}/v1/public/storages/webs`,
697
366
  "GET",
698
- { "X-Public-Key": secretKey }
367
+ { "X-API-Key": apiKey }
699
368
  );
700
- if (appsRes.status === 401) {
701
- error("Secret Key\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uCF58\uC194\uC5D0\uC11C \uD0A4\uB97C \uD655\uC778\uD558\uC138\uC694");
369
+ if (listRes.status !== 200) {
370
+ error(`API Key \uC778\uC99D \uC2E4\uD328 (${listRes.status})`);
702
371
  process.exit(1);
703
372
  }
704
- if (appsRes.status !== 200) {
705
- throw new Error(`HTTP ${appsRes.status}`);
706
- }
707
- const appsData = appsRes.data;
708
- const apps = appsData.apps || [];
709
- if (apps.length > 0) {
373
+ const listData = listRes.data;
374
+ const storages = listData.storages || [];
375
+ if (storages.length > 0) {
710
376
  log(`
711
- ${colors.dim}\uB0B4 \uC571 \uBAA9\uB85D:${colors.reset}`);
712
- apps.forEach((a, i) => {
713
- const date = a.created_at ? a.created_at.substring(0, 10) : "";
714
- log(` ${colors.cyan}${i + 1}${colors.reset}) ${a.name} ${colors.dim}(${date})${colors.reset}`);
377
+ ${colors.dim}\uAE30\uC874 \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0:${colors.reset}`);
378
+ storages.forEach((s, i) => {
379
+ log(` ${colors.cyan}${i + 1}${colors.reset}) ${s.name} (${colors.dim}${s.id}${colors.reset})`);
715
380
  });
716
- log(` ${colors.cyan}0${colors.reset}) \uC0C8 \uC571 \uB9CC\uB4E4\uAE30`);
381
+ log(` ${colors.cyan}0${colors.reset}) \uC0C8\uB85C \uC0DD\uC131`);
717
382
  const choice = await prompt(`
718
383
  ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
719
384
  const num = parseInt(choice, 10);
720
- if (num > 0 && num <= apps.length) {
721
- appId = apps[num - 1].id;
722
- success(`\uC120\uD0DD\uB428: ${apps[num - 1].name}`);
723
- info("Public Key \uC0DD\uC131 \uC911...");
724
- const newKeyRes = await makeRequest(
725
- `${DEFAULT_BASE_URL}/v1/public/cli/apps/${appId}/public-keys`,
726
- "POST",
727
- { "X-Public-Key": secretKey },
728
- JSON.stringify({})
729
- );
730
- if (newKeyRes.status === 201) {
731
- const keyData = newKeyRes.data;
732
- publicKey = keyData.key;
733
- success("Public Key \uBC1C\uAE09 \uC644\uB8CC");
734
- } else {
735
- warn("Public Key \uC790\uB3D9 \uC0DD\uC131 \uC2E4\uD328. \uC218\uB3D9\uC73C\uB85C \uC0DD\uC131\uD558\uC138\uC694");
736
- }
385
+ if (num > 0 && num <= storages.length) {
386
+ storageId = storages[num - 1].id;
387
+ success(`\uC120\uD0DD\uB428: ${storages[num - 1].name}`);
737
388
  }
738
389
  }
739
- if (!appId) {
740
- const projectName = path2.basename(cwd);
741
- const appName = await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
742
- info("\uC571 \uC0DD\uC131 \uC911...");
390
+ if (!storageId) {
391
+ const projectName = path.basename(process.cwd());
392
+ const name = await prompt(`${colors.blue}?${colors.reset} \uC2A4\uD1A0\uB9AC\uC9C0 \uC774\uB984 (${projectName}): `) || projectName;
393
+ info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC911...");
743
394
  const createRes = await makeRequest(
744
- `${DEFAULT_BASE_URL}/v1/public/cli/apps`,
395
+ `${DEFAULT_BASE_URL}/v1/public/storages/webs`,
745
396
  "POST",
746
- { "X-Public-Key": secretKey },
747
- JSON.stringify({ name: appName })
397
+ { "X-API-Key": apiKey },
398
+ JSON.stringify({ name })
748
399
  );
749
- if (createRes.status === 402) {
750
- error("\uC571 \uC0DD\uC131 \uD55C\uB3C4 \uCD08\uACFC. \uD50C\uB79C \uC5C5\uADF8\uB808\uC774\uB4DC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4");
751
- process.exit(1);
752
- }
753
- if (createRes.status !== 201) {
400
+ if (createRes.status !== 200) {
754
401
  const data = createRes.data;
755
- error(`\uC571 \uC0DD\uC131 \uC2E4\uD328: ${data?.error || `HTTP ${createRes.status}`}`);
402
+ error(`\uC0DD\uC131 \uC2E4\uD328: ${data?.error || data?.message || `HTTP ${createRes.status}`}`);
756
403
  process.exit(1);
757
404
  }
758
405
  const createData = createRes.data;
759
- appId = createData.app_id;
760
- publicKey = createData.public_key;
761
- success(`\uC571 \uC0DD\uC131 \uC644\uB8CC: ${createData.app_name}`);
762
- if (publicKey) {
763
- success("Public Key \uC790\uB3D9 \uBC1C\uAE09 \uC644\uB8CC");
764
- }
765
- }
766
- } catch (err) {
767
- error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
768
- log(`${colors.dim}\uC218\uB3D9\uC73C\uB85C \uC785\uB825\uD558\uC138\uC694${colors.reset}`);
769
- appId = await prompt(`${colors.blue}?${colors.reset} App ID: `);
770
- publicKey = await prompt(`${colors.blue}?${colors.reset} Public Key (cb_pk_): `);
771
- if (!appId || !publicKey) {
772
- error("App ID\uC640 Public Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
773
- process.exit(1);
774
- }
775
- }
776
- if (!publicKey) {
777
- 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");
778
- process.exit(1);
779
- }
780
- const publicKeyForSdk = publicKey;
781
- let storageId = "";
782
- try {
783
- info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC870\uD68C \uC911...");
784
- const listRes = await makeRequest(
785
- `${DEFAULT_BASE_URL}/v1/public/storages/webs`,
786
- "GET",
787
- { "X-Public-Key": publicKeyForSdk }
788
- );
789
- if (listRes.status === 200) {
790
- const listData = listRes.data;
791
- const storages = listData.storages || [];
792
- if (storages.length > 0) {
793
- log(`
794
- ${colors.dim}\uAE30\uC874 \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0:${colors.reset}`);
795
- storages.forEach((s, i) => {
796
- log(` ${colors.cyan}${i + 1}${colors.reset}) ${s.name} (${colors.dim}${s.id}${colors.reset})`);
797
- });
798
- log(` ${colors.cyan}0${colors.reset}) \uC0C8\uB85C \uC0DD\uC131`);
799
- const choice = await prompt(`
800
- ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
801
- const num = parseInt(choice, 10);
802
- if (num > 0 && num <= storages.length) {
803
- storageId = storages[num - 1].id;
804
- success(`\uC120\uD0DD\uB428: ${storages[num - 1].name}`);
805
- }
806
- }
807
- if (!storageId) {
808
- const projectName = path2.basename(cwd);
809
- const name = await prompt(`${colors.blue}?${colors.reset} \uC2A4\uD1A0\uB9AC\uC9C0 \uC774\uB984 (${projectName}): `) || projectName;
810
- info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC911...");
811
- const createRes = await makeRequest(
812
- `${DEFAULT_BASE_URL}/v1/public/storages/webs`,
813
- "POST",
814
- { "X-Public-Key": publicKeyForSdk },
815
- JSON.stringify({ name })
816
- );
817
- if (createRes.status !== 200) {
818
- const data = createRes.data;
819
- error(`\uC0DD\uC131 \uC2E4\uD328: ${data?.error || data?.message || `HTTP ${createRes.status}`}`);
820
- process.exit(1);
821
- }
822
- const createData = createRes.data;
823
- storageId = createData.storage_web_id;
824
- success(`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC644\uB8CC: ${createData.name}`);
825
- }
826
- } else {
827
- throw new Error(`HTTP ${listRes.status}`);
406
+ storageId = createData.storage_web_id;
407
+ success(`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC644\uB8CC: ${createData.name}`);
828
408
  }
829
409
  } catch (err) {
830
410
  error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
@@ -838,43 +418,41 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
838
418
  const defaultDir = detectBuildDir();
839
419
  const deployDir = await prompt(`${colors.blue}?${colors.reset} \uBC30\uD3EC \uB514\uB809\uD1A0\uB9AC (${defaultDir}): `) || defaultDir;
840
420
  const config = {
841
- publicKey: publicKey || "",
421
+ apiKey,
842
422
  storageId,
843
423
  deployDir
844
424
  };
845
- if (secretKey) {
846
- config.secretKey = secretKey;
847
- }
848
- fs2.writeFileSync(rcPath, JSON.stringify(config, null, 2) + "\n");
425
+ fs.writeFileSync(rcPath, JSON.stringify(config, null, 2) + "\n");
849
426
  success(".connectbaserc \uC0DD\uC131 \uC644\uB8CC");
850
427
  addToGitignore(".connectbaserc");
851
428
  addDeployScript(deployDir);
852
- await setupClaudeCode(publicKeyForSdk, secretKey, projectRoot);
429
+ const setupClaude = await prompt(`
430
+ ${colors.blue}?${colors.reset} Claude Code \uC124\uC815\uC744 \uCD94\uAC00\uD560\uAE4C\uC694? (CLAUDE.md, MCP \uC124\uC815) (Y/n): `);
431
+ if (setupClaude.toLowerCase() !== "n") {
432
+ await setupClaudeCode(apiKey);
433
+ }
853
434
  log(`
854
435
  ${colors.green}\uCD08\uAE30\uD654 \uC644\uB8CC!${colors.reset}
855
436
  `);
856
437
  log(`${colors.dim}\uBC30\uD3EC\uD558\uB824\uBA74:${colors.reset}`);
857
- log(` ${colors.cyan}npm run deploy${colors.reset}`);
858
- log(`
859
- ${colors.dim}Claude Code\uC5D0\uC11C MCP\uAC00 \uC790\uB3D9 \uC5F0\uACB0\uB429\uB2C8\uB2E4${colors.reset}
438
+ log(` ${colors.cyan}npm run deploy${colors.reset}
860
439
  `);
861
440
  }
862
441
  function detectBuildDir() {
863
- if (fs2.existsSync(path2.join(process.cwd(), "dist"))) return "./dist";
864
- if (fs2.existsSync(path2.join(process.cwd(), "build"))) return "./build";
865
- if (fs2.existsSync(path2.join(process.cwd(), "out"))) return "./out";
866
- if (fs2.existsSync(path2.join(process.cwd(), ".next"))) return "./out";
867
- if (fs2.existsSync(path2.join(process.cwd(), "vite.config.ts")) || fs2.existsSync(path2.join(process.cwd(), "vite.config.js"))) return "./dist";
868
- if (fs2.existsSync(path2.join(process.cwd(), "next.config.js")) || fs2.existsSync(path2.join(process.cwd(), "next.config.mjs"))) return "./out";
442
+ if (fs.existsSync(path.join(process.cwd(), "dist"))) return "./dist";
443
+ if (fs.existsSync(path.join(process.cwd(), "build"))) return "./build";
444
+ if (fs.existsSync(path.join(process.cwd(), "out"))) return "./out";
445
+ if (fs.existsSync(path.join(process.cwd(), ".next"))) return "./out";
446
+ if (fs.existsSync(path.join(process.cwd(), "vite.config.ts")) || fs.existsSync(path.join(process.cwd(), "vite.config.js"))) return "./dist";
447
+ if (fs.existsSync(path.join(process.cwd(), "next.config.js")) || fs.existsSync(path.join(process.cwd(), "next.config.mjs"))) return "./out";
869
448
  return "./dist";
870
449
  }
871
- function addToGitignore(entry, basePath) {
872
- const dir = basePath || process.cwd();
873
- const gitignorePath = path2.join(dir, ".gitignore");
874
- if (fs2.existsSync(gitignorePath)) {
875
- const content = fs2.readFileSync(gitignorePath, "utf-8");
450
+ function addToGitignore(entry) {
451
+ const gitignorePath = path.join(process.cwd(), ".gitignore");
452
+ if (fs.existsSync(gitignorePath)) {
453
+ const content = fs.readFileSync(gitignorePath, "utf-8");
876
454
  if (!content.includes(entry)) {
877
- fs2.appendFileSync(gitignorePath, `
455
+ fs.appendFileSync(gitignorePath, `
878
456
  # Connect Base
879
457
  ${entry}
880
458
  `);
@@ -883,28 +461,28 @@ ${entry}
883
461
  info(`.gitignore\uC5D0 \uC774\uBBF8 ${entry}\uAC00 \uD3EC\uD568\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4`);
884
462
  }
885
463
  } else {
886
- fs2.writeFileSync(gitignorePath, `# Connect Base
464
+ fs.writeFileSync(gitignorePath, `# Connect Base
887
465
  ${entry}
888
466
  `);
889
467
  success(".gitignore \uC0DD\uC131 \uC644\uB8CC");
890
468
  }
891
469
  }
892
470
  function addDeployScript(deployDir) {
893
- const pkgPath = path2.join(process.cwd(), "package.json");
894
- if (!fs2.existsSync(pkgPath)) {
471
+ const pkgPath = path.join(process.cwd(), "package.json");
472
+ if (!fs.existsSync(pkgPath)) {
895
473
  warn("package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
896
474
  return;
897
475
  }
898
476
  try {
899
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
477
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
900
478
  if (!pkg.scripts) pkg.scripts = {};
901
479
  if (pkg.scripts.deploy) {
902
480
  info(`package.json\uC5D0 \uC774\uBBF8 deploy \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC788\uC2B5\uB2C8\uB2E4: "${pkg.scripts.deploy}"`);
903
481
  return;
904
482
  }
905
- const deployCmd = `connectbase deploy ${deployDir}`;
483
+ const deployCmd = `connectbase-client deploy ${deployDir}`;
906
484
  pkg.scripts.deploy = pkg.scripts.build ? `${pkg.scripts.build} && ${deployCmd}` : deployCmd;
907
- fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
485
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
908
486
  success(`package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC`);
909
487
  } catch {
910
488
  warn("package.json \uC218\uC815\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4");
@@ -918,58 +496,21 @@ function getGitRoot() {
918
496
  return null;
919
497
  }
920
498
  }
921
- function detectMonorepo(gitRoot) {
922
- const cwd = process.cwd();
923
- const result = { type: "none", root: gitRoot, isSubPackage: false };
924
- if (fs2.existsSync(path2.join(gitRoot, "pnpm-workspace.yaml"))) {
925
- result.type = "pnpm";
926
- } else if (fs2.existsSync(path2.join(gitRoot, "turbo.json"))) {
927
- result.type = "turborepo";
928
- } else if (fs2.existsSync(path2.join(gitRoot, "nx.json"))) {
929
- result.type = "nx";
930
- } else if (fs2.existsSync(path2.join(gitRoot, "lerna.json"))) {
931
- result.type = "lerna";
932
- } else {
933
- const rootPkgPath = path2.join(gitRoot, "package.json");
934
- if (fs2.existsSync(rootPkgPath)) {
935
- try {
936
- const pkg = JSON.parse(fs2.readFileSync(rootPkgPath, "utf-8"));
937
- if (pkg.workspaces) {
938
- result.type = fs2.existsSync(path2.join(gitRoot, "yarn.lock")) ? "yarn" : "npm";
939
- }
940
- } catch {
941
- }
942
- }
943
- }
944
- if (result.type !== "none" && cwd !== gitRoot) {
945
- const cwdPkgPath = path2.join(cwd, "package.json");
946
- if (fs2.existsSync(cwdPkgPath)) {
947
- result.isSubPackage = true;
948
- try {
949
- const pkg = JSON.parse(fs2.readFileSync(cwdPkgPath, "utf-8"));
950
- result.subPackageName = pkg.name;
951
- } catch {
952
- }
953
- }
954
- }
955
- return result;
956
- }
957
- async function downloadDocs(publicKey, templates, baseDir) {
958
- if (!baseDir) {
959
- baseDir = getProjectRoot();
960
- }
961
- const gitRoot = getGitRoot() || baseDir;
962
- const monorepo = detectMonorepo(gitRoot);
963
- if (monorepo.type !== "none") {
964
- info(`\uBAA8\uB178\uB808\uD3EC \uAC10\uC9C0: ${monorepo.type} (\uB8E8\uD2B8: ${gitRoot})`);
965
- if (monorepo.isSubPackage) {
966
- info(`\uD604\uC7AC \uC11C\uBE0C \uD328\uD0A4\uC9C0: ${monorepo.subPackageName || path2.basename(process.cwd())}`);
499
+ async function downloadDocs(apiKey, templates, monorepo) {
500
+ let baseDir = process.cwd();
501
+ if (monorepo) {
502
+ const gitRoot = getGitRoot();
503
+ if (gitRoot) {
504
+ baseDir = gitRoot;
505
+ info(`\uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8 \uAC10\uC9C0: ${gitRoot}`);
506
+ } else {
507
+ warn("git root\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.");
967
508
  }
968
509
  }
969
- const docsDir = path2.join(gitRoot, ".claude", "docs");
970
- const rootClaudeMd = path2.join(gitRoot, "CLAUDE.md");
971
- if (!fs2.existsSync(docsDir)) {
972
- fs2.mkdirSync(docsDir, { recursive: true });
510
+ const docsDir = path.join(baseDir, ".claude", "docs");
511
+ const rootClaudeMd = path.join(baseDir, "CLAUDE.md");
512
+ if (!fs.existsSync(docsDir)) {
513
+ fs.mkdirSync(docsDir, { recursive: true });
973
514
  }
974
515
  if (!templates) {
975
516
  templates = ["rules"];
@@ -978,7 +519,7 @@ async function downloadDocs(publicKey, templates, baseDir) {
978
519
  try {
979
520
  const templateParam = templates.join(",");
980
521
  const res = await makeRequest(
981
- `${DEFAULT_BASE_URL}/v1/storages/webs/claude-md?template=${encodeURIComponent(templateParam)}&format=sections&public_key=${encodeURIComponent(publicKey)}`,
522
+ `${DEFAULT_BASE_URL}/v1/storages/webs/claude-md?template=${encodeURIComponent(templateParam)}&format=sections&api_key=${encodeURIComponent(apiKey)}`,
982
523
  "GET",
983
524
  {}
984
525
  );
@@ -988,15 +529,11 @@ async function downloadDocs(publicKey, templates, baseDir) {
988
529
  const data = typeof res.data === "string" ? JSON.parse(res.data) : res.data;
989
530
  const sections = data.sections || [];
990
531
  for (const section of sections) {
991
- const filePath = path2.join(docsDir, section.filename);
992
- fs2.writeFileSync(filePath, section.content);
532
+ const filePath = path.join(docsDir, section.filename);
533
+ fs.writeFileSync(filePath, section.content);
993
534
  }
994
- success(`${gitRoot}/.claude/docs/ \uC5D0 ${sections.length}\uAC1C \uBB38\uC11C \uC800\uC7A5 \uC644\uB8CC`);
535
+ success(`.claude/docs/ \uC5D0 ${sections.length}\uAC1C \uBB38\uC11C \uC800\uC7A5 \uC644\uB8CC`);
995
536
  updateRootClaudeMd(rootClaudeMd);
996
- if (monorepo.isSubPackage) {
997
- const subClaudeMd = path2.join(process.cwd(), "CLAUDE.md");
998
- createSubPackageClaudeMd(subClaudeMd, gitRoot);
999
- }
1000
537
  log(`${colors.dim}\u203B SDK \uBB38\uC11C\uB294 MCP search_sdk_docs\uB85C \uAC80\uC0C9\uD558\uC138\uC694${colors.reset}`);
1001
538
  } catch {
1002
539
  warn("SDK \uAC00\uC774\uB4DC \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328, \uAE30\uBCF8 \uBB38\uC11C\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4");
@@ -1006,12 +543,8 @@ async function downloadDocs(publicKey, templates, baseDir) {
1006
543
 
1007
544
  \uC0C1\uC138 \uAD6C\uD604\uBC95\uC740 MCP \`search_sdk_docs\` \uB3C4\uAD6C\uB85C \uAC80\uC0C9\uD558\uC138\uC694.
1008
545
  `;
1009
- fs2.writeFileSync(path2.join(docsDir, "project-rules.md"), fallbackContent);
546
+ fs.writeFileSync(path.join(docsDir, "project-rules.md"), fallbackContent);
1010
547
  updateRootClaudeMd(rootClaudeMd);
1011
- if (monorepo.isSubPackage) {
1012
- const subClaudeMd = path2.join(process.cwd(), "CLAUDE.md");
1013
- createSubPackageClaudeMd(subClaudeMd, gitRoot);
1014
- }
1015
548
  success(`.claude/docs/ \uC0DD\uC131 \uC644\uB8CC (\uAE30\uBCF8)`);
1016
549
  }
1017
550
  }
@@ -1022,395 +555,87 @@ function updateRootClaudeMd(claudeMdPath) {
1022
555
  ## ConnectBase SDK
1023
556
 
1024
557
  \uC774 \uD504\uB85C\uC81D\uD2B8\uB294 **ConnectBase**\uB97C \uBC31\uC5D4\uB4DC\uB85C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.
558
+ \uC0AC\uC6A9\uC790\uB294 **\uBE44\uAC1C\uBC1C\uC790**\uC785\uB2C8\uB2E4. \uC26C\uC6B4 \uB9D0\uB85C \uC124\uBA85\uD558\uACE0, \uBC14\uB85C \uC2E4\uD589 \uAC00\uB2A5\uD55C \uC644\uC131\uB41C \uCF54\uB4DC\uB97C \uC81C\uACF5\uD558\uC138\uC694.
1025
559
 
1026
560
  ### AI \uD544\uB3C5 \u2014 \uCF54\uB529 \uC804 \uBC18\uB4DC\uC2DC \uC218\uD589
1027
561
 
1028
- 1. \`.claude/docs/project-rules.md\`\uB97C **Read tool\uB85C \uC77D\uC73C\uC138\uC694** (\uAE30\uB2A5 \uB9E4\uD551\uD45C, \uBCF4\uC548 \uADDC\uCE59, \uC5D0\uB7EC \uAC00\uC774\uB4DC)
1029
- 2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
1030
- 3. \uAC80\uC0C9 \uACB0\uACFC\uC758 \uCF54\uB4DC \uD328\uD134\uC744 \uB530\uB77C \uAD6C\uD604\uD558\uC138\uC694
562
+ 1. \`.claude/docs/project-rules.md\` \uD30C\uC77C\uC744 **Read tool\uB85C \uC77D\uC73C\uC138\uC694** (\uAE30\uB2A5 \uB9E4\uD551\uD45C, \uBCF4\uC548 \uADDC\uCE59 \uD3EC\uD568)
563
+ 2. \uC0AC\uC6A9\uC790 \uC694\uCCAD\uC5D0 \uB9DE\uB294 \uAE30\uB2A5\uC744 \uB9E4\uD551\uD45C\uC5D0\uC11C \uCC3E\uC73C\uC138\uC694
564
+ 3. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
565
+ 4. \uC644\uC131\uB41C \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uC138\uC694 (\uC2A4\uB2C8\uD3AB X, \uBC14\uB85C \uC2E4\uD589 \uAC00\uB2A5\uD55C \uC804\uCCB4 \uCF54\uB4DC)
566
+
567
+ ### \uBE44\uAC1C\uBC1C\uC790 \uC751\uB300 \uC6D0\uCE59
568
+
569
+ - \uC804\uBB38 \uC6A9\uC5B4 \uB300\uC2E0 \uC26C\uC6B4 \uB9D0 \uC0AC\uC6A9 ("API" \u2192 "\uAE30\uB2A5", "DB" \u2192 "\uC800\uC7A5\uC18C")
570
+ - \uC5D0\uB7EC \uBC1C\uC0DD \uC2DC \uC6D0\uC778\uACFC \uD574\uACB0\uCC45\uC744 \uD55C \uC904\uB85C \uC124\uBA85
571
+ - \uCF54\uB4DC \uC791\uC131 \uC804 "\uC774\uB807\uAC8C \uB9CC\uB4E4\uAC8C\uC694~" \uD55C \uC904 \uC694\uC57D \uBA3C\uC800
572
+ - \uBAA8\uB974\uB294 \uAC8C \uC788\uC73C\uBA74 \uB9CC\uB4E4\uAE30 \uC804\uC5D0 \uBA3C\uC800 \uC9C8\uBB38
1031
573
  ${endMarker}`;
1032
- if (fs2.existsSync(claudeMdPath)) {
1033
- let content = fs2.readFileSync(claudeMdPath, "utf-8");
574
+ if (fs.existsSync(claudeMdPath)) {
575
+ let content = fs.readFileSync(claudeMdPath, "utf-8");
1034
576
  const startIdx = content.indexOf(startMarker);
1035
577
  const endIdx = content.indexOf(endMarker);
1036
578
  if (startIdx !== -1 && endIdx !== -1) {
1037
579
  content = content.substring(0, startIdx) + sdkBlock + content.substring(endIdx + endMarker.length);
1038
580
  } else {
1039
- content = sdkBlock + "\n\n" + content.trimStart();
581
+ content = content.trimEnd() + "\n\n" + sdkBlock + "\n";
1040
582
  }
1041
- fs2.writeFileSync(claudeMdPath, content);
583
+ fs.writeFileSync(claudeMdPath, content);
1042
584
  info("CLAUDE.md\uC5D0 ConnectBase \uCC38\uC870 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
1043
585
  } else {
1044
- fs2.writeFileSync(claudeMdPath, `# \uD504\uB85C\uC81D\uD2B8
586
+ fs.writeFileSync(claudeMdPath, `# \uD504\uB85C\uC81D\uD2B8
1045
587
 
1046
588
  ${sdkBlock}
1047
589
  `);
1048
590
  success("CLAUDE.md \uC0DD\uC131 \uC644\uB8CC");
1049
591
  }
1050
592
  }
1051
- function createSubPackageClaudeMd(subClaudeMdPath, gitRoot) {
1052
- const startMarker = "<!-- CONNECTBASE_SUB_START -->";
1053
- const endMarker = "<!-- CONNECTBASE_SUB_END -->";
1054
- const relPath = path2.relative(path2.dirname(subClaudeMdPath), gitRoot);
1055
- const subBlock = `${startMarker}
1056
- ## ConnectBase SDK
1057
-
1058
- \uC774 \uD328\uD0A4\uC9C0\uB294 **ConnectBase**\uB97C \uBC31\uC5D4\uB4DC\uB85C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.
1059
-
1060
- ### AI \uD544\uB3C5
1061
-
1062
- 1. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC758 \`${relPath}/.claude/docs/project-rules.md\`\uB97C **Read tool\uB85C \uC77D\uC73C\uC138\uC694**
1063
- 2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C SDK \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
1064
- 3. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC758 \`${relPath}/CLAUDE.md\`\uB3C4 \uCC38\uACE0\uD558\uC138\uC694
1065
- ${endMarker}`;
1066
- if (fs2.existsSync(subClaudeMdPath)) {
1067
- let content = fs2.readFileSync(subClaudeMdPath, "utf-8");
1068
- const startIdx = content.indexOf(startMarker);
1069
- const endIdx = content.indexOf(endMarker);
1070
- if (startIdx !== -1 && endIdx !== -1) {
1071
- content = content.substring(0, startIdx) + subBlock + content.substring(endIdx + endMarker.length);
1072
- } else if (content.indexOf("<!-- CONNECTBASE_START -->") === -1) {
1073
- content = content.trimEnd() + "\n\n" + subBlock + "\n";
1074
- } else {
1075
- return;
1076
- }
1077
- fs2.writeFileSync(subClaudeMdPath, content);
1078
- } else {
1079
- fs2.writeFileSync(subClaudeMdPath, `# ${path2.basename(path2.dirname(subClaudeMdPath))}
1080
-
1081
- ${subBlock}
1082
- `);
1083
- }
1084
- success(`\uC11C\uBE0C \uD328\uD0A4\uC9C0 CLAUDE.md \uC0DD\uC131 \uC644\uB8CC: ${subClaudeMdPath}`);
1085
- }
1086
- async function setupMcp(secretKey) {
1087
- const gitRoot = getGitRoot();
1088
- const root = gitRoot || process.cwd();
1089
- const monorepo = detectMonorepo(root);
1090
- if (monorepo.type !== "none") {
1091
- info(`\uBAA8\uB178\uB808\uD3EC \uAC10\uC9C0: ${monorepo.type} (\uB8E8\uD2B8: ${root})`);
1092
- if (monorepo.isSubPackage) {
1093
- info(`MCP \uC124\uC815\uC740 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8(${root})\uC5D0 \uC0DD\uC131\uB429\uB2C8\uB2E4`);
1094
- }
1095
- }
1096
- const mcpConfigPath = path2.join(root, ".mcp.json");
1097
- if (!secretKey) {
1098
- log(`
1099
- ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
1100
- log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
1101
- log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
1102
- `);
1103
- const authChoice = await prompt(`${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `);
1104
- if (authChoice === "2") {
1105
- try {
1106
- secretKey = await browserAuthFlow();
1107
- const skRcPath = path2.join(process.cwd(), ".connectbaserc");
1108
- let rcData = {};
1109
- if (fs2.existsSync(skRcPath)) {
1110
- try {
1111
- rcData = JSON.parse(fs2.readFileSync(skRcPath, "utf-8"));
1112
- } catch {
1113
- }
593
+ async function setupClaudeCode(apiKey) {
594
+ const mcpConfigPath = path.join(process.cwd(), ".mcp.json");
595
+ await downloadDocs(apiKey);
596
+ const mcpConfig = {
597
+ mcpServers: {
598
+ "connect-base": {
599
+ type: "http",
600
+ url: "https://mcp.connectbase.world/mcp",
601
+ headers: {
602
+ Authorization: "Bearer YOUR_SECRET_KEY_HERE"
1114
603
  }
1115
- rcData.secretKey = secretKey;
1116
- fs2.writeFileSync(skRcPath, JSON.stringify(rcData, null, 2) + "\n");
1117
- addToGitignore(".connectbaserc", root);
1118
- success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
1119
- } catch (err) {
1120
- error(`\uC778\uC99D \uC2E4\uD328: ${err instanceof Error ? err.message : err}`);
1121
- process.exit(1);
1122
604
  }
1123
- } else {
1124
- secretKey = await promptSecret(`${colors.blue}?${colors.reset} Secret Key: `);
1125
- }
1126
- }
1127
- const mcpEntry = {
1128
- type: "http",
1129
- url: "https://mcp.connectbase.world/mcp",
1130
- headers: {
1131
- Authorization: `Bearer ${secretKey || "YOUR_SECRET_KEY_HERE"}`
1132
605
  }
1133
606
  };
1134
- let mcpConfig = { mcpServers: {} };
1135
- if (fs2.existsSync(mcpConfigPath)) {
1136
- try {
1137
- mcpConfig = JSON.parse(fs2.readFileSync(mcpConfigPath, "utf-8"));
1138
- if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== "object") {
1139
- mcpConfig.mcpServers = {};
1140
- }
1141
- } catch {
1142
- const backupPath = mcpConfigPath + ".backup";
1143
- fs2.copyFileSync(mcpConfigPath, backupPath);
1144
- warn(`.mcp.json \uD30C\uC2F1 \uC2E4\uD328, \uBC31\uC5C5 \uC0DD\uC131: ${backupPath}`);
1145
- mcpConfig = { mcpServers: {} };
1146
- }
1147
- }
1148
- mcpConfig.mcpServers["connect-base"] = mcpEntry;
1149
- fs2.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
1150
- success(`${mcpConfigPath} \uC0DD\uC131 \uC644\uB8CC`);
1151
- addToGitignore(".mcp.json", root);
1152
- if (secretKey && secretKey !== "YOUR_SECRET_KEY_HERE") {
1153
- success("MCP \uC11C\uBC84 \uC124\uC815 \uC644\uB8CC (Secret Key \uC801\uC6A9\uB428)");
607
+ fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
608
+ success(".mcp.json \uC0DD\uC131 \uC644\uB8CC");
609
+ addToGitignore(".mcp.json");
610
+ warn("MCP \uC11C\uBC84\uB294 Secret Key (cb_sk_)\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4.");
611
+ info(".mcp.json \uD30C\uC77C\uC758 YOUR_SECRET_KEY_HERE\uB97C Secret Key\uB85C \uAD50\uCCB4\uD558\uC138\uC694.");
612
+ info("Secret Key\uB294 \uCF58\uC194 > \uC124\uC815 > API Keys\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
613
+ }
614
+ function createWsTextFrame(payload) {
615
+ const data = Buffer.from(payload, "utf-8");
616
+ const len = data.length;
617
+ const maskKey = crypto.randomBytes(4);
618
+ let header;
619
+ if (len < 126) {
620
+ header = Buffer.alloc(2);
621
+ header[0] = 129;
622
+ header[1] = 128 | len;
623
+ } else if (len < 65536) {
624
+ header = Buffer.alloc(4);
625
+ header[0] = 129;
626
+ header[1] = 128 | 126;
627
+ header.writeUInt16BE(len, 2);
1154
628
  } else {
1155
- warn("MCP \uC11C\uBC84\uB294 Secret Key (cb_sk_)\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4.");
1156
- info(".mcp.json \uD30C\uC77C\uC758 YOUR_SECRET_KEY_HERE\uB97C Secret Key\uB85C \uAD50\uCCB4\uD558\uC138\uC694.");
629
+ header = Buffer.alloc(10);
630
+ header[0] = 129;
631
+ header[1] = 128 | 127;
632
+ header.writeBigUInt64BE(BigInt(len), 2);
1157
633
  }
1158
- log(`
1159
- ${colors.dim}Claude Code\uC5D0\uC11C ConnectBase MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.${colors.reset}`);
1160
- }
1161
- async function setupClaudeCode(publicKey, secretKey, projectRoot) {
1162
- const root = projectRoot || getProjectRoot();
1163
- await downloadDocs(publicKey, void 0, root);
1164
- await setupMcp(secretKey);
1165
- }
1166
- async function fetchLatestVersion(packageName) {
1167
- try {
1168
- const res = await makeRequest(
1169
- `https://registry.npmjs.org/${packageName}/latest`,
1170
- "GET",
1171
- {}
1172
- );
1173
- if (res.status === 200) {
1174
- const data = res.data;
1175
- return data.version || null;
1176
- }
1177
- } catch {
1178
- }
1179
- return null;
1180
- }
1181
- function compareVersions(a, b) {
1182
- const pa = a.split(".").map(Number);
1183
- const pb = b.split(".").map(Number);
1184
- for (let i = 0; i < 3; i++) {
1185
- const diff = (pa[i] || 0) - (pb[i] || 0);
1186
- if (diff !== 0) return diff > 0 ? 1 : -1;
1187
- }
1188
- return 0;
1189
- }
1190
- function findSubPackagesWithConfig(gitRoot) {
1191
- const results = [];
1192
- const candidates = ["apps", "packages", "projects", "services", "libs"];
1193
- for (const candidate of candidates) {
1194
- const dir = path2.join(gitRoot, candidate);
1195
- if (!fs2.existsSync(dir)) continue;
1196
- try {
1197
- const entries = fs2.readdirSync(dir, { withFileTypes: true });
1198
- for (const entry of entries) {
1199
- if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
1200
- const subDir = path2.join(dir, entry.name);
1201
- if (fs2.existsSync(path2.join(subDir, ".connectbaserc"))) {
1202
- results.push(subDir);
1203
- }
1204
- }
1205
- } catch {
1206
- }
1207
- }
1208
- try {
1209
- const entries = fs2.readdirSync(gitRoot, { withFileTypes: true });
1210
- for (const entry of entries) {
1211
- if (!entry.isDirectory() || entry.name.startsWith(".") || candidates.includes(entry.name)) continue;
1212
- if (entry.name === "node_modules") continue;
1213
- const subDir = path2.join(gitRoot, entry.name);
1214
- if (fs2.existsSync(path2.join(subDir, ".connectbaserc")) && !results.includes(subDir)) {
1215
- results.push(subDir);
1216
- }
1217
- }
1218
- } catch {
1219
- }
1220
- return results;
1221
- }
1222
- async function update(config, opts) {
1223
- log(`
1224
- ${colors.cyan}ConnectBase Update${colors.reset}
1225
- `);
1226
- const gitRoot = getGitRoot();
1227
- const projectRoot = gitRoot || process.cwd();
1228
- const monorepo = detectMonorepo(projectRoot);
1229
- info("\uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778 \uC911...");
1230
- const latestVersion = await fetchLatestVersion("connectbase-client");
1231
- if (latestVersion) {
1232
- const cmp = compareVersions(VERSION, latestVersion);
1233
- if (cmp >= 0) {
1234
- success(`\uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4 (v${VERSION})`);
1235
- } else {
1236
- warn(`\uC0C8 \uBC84\uC804\uC774 \uC788\uC2B5\uB2C8\uB2E4: v${VERSION} \u2192 v${colors.green}${latestVersion}${colors.reset}`);
1237
- log(` ${colors.cyan}npm install connectbase-client@latest${colors.reset}`);
1238
- log(` ${colors.cyan}pnpm add connectbase-client@latest${colors.reset}`);
1239
- log("");
1240
- }
1241
- } else {
1242
- warn("npm \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0\uC11C \uBC84\uC804 \uC815\uBCF4\uB97C \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
1243
- }
1244
- if (opts.checkOnly) {
1245
- return;
1246
- }
1247
- if (monorepo.type !== "none") {
1248
- info(`\uBAA8\uB178\uB808\uD3EC \uAC10\uC9C0: ${monorepo.type} (\uB8E8\uD2B8: ${projectRoot})`);
1249
- const subPackages = findSubPackagesWithConfig(projectRoot);
1250
- if (subPackages.length > 0) {
1251
- info(`ConnectBase\uB97C \uC0AC\uC6A9\uD558\uB294 \uC11C\uBE0C \uD328\uD0A4\uC9C0 ${subPackages.length}\uAC1C \uBC1C\uACAC:`);
1252
- for (const sp of subPackages) {
1253
- log(` ${colors.dim}\u2022 ${path2.relative(projectRoot, sp)}${colors.reset}`);
1254
- }
1255
- }
1256
- }
1257
- if (!opts.skipDocs) {
1258
- log("");
1259
- info("SDK \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8 \uC911...");
1260
- let docsKey = config.publicKey || config.secretKey;
1261
- if (!docsKey) {
1262
- const rootRcPath = path2.join(projectRoot, ".connectbaserc");
1263
- if (fs2.existsSync(rootRcPath)) {
1264
- try {
1265
- const rc = JSON.parse(fs2.readFileSync(rootRcPath, "utf-8"));
1266
- docsKey = rc.publicKey || rc.secretKey;
1267
- } catch {
1268
- }
1269
- }
1270
- }
1271
- if (!docsKey && monorepo.type !== "none") {
1272
- const subPackages = findSubPackagesWithConfig(projectRoot);
1273
- for (const sp of subPackages) {
1274
- try {
1275
- const rc = JSON.parse(fs2.readFileSync(path2.join(sp, ".connectbaserc"), "utf-8"));
1276
- if (rc.publicKey || rc.secretKey) {
1277
- docsKey = rc.publicKey || rc.secretKey;
1278
- break;
1279
- }
1280
- } catch {
1281
- }
1282
- }
1283
- }
1284
- if (docsKey) {
1285
- await downloadDocs(docsKey, void 0, projectRoot);
1286
- } else {
1287
- warn("Public Key\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC5B4 \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4");
1288
- info(".connectbaserc \uD30C\uC77C\uC774\uB098 --public-key \uC635\uC158\uC73C\uB85C \uD0A4\uB97C \uC9C0\uC815\uD558\uC138\uC694");
1289
- }
1290
- }
1291
- if (!opts.skipMcp) {
1292
- log("");
1293
- const mcpConfigPath = path2.join(projectRoot, ".mcp.json");
1294
- if (fs2.existsSync(mcpConfigPath)) {
1295
- let secretKey = config.secretKey;
1296
- if (!secretKey) {
1297
- const rootRcPath = path2.join(projectRoot, ".connectbaserc");
1298
- if (fs2.existsSync(rootRcPath)) {
1299
- try {
1300
- const rc = JSON.parse(fs2.readFileSync(rootRcPath, "utf-8"));
1301
- secretKey = rc.secretKey;
1302
- } catch {
1303
- }
1304
- }
1305
- }
1306
- if (!secretKey && monorepo.type !== "none") {
1307
- const subPackages = findSubPackagesWithConfig(projectRoot);
1308
- for (const sp of subPackages) {
1309
- try {
1310
- const rc = JSON.parse(fs2.readFileSync(path2.join(sp, ".connectbaserc"), "utf-8"));
1311
- if (rc.secretKey) {
1312
- secretKey = rc.secretKey;
1313
- break;
1314
- }
1315
- } catch {
1316
- }
1317
- }
1318
- }
1319
- if (secretKey) {
1320
- try {
1321
- const mcpConfig = JSON.parse(fs2.readFileSync(mcpConfigPath, "utf-8"));
1322
- const servers = mcpConfig.mcpServers || {};
1323
- if (servers["connect-base"]) {
1324
- servers["connect-base"] = {
1325
- type: "http",
1326
- url: "https://mcp.connectbase.world/mcp",
1327
- headers: { Authorization: `Bearer ${secretKey}` }
1328
- };
1329
- mcpConfig.mcpServers = servers;
1330
- fs2.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
1331
- success("MCP \uC124\uC815 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
1332
- } else {
1333
- info('.mcp.json\uC5D0 connect-base \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. "connectbase mcp"\uB85C \uC124\uC815\uD558\uC138\uC694');
1334
- }
1335
- } catch {
1336
- warn(".mcp.json \uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328");
1337
- }
1338
- } else {
1339
- info("Secret Key\uAC00 \uC5C6\uC5B4 MCP \uC124\uC815 \uC5C5\uB370\uC774\uD2B8\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4");
1340
- }
1341
- } else {
1342
- info('.mcp.json\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. "connectbase mcp"\uB85C \uCD5C\uCD08 \uC124\uC815\uD558\uC138\uC694');
1343
- }
1344
- }
1345
- if (opts.setupRoot || monorepo.type !== "none" && monorepo.isSubPackage) {
1346
- log("");
1347
- setupMonorepoRoot(projectRoot, monorepo);
1348
- }
1349
- log(`
1350
- ${colors.green}\uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!${colors.reset}
1351
- `);
1352
- }
1353
- function setupMonorepoRoot(projectRoot, monorepo) {
1354
- const rootPkgPath = path2.join(projectRoot, "package.json");
1355
- if (!fs2.existsSync(rootPkgPath)) {
1356
- 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");
1357
- return;
1358
- }
1359
- try {
1360
- const pkg = JSON.parse(fs2.readFileSync(rootPkgPath, "utf-8"));
1361
- if (!pkg.scripts) pkg.scripts = {};
1362
- let changed = false;
1363
- if (!pkg.scripts["cb:update"]) {
1364
- pkg.scripts["cb:update"] = "npx connectbase update";
1365
- changed = true;
1366
- }
1367
- if (!pkg.scripts["cb:docs"]) {
1368
- pkg.scripts["cb:docs"] = "npx connectbase docs";
1369
- changed = true;
1370
- }
1371
- if (!pkg.scripts["cb:mcp"]) {
1372
- pkg.scripts["cb:mcp"] = "npx connectbase mcp";
1373
- changed = true;
1374
- }
1375
- if (changed) {
1376
- fs2.writeFileSync(rootPkgPath, JSON.stringify(pkg, null, 2) + "\n");
1377
- success("\uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC");
1378
- log(` ${colors.dim}\uC0AC\uC6A9\uBC95:${colors.reset}`);
1379
- 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`);
1380
- log(` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:docs${colors.reset} \u2014 \uBB38\uC11C\uB9CC \uAC31\uC2E0`);
1381
- log(` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:mcp${colors.reset} \u2014 MCP \uC124\uC815`);
1382
- } else {
1383
- 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");
1384
- }
1385
- } catch {
1386
- warn("\uB8E8\uD2B8 package.json \uC218\uC815\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4");
1387
- }
1388
- }
1389
- function createWsTextFrame(payload) {
1390
- const data = Buffer.from(payload, "utf-8");
1391
- const len = data.length;
1392
- const maskKey = crypto.randomBytes(4);
1393
- let header;
1394
- if (len < 126) {
1395
- header = Buffer.alloc(2);
1396
- header[0] = 129;
1397
- header[1] = 128 | len;
1398
- } else if (len < 65536) {
1399
- header = Buffer.alloc(4);
1400
- header[0] = 129;
1401
- header[1] = 128 | 126;
1402
- header.writeUInt16BE(len, 2);
1403
- } else {
1404
- header = Buffer.alloc(10);
1405
- header[0] = 129;
1406
- header[1] = 128 | 127;
1407
- header.writeBigUInt64BE(BigInt(len), 2);
1408
- }
1409
- const masked = Buffer.alloc(data.length);
1410
- for (let i = 0; i < data.length; i++) {
1411
- masked[i] = data[i] ^ maskKey[i % 4];
1412
- }
1413
- return Buffer.concat([header, maskKey, masked]);
634
+ const masked = Buffer.alloc(data.length);
635
+ for (let i = 0; i < data.length; i++) {
636
+ masked[i] = data[i] ^ maskKey[i % 4];
637
+ }
638
+ return Buffer.concat([header, maskKey, masked]);
1414
639
  }
1415
640
  function createWsCloseFrame(code) {
1416
641
  const maskKey = crypto.randomBytes(4);
@@ -1431,159 +656,10 @@ function createWsPongFrame() {
1431
656
  header[1] = 128 | 0;
1432
657
  return Buffer.concat([header, maskKey]);
1433
658
  }
1434
- function createWsBinaryFrame(payload) {
1435
- const len = payload.length;
1436
- const maskKey = crypto.randomBytes(4);
1437
- let header;
1438
- if (len < 126) {
1439
- header = Buffer.alloc(2);
1440
- header[0] = 130;
1441
- header[1] = 128 | len;
1442
- } else if (len < 65536) {
1443
- header = Buffer.alloc(4);
1444
- header[0] = 130;
1445
- header[1] = 128 | 126;
1446
- header.writeUInt16BE(len, 2);
1447
- } else {
1448
- header = Buffer.alloc(10);
1449
- header[0] = 130;
1450
- header[1] = 128 | 127;
1451
- header.writeBigUInt64BE(BigInt(len), 2);
1452
- }
1453
- const masked = Buffer.alloc(len);
1454
- for (let i = 0; i < len; i++) {
1455
- masked[i] = payload[i] ^ maskKey[i % 4];
1456
- }
1457
- return Buffer.concat([header, maskKey, masked]);
1458
- }
1459
- var UpstreamWsFrameParser = class {
1460
- constructor(handlers) {
1461
- this.buffer = Buffer.alloc(0);
1462
- this.fragmentBuffer = null;
1463
- this.h = handlers;
1464
- }
1465
- feed(chunk) {
1466
- this.buffer = Buffer.concat([this.buffer, chunk]);
1467
- try {
1468
- this.parse();
1469
- } catch (e) {
1470
- this.h.onError(e instanceof Error ? e : new Error(String(e)));
1471
- }
1472
- }
1473
- parse() {
1474
- while (this.buffer.length >= 2) {
1475
- const firstByte = this.buffer[0];
1476
- const secondByte = this.buffer[1];
1477
- const fin = (firstByte & 128) !== 0;
1478
- const rsv1 = (firstByte & 64) !== 0;
1479
- const opcode = firstByte & 15;
1480
- const isMasked = (secondByte & 128) !== 0;
1481
- let payloadLen = secondByte & 127;
1482
- let offset = 2;
1483
- if (payloadLen === 126) {
1484
- if (this.buffer.length < 4) return;
1485
- payloadLen = this.buffer.readUInt16BE(2);
1486
- offset = 4;
1487
- } else if (payloadLen === 127) {
1488
- if (this.buffer.length < 10) return;
1489
- const big = this.buffer.readBigUInt64BE(2);
1490
- if (big > BigInt(Number.MAX_SAFE_INTEGER)) {
1491
- throw new Error(`payload too large: ${big}`);
1492
- }
1493
- payloadLen = Number(big);
1494
- offset = 10;
1495
- }
1496
- let maskKey = null;
1497
- if (isMasked) {
1498
- if (this.buffer.length < offset + 4) return;
1499
- maskKey = this.buffer.subarray(offset, offset + 4);
1500
- offset += 4;
1501
- }
1502
- if (this.buffer.length < offset + payloadLen) return;
1503
- if (rsv1 && (opcode === 1 || opcode === 2 || opcode === 0)) {
1504
- throw new Error("upstream sent compressed (RSV1) frame without negotiation");
1505
- }
1506
- let payload = this.buffer.subarray(offset, offset + payloadLen);
1507
- if (maskKey) {
1508
- const unmasked = Buffer.alloc(payloadLen);
1509
- for (let i = 0; i < payloadLen; i++) {
1510
- unmasked[i] = payload[i] ^ maskKey[i % 4];
1511
- }
1512
- payload = unmasked;
1513
- }
1514
- this.buffer = this.buffer.subarray(offset + payloadLen);
1515
- const payloadCopy = Buffer.from(payload);
1516
- switch (opcode) {
1517
- case 0:
1518
- if (this.fragmentBuffer == null) {
1519
- throw new Error("continuation frame without preceding non-final data frame");
1520
- }
1521
- this.fragmentBuffer = Buffer.concat([this.fragmentBuffer, payloadCopy]);
1522
- if (fin) {
1523
- const out = this.fragmentBuffer;
1524
- this.fragmentBuffer = null;
1525
- this.h.onPayload(out);
1526
- }
1527
- break;
1528
- case 1:
1529
- // TEXT
1530
- case 2:
1531
- if (fin) {
1532
- this.h.onPayload(payloadCopy);
1533
- } else {
1534
- this.fragmentBuffer = payloadCopy;
1535
- }
1536
- break;
1537
- case 8:
1538
- this.h.onClose();
1539
- break;
1540
- case 9:
1541
- this.h.onPing(payloadCopy);
1542
- break;
1543
- case 10:
1544
- break;
1545
- default:
1546
- throw new Error(`unknown WS opcode 0x${opcode.toString(16)}`);
1547
- }
1548
- }
1549
- }
1550
- };
1551
- function createUpstreamTextFrame(payload) {
1552
- return buildClientFrame(129, payload);
1553
- }
1554
- function createUpstreamPongFrame(payload) {
1555
- return buildClientFrame(138, payload);
1556
- }
1557
- function buildClientFrame(firstByte, payload) {
1558
- const len = payload.length;
1559
- const maskKey = crypto.randomBytes(4);
1560
- let header;
1561
- if (len < 126) {
1562
- header = Buffer.alloc(2);
1563
- header[0] = firstByte;
1564
- header[1] = 128 | len;
1565
- } else if (len < 65536) {
1566
- header = Buffer.alloc(4);
1567
- header[0] = firstByte;
1568
- header[1] = 128 | 126;
1569
- header.writeUInt16BE(len, 2);
1570
- } else {
1571
- header = Buffer.alloc(10);
1572
- header[0] = firstByte;
1573
- header[1] = 128 | 127;
1574
- header.writeBigUInt64BE(BigInt(len), 2);
1575
- }
1576
- const masked = Buffer.alloc(len);
1577
- for (let i = 0; i < len; i++) {
1578
- masked[i] = payload[i] ^ maskKey[i % 4];
1579
- }
1580
- return Buffer.concat([header, maskKey, masked]);
1581
- }
1582
659
  var WsFrameParser = class {
1583
660
  constructor(handlers) {
1584
661
  this.buffer = Buffer.alloc(0);
1585
662
  this.onMessage = handlers.onMessage;
1586
- this.onBinary = handlers.onBinary;
1587
663
  this.onClose = handlers.onClose;
1588
664
  this.onPing = handlers.onPing;
1589
665
  }
@@ -1628,13 +704,6 @@ var WsFrameParser = class {
1628
704
  case 1:
1629
705
  this.onMessage(payload.toString("utf-8"));
1630
706
  break;
1631
- case 2:
1632
- if (this.onBinary) {
1633
- const copy = Buffer.alloc(payload.length);
1634
- payload.copy(copy);
1635
- this.onBinary(copy);
1636
- }
1637
- break;
1638
707
  case 8:
1639
708
  this.onClose();
1640
709
  break;
@@ -1656,195 +725,27 @@ function getTunnelServerUrl(baseUrl) {
1656
725
  }
1657
726
  return baseUrl.replace(/:\d+/, ":8090");
1658
727
  }
1659
- async function resolveApp(secretKey, baseUrl, appIdOption) {
1660
- if (appIdOption) {
1661
- return { appId: appIdOption };
1662
- }
1663
- let apps = [];
1664
- try {
1665
- info("\uC571 \uBAA9\uB85D \uC870\uD68C \uC911...");
1666
- const appsRes = await makeRequest(
1667
- `${baseUrl}/v1/public/cli/apps`,
1668
- "GET",
1669
- { "X-Public-Key": secretKey }
1670
- );
1671
- if (appsRes.status === 401) {
1672
- error("Secret Key\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uCF58\uC194\uC5D0\uC11C \uD0A4\uB97C \uD655\uC778\uD558\uC138\uC694");
1673
- process.exit(1);
1674
- }
1675
- if (appsRes.status !== 200) {
1676
- throw new Error(`HTTP ${appsRes.status}`);
1677
- }
1678
- const appsData = appsRes.data;
1679
- apps = appsData.apps || [];
1680
- } catch (err) {
1681
- error(`\uC571 \uBAA9\uB85D \uC870\uD68C \uC2E4\uD328: ${err instanceof Error ? err.message : err}`);
1682
- process.exit(1);
1683
- }
1684
- if (apps.length === 1) {
1685
- success(`\uC571 \uC790\uB3D9 \uC120\uD0DD: ${apps[0].name}`);
1686
- return { appId: apps[0].id };
1687
- }
1688
- if (apps.length > 0) {
1689
- log(`
1690
- ${colors.dim}\uB0B4 \uC571 \uBAA9\uB85D:${colors.reset}`);
1691
- apps.forEach((a, i) => {
1692
- const date = a.created_at ? a.created_at.substring(0, 10) : "";
1693
- log(` ${colors.cyan}${i + 1}${colors.reset}) ${a.name} ${colors.dim}(${date})${colors.reset}`);
1694
- });
1695
- }
1696
- log(` ${colors.cyan}0${colors.reset}) \uC0C8 \uC571 \uB9CC\uB4E4\uAE30`);
1697
- const choice = await prompt(`
1698
- ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
1699
- const num = parseInt(choice, 10);
1700
- if (num > 0 && num <= apps.length) {
1701
- success(`\uC120\uD0DD\uB428: ${apps[num - 1].name}`);
1702
- return { appId: apps[num - 1].id };
1703
- }
1704
- const projectName = path2.basename(process.cwd());
1705
- const appName = await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
1706
- info("\uC571 \uC0DD\uC131 \uC911...");
1707
- const createRes = await makeRequest(
1708
- `${baseUrl}/v1/public/cli/apps`,
1709
- "POST",
1710
- { "X-Public-Key": secretKey },
1711
- JSON.stringify({ name: appName })
1712
- );
1713
- if (createRes.status === 402) {
1714
- error("\uC571 \uC0DD\uC131 \uD55C\uB3C4 \uCD08\uACFC. \uD50C\uB79C \uC5C5\uADF8\uB808\uC774\uB4DC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4");
1715
- process.exit(1);
1716
- }
1717
- if (createRes.status !== 201) {
1718
- const data = createRes.data;
1719
- error(`\uC571 \uC0DD\uC131 \uC2E4\uD328: ${data?.error || `HTTP ${createRes.status}`}`);
1720
- process.exit(1);
1721
- }
1722
- const createData = createRes.data;
1723
- success(`\uC571 \uC0DD\uC131 \uC644\uB8CC: ${createData.app_name}`);
1724
- return { appId: createData.app_id, publicKey: createData.public_key };
1725
- }
1726
- async function registerEndpointBinding(baseUrl, appId, secretKey, tunnelId, label, description) {
1727
- try {
1728
- const apiBase = baseUrl.replace(/\/+$/, "");
1729
- const res = await fetch(`${apiBase}/v1/apps/${encodeURIComponent(appId)}/endpoints/cli`, {
1730
- method: "POST",
1731
- headers: {
1732
- "Content-Type": "application/json",
1733
- "X-Public-Key": secretKey
1734
- },
1735
- body: JSON.stringify({
1736
- label,
1737
- tunnel_id: tunnelId,
1738
- description: description ?? `CLI tunnel start (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)})`
1739
- })
1740
- });
1741
- if (res.status === 201) {
1742
- success(`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC644\uB8CC`);
1743
- log(`${colors.green}\u2192${colors.reset} SDK: ${colors.cyan}cb.endpoint.call("${label}", { path: "/...", method: "POST", body: ... })${colors.reset}`);
1744
- } else if (res.status === 409) {
1745
- warn(`"${label}" \uB77C\uBCA8\uC774 \uC774\uBBF8 \uB4F1\uB85D\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uC0C8 tunnel_id \uB85C \uAC31\uC2E0\uD558\uB824\uBA74 \uCF58\uC194\uC5D0\uC11C \uC218\uB3D9 PATCH.`);
1746
- } else {
1747
- const text = await res.text().catch(() => "");
1748
- error(`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC2E4\uD328 (status ${res.status}): ${text || "(\uBE48 \uC751\uB2F5)"}`);
1749
- if (res.status === 401) {
1750
- info("cb_sk_ Secret Key \uC778\uC9C0, \uCF58\uC194\uC5D0\uC11C \uD68C\uC218\uB418\uC9C0 \uC54A\uC558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.");
1751
- }
1752
- }
1753
- } catch (err) {
1754
- error(`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC911 \uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
1755
- }
1756
- }
1757
- function acquireTunnelLock2(appID, port, force) {
1758
- const result = acquireTunnelLock(appID, port, force, VERSION);
1759
- if (result && result.startsWith("LOCKED:")) {
1760
- const [, pid, startedAt, host] = result.split(":");
1761
- error(`\uC774\uBBF8 \uC2E4\uD589 \uC911\uC778 \uD130\uB110\uC774 \uC788\uC2B5\uB2C8\uB2E4: PID ${pid}, \uC2DC\uC791 ${startedAt}, \uD638\uC2A4\uD2B8 ${host}`);
1762
- info("\uC911\uBCF5 \uC2E4\uD589\uC744 \uBB34\uC2DC\uD558\uB824\uBA74 --force \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694");
1763
- process.exit(2);
1764
- }
1765
- return result;
1766
- }
1767
728
  async function startTunnel(port, config, tunnelOpts) {
1768
- let tunnelKey = config.secretKey || (config.publicKey?.startsWith("cb_sk_") ? config.publicKey : "");
1769
- if (!tunnelKey) {
1770
- info("Secret Key\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uBC1C\uAE09\uD569\uB2C8\uB2E4...");
1771
- try {
1772
- const sk = await browserAuthFlow();
1773
- const rcPath2 = path2.join(process.cwd(), ".connectbaserc");
1774
- let rcData = {};
1775
- if (fs2.existsSync(rcPath2)) {
1776
- try {
1777
- rcData = JSON.parse(fs2.readFileSync(rcPath2, "utf-8"));
1778
- } catch {
1779
- }
1780
- }
1781
- rcData.secretKey = sk;
1782
- fs2.writeFileSync(rcPath2, JSON.stringify(rcData, null, 2) + "\n");
1783
- addToGitignore(".connectbaserc");
1784
- success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
1785
- tunnelKey = sk;
1786
- config.secretKey = sk;
1787
- } catch (err) {
1788
- error(`\uC778\uC99D \uC2E4\uD328: ${err instanceof Error ? err.message : err}`);
1789
- info("-k \uC635\uC158\uC73C\uB85C Secret Key\uB97C \uC9C1\uC811 \uC804\uB2EC\uD560 \uC218\uB3C4 \uC788\uC2B5\uB2C8\uB2E4");
1790
- process.exit(1);
1791
- }
1792
- } else {
1793
- config.secretKey = tunnelKey;
1794
- }
1795
- const rcPath = path2.join(process.cwd(), ".connectbaserc");
1796
- let savedAppId = tunnelOpts?.appId || "";
1797
- if (!savedAppId) {
1798
- try {
1799
- const rc = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
1800
- if (rc.tunnelAppId) savedAppId = rc.tunnelAppId;
1801
- } catch {
1802
- }
1803
- }
1804
- const { appId } = await resolveApp(tunnelKey, config.baseUrl, savedAppId);
1805
- try {
1806
- let rcData = {};
1807
- if (fs2.existsSync(rcPath)) {
1808
- try {
1809
- rcData = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
1810
- } catch {
1811
- }
1812
- }
1813
- if (rcData.tunnelAppId !== appId) {
1814
- rcData.tunnelAppId = appId;
1815
- fs2.writeFileSync(rcPath, JSON.stringify(rcData, null, 2) + "\n");
1816
- }
1817
- } catch {
729
+ if (!config.apiKey) {
730
+ error("API Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. -k \uC635\uC158 \uB610\uB294 CONNECTBASE_API_KEY \uD658\uACBD\uBCC0\uC218\uB97C \uC124\uC815\uD558\uC138\uC694");
731
+ process.exit(1);
1818
732
  }
1819
- const lockPath = acquireTunnelLock2(appId, port, tunnelOpts?.force ?? false);
1820
733
  const tunnelServerUrl = getTunnelServerUrl(config.baseUrl);
1821
734
  const parsedUrl = new URL(tunnelServerUrl);
1822
735
  const isHttps = parsedUrl.protocol === "https:";
1823
- let wsPath = `/v2/tunnel/connect?app_id=${encodeURIComponent(appId)}&local_port=${port}`;
736
+ let wsPath = `/v1/tunnel/connect?api_key=${encodeURIComponent(config.apiKey)}`;
1824
737
  if (tunnelOpts?.timeout) {
1825
738
  wsPath += `&timeout=${tunnelOpts.timeout}`;
1826
739
  }
1827
740
  if (tunnelOpts?.maxBody) {
1828
741
  wsPath += `&max_body=${tunnelOpts.maxBody}`;
1829
742
  }
1830
- if (tunnelOpts?.public) {
1831
- wsPath += `&public=1`;
1832
- }
1833
743
  let reconnectAttempts = 0;
1834
744
  const maxReconnectAttempts = 10;
1835
745
  let shouldReconnect = true;
1836
746
  let socket = null;
1837
- let lockReleased = false;
1838
- const releaseLock = () => {
1839
- if (lockPath && !lockReleased) {
1840
- lockReleased = true;
1841
- releaseTunnelLock(lockPath);
1842
- }
1843
- };
1844
- process.on("exit", releaseLock);
1845
747
  const cleanup = () => {
1846
748
  shouldReconnect = false;
1847
- releaseLock();
1848
749
  if (socket) {
1849
750
  try {
1850
751
  socket.write(createWsCloseFrame(1e3));
@@ -1878,8 +779,7 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
1878
779
  "Upgrade": "websocket",
1879
780
  "Connection": "Upgrade",
1880
781
  "Sec-WebSocket-Key": wsKey,
1881
- "Sec-WebSocket-Version": "13",
1882
- "Authorization": `Bearer ${config.secretKey ?? config.publicKey}`
782
+ "Sec-WebSocket-Version": "13"
1883
783
  }
1884
784
  };
1885
785
  const req = lib.request(reqOptions);
@@ -1890,22 +790,11 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
1890
790
  onMessage: (data) => {
1891
791
  try {
1892
792
  const msg = JSON.parse(data);
1893
- handleMessage(msg, sock, port).catch((e) => {
1894
- error(`\uBA54\uC2DC\uC9C0 \uCC98\uB9AC \uC911 \uC608\uC678: ${e instanceof Error ? e.message : e}`);
1895
- });
793
+ handleMessage(msg, sock, port);
1896
794
  } catch (e) {
1897
795
  warn(`\uBA54\uC2DC\uC9C0 \uD30C\uC2F1 \uC2E4\uD328: ${e}`);
1898
796
  }
1899
797
  },
1900
- onBinary: (data) => {
1901
- if (data.length < 8) {
1902
- warn(`v2 binary frame too short (${data.length} bytes)`);
1903
- return;
1904
- }
1905
- const streamId = data.readBigUInt64BE(0).toString();
1906
- const payload = data.subarray(8);
1907
- routeStreamData(streamId, payload);
1908
- },
1909
798
  onClose: () => {
1910
799
  info("\uC11C\uBC84\uAC00 \uC5F0\uACB0\uC744 \uC885\uB8CC\uD588\uC2B5\uB2C8\uB2E4");
1911
800
  sock.destroy();
@@ -1969,314 +858,95 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
1969
858
  info(`${(delay / 1e3).toFixed(0)}\uCD08 \uD6C4 \uC7AC\uC5F0\uACB0 \uC2DC\uB3C4... (${reconnectAttempts}/${maxReconnectAttempts})`);
1970
859
  setTimeout(connect, delay);
1971
860
  }
1972
- const streams = /* @__PURE__ */ new Map();
1973
- function routeStreamData(streamId, payload) {
1974
- const f = streams.get(streamId);
1975
- if (!f) return;
1976
- f.feedBody(payload);
1977
- }
1978
- function sendControl(sock, msg) {
1979
- try {
1980
- sock.write(createWsTextFrame(JSON.stringify(msg)));
1981
- } catch {
1982
- }
1983
- }
1984
- function sendBinary(sock, streamIdStr, payload) {
1985
- try {
1986
- const header = Buffer.alloc(8);
1987
- header.writeBigUInt64BE(BigInt(streamIdStr), 0);
1988
- sock.write(createWsBinaryFrame(Buffer.concat([header, payload])));
1989
- } catch {
861
+ function handleMessage(msg, sock, localPort) {
862
+ switch (msg.type) {
863
+ case "tunnel_ready":
864
+ success(`\uD130\uB110 \uD65C\uC131\uD654!`);
865
+ log(`${colors.green}\u2192${colors.reset} URL: ${colors.cyan}${msg.url}${colors.reset}`);
866
+ log(`${colors.green}\u2192${colors.reset} \uB85C\uCEEC: ${colors.cyan}http://localhost:${localPort}${colors.reset}`);
867
+ if (msg.timeout || msg.max_body) {
868
+ 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}`);
869
+ }
870
+ log(`
871
+ ${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
872
+ `);
873
+ break;
874
+ case "http_request":
875
+ forwardRequest(msg, sock, localPort);
876
+ break;
877
+ case "tunnel_error":
878
+ error(`\uD130\uB110 \uC5D0\uB7EC: ${msg.message}`);
879
+ break;
880
+ case "ping":
881
+ sock.write(createWsTextFrame(JSON.stringify({ type: "pong" })));
882
+ break;
1990
883
  }
1991
884
  }
1992
- function startHTTPStream(sock, streamId, open, localPort) {
1993
- const method = open.method || "GET";
1994
- const reqPath = open.path || "/";
1995
- const query = open.query || "";
1996
- const headers = open.headers || {};
885
+ function forwardRequest(msg, sock, localPort) {
886
+ const requestId = msg.request_id;
887
+ const method = msg.method;
888
+ const reqPath = msg.path;
889
+ const query = msg.query || "";
890
+ const headers = msg.headers || {};
891
+ const bodyBase64 = msg.body;
1997
892
  const fullPath = query ? `${reqPath}?${query}` : reqPath;
1998
893
  const localHeaders = {};
1999
- for (const [k, v] of Object.entries(headers)) {
2000
- if (k.toLowerCase() !== "host") localHeaders[k] = v;
2001
- }
2002
- localHeaders["host"] = `localhost:${localPort}`;
2003
- let started = false;
2004
- let cancelled = false;
2005
- const localReq = http.request(
2006
- { hostname: "127.0.0.1", port: localPort, path: fullPath, method, headers: localHeaders },
2007
- (res) => {
2008
- const respHeaders = {};
2009
- for (const [k, v] of Object.entries(res.headers)) {
2010
- if (v == null) continue;
2011
- respHeaders[k] = Array.isArray(v) ? v.join(", ") : v;
2012
- }
2013
- started = true;
2014
- sendControl(sock, {
2015
- type: "stream_response",
2016
- stream_id: streamId,
2017
- status: res.statusCode || 502,
2018
- headers: respHeaders
2019
- });
2020
- const methodColor = method === "GET" ? colors.green : method === "POST" ? colors.blue : colors.yellow;
2021
- log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode}`);
2022
- res.on("data", (chunk) => {
2023
- if (cancelled) return;
2024
- sendBinary(sock, streamId, chunk);
2025
- });
2026
- res.on("end", () => {
2027
- if (cancelled) return;
2028
- sendControl(sock, { type: "stream_eof", stream_id: streamId, side: "upstream" });
2029
- sendControl(sock, { type: "stream_close", stream_id: streamId });
2030
- streams.delete(streamId);
2031
- });
2032
- res.on("error", (err) => {
2033
- sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
2034
- streams.delete(streamId);
2035
- });
2036
- }
2037
- );
2038
- localReq.on("error", (err) => {
2039
- warn(`\uB85C\uCEEC \uC11C\uBC84 \uC5F0\uACB0 \uC2E4\uD328 (${method} ${reqPath}): ${err.message}`);
2040
- if (!started) {
2041
- sendControl(sock, {
2042
- type: "stream_response",
2043
- stream_id: streamId,
2044
- status: 502,
2045
- headers: { "content-type": "application/json" }
2046
- });
2047
- sendBinary(sock, streamId, Buffer.from(JSON.stringify({ error: `Local server error: ${err.message}` })));
2048
- }
2049
- sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
2050
- streams.delete(streamId);
2051
- });
2052
- const forwarder = {
2053
- kind: "http",
2054
- feedBody: (chunk) => {
2055
- if (!cancelled) localReq.write(chunk);
2056
- },
2057
- endBody: () => {
2058
- if (!cancelled) localReq.end();
2059
- },
2060
- cancel: () => {
2061
- cancelled = true;
2062
- try {
2063
- localReq.destroy();
2064
- } catch {
2065
- }
894
+ for (const [key, value] of Object.entries(headers)) {
895
+ if (key.toLowerCase() !== "host") {
896
+ localHeaders[key] = value;
2066
897
  }
2067
- };
2068
- streams.set(streamId, forwarder);
2069
- }
2070
- function startWSStream(sock, streamId, open, localPort) {
2071
- const reqPath = open.path || "/";
2072
- const query = open.query || "";
2073
- const headers = open.headers || {};
2074
- const fullPath = query ? `${reqPath}?${query}` : reqPath;
2075
- const localHeaders = {};
2076
- for (const [k, v] of Object.entries(headers)) {
2077
- const lk = k.toLowerCase();
2078
- if (lk === "host") continue;
2079
- if (lk === "sec-websocket-extensions") continue;
2080
- localHeaders[k] = v;
2081
898
  }
2082
899
  localHeaders["host"] = `localhost:${localPort}`;
2083
- localHeaders["connection"] = "Upgrade";
2084
- localHeaders["upgrade"] = "websocket";
2085
- let cancelled = false;
2086
- let upstream = null;
2087
- const req = http.request({
900
+ const reqOptions = {
2088
901
  hostname: "127.0.0.1",
2089
902
  port: localPort,
2090
903
  path: fullPath,
2091
- method: "GET",
904
+ method,
2092
905
  headers: localHeaders
2093
- });
2094
- req.on("upgrade", (res, sk) => {
2095
- upstream = sk;
2096
- const respHeaders = {};
2097
- for (const [k, v] of Object.entries(res.headers)) {
2098
- if (v == null) continue;
2099
- respHeaders[k] = Array.isArray(v) ? v.join(", ") : v;
2100
- }
2101
- sendControl(sock, {
2102
- type: "stream_response",
2103
- stream_id: streamId,
2104
- status: res.statusCode || 101,
2105
- headers: respHeaders,
2106
- websocket: true
2107
- });
2108
- log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${colors.cyan}WS${colors.reset} ${reqPath} \u2192 101`);
2109
- const upstreamParser = new UpstreamWsFrameParser({
2110
- onPayload: (payload) => {
2111
- if (cancelled) return;
2112
- sendBinary(sock, streamId, payload);
2113
- },
2114
- onPing: (payload) => {
2115
- if (cancelled || !upstream) return;
2116
- upstream.write(createUpstreamPongFrame(payload));
2117
- },
2118
- onClose: () => {
2119
- if (cancelled) return;
2120
- sendControl(sock, { type: "stream_close", stream_id: streamId });
2121
- streams.delete(streamId);
2122
- },
2123
- onError: (err) => {
2124
- sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_frame_error: ${err.message}` });
2125
- streams.delete(streamId);
906
+ };
907
+ const localReq = http.request(reqOptions, (res) => {
908
+ const chunks = [];
909
+ res.on("data", (chunk) => chunks.push(chunk));
910
+ res.on("end", () => {
911
+ const body = Buffer.concat(chunks);
912
+ const responseHeaders = {};
913
+ for (const [key, value] of Object.entries(res.headers)) {
914
+ if (value) responseHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
2126
915
  }
2127
- });
2128
- sk.on("data", (chunk) => {
2129
- if (cancelled) return;
2130
- upstreamParser.feed(chunk);
2131
- });
2132
- sk.on("close", () => {
2133
- if (cancelled) return;
2134
- sendControl(sock, { type: "stream_close", stream_id: streamId });
2135
- streams.delete(streamId);
2136
- });
2137
- sk.on("error", (err) => {
2138
- sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
2139
- streams.delete(streamId);
2140
- });
2141
- });
2142
- req.on("response", (res) => {
2143
- const respHeaders = {};
2144
- for (const [k, v] of Object.entries(res.headers)) {
2145
- if (v == null) continue;
2146
- respHeaders[k] = Array.isArray(v) ? v.join(", ") : v;
2147
- }
2148
- sendControl(sock, {
2149
- type: "stream_response",
2150
- stream_id: streamId,
2151
- status: res.statusCode || 502,
2152
- headers: respHeaders,
2153
- websocket: false
2154
- });
2155
- sendControl(sock, { type: "stream_close", stream_id: streamId });
2156
- streams.delete(streamId);
2157
- });
2158
- req.on("error", (err) => {
2159
- sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
2160
- streams.delete(streamId);
2161
- });
2162
- req.end();
2163
- const forwarder = {
2164
- kind: "ws",
2165
- // Encode the v2 payload as an upstream WS frame BEFORE writing to
2166
- // the socket. The prior implementation wrote raw payload to the
2167
- // socket which is not a valid WS frame; upstream WS servers (e.g.
2168
- // ComfyUI) would error out. Default to TEXT opcode — the v2
2169
- // protocol does not propagate the original opcode from the client,
2170
- // and most WS APIs (JSON-based) use text. Binary client→upstream
2171
- // is a known limitation pending opcode propagation in v2.
2172
- feedBody: (chunk) => {
2173
- if (cancelled || !upstream) return;
2174
- upstream.write(createUpstreamTextFrame(chunk));
2175
- },
2176
- endBody: () => {
2177
- },
2178
- cancel: () => {
2179
- cancelled = true;
916
+ const response = {
917
+ type: "http_response",
918
+ request_id: requestId,
919
+ status: res.statusCode || 200,
920
+ headers: responseHeaders,
921
+ body: body.length > 0 ? body.toString("base64") : ""
922
+ };
2180
923
  try {
2181
- upstream?.destroy();
924
+ sock.write(createWsTextFrame(JSON.stringify(response)));
925
+ const methodColor = method === "GET" ? colors.green : method === "POST" ? colors.blue : colors.yellow;
926
+ log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode}`);
2182
927
  } catch {
928
+ warn(`\uC751\uB2F5 \uC804\uC1A1 \uC2E4\uD328: ${requestId}`);
2183
929
  }
930
+ });
931
+ });
932
+ localReq.on("error", (err) => {
933
+ const response = {
934
+ type: "http_response",
935
+ request_id: requestId,
936
+ status: 502,
937
+ headers: { "content-type": "application/json" },
938
+ body: Buffer.from(JSON.stringify({ error: `Local server error: ${err.message}` })).toString("base64")
939
+ };
940
+ try {
941
+ sock.write(createWsTextFrame(JSON.stringify(response)));
942
+ } catch {
2184
943
  }
2185
- };
2186
- streams.set(streamId, forwarder);
2187
- }
2188
- async function handleMessage(msg, sock, localPort) {
2189
- switch (msg.type) {
2190
- case "tunnel_ready": {
2191
- const proxyToken = typeof msg.proxy_token === "string" ? msg.proxy_token : "";
2192
- const tunnelUrl = typeof msg.url === "string" ? msg.url : "";
2193
- const isPublic = msg.public === true;
2194
- success(`\uD130\uB110 \uD65C\uC131\uD654!`);
2195
- log(`${colors.green}\u2192${colors.reset} URL: ${colors.cyan}${tunnelUrl}${colors.reset}`);
2196
- log(`${colors.green}\u2192${colors.reset} \uB85C\uCEEC: ${colors.cyan}http://localhost:${localPort}${colors.reset}`);
2197
- if (msg.timeout || msg.max_body) {
2198
- 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}`);
2199
- }
2200
- if (isPublic) {
2201
- log("");
2202
- log(`${colors.yellow}\u26A0 \uACF5\uAC1C \uBAA8\uB4DC (--public):${colors.reset} proxy_token \uAC80\uC99D\uC774 \uBE44\uD65C\uC131\uD654\uB410\uC2B5\uB2C8\uB2E4.`);
2203
- 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}`);
2204
- 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}`);
2205
- log("");
2206
- log(`${colors.dim} $ curl ${tunnelUrl}/${colors.reset}`);
2207
- } else if (tunnelOpts?.showToken && proxyToken && tunnelUrl) {
2208
- log("");
2209
- log(`${colors.green}\u2192${colors.reset} \uD1A0\uD070: ${colors.yellow}${proxyToken}${colors.reset}`);
2210
- log(`
2211
- ${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}`);
2212
- log(`${colors.dim} $ curl -H "X-Proxy-Token: ${proxyToken}" ${tunnelUrl}/${colors.reset}`);
2213
- log(`${colors.dim} $ curl "${tunnelUrl}/?proxy_token=${proxyToken}"${colors.reset}`);
2214
- }
2215
- if (tunnelOpts?.label) {
2216
- const tunnelId = typeof msg.tunnel_id === "string" ? msg.tunnel_id : "";
2217
- if (!tunnelId) {
2218
- error(`tunnel_ready \uBA54\uC2DC\uC9C0\uC5D0 tunnel_id \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 endpoint "${tunnelOpts.label}" \uC790\uB3D9 \uB4F1\uB85D skip`);
2219
- } else {
2220
- await registerEndpointBinding(
2221
- config.baseUrl,
2222
- appId,
2223
- tunnelKey,
2224
- tunnelId,
2225
- tunnelOpts.label,
2226
- tunnelOpts.description
2227
- );
2228
- }
2229
- }
2230
- log(`
2231
- ${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
2232
- `);
2233
- break;
2234
- }
2235
- case "stream_open": {
2236
- const sid = String(msg.stream_id ?? "");
2237
- if (!sid) {
2238
- warn("stream_open with empty stream_id");
2239
- break;
2240
- }
2241
- const kind = msg.kind || "http";
2242
- if (kind === "ws") {
2243
- startWSStream(sock, sid, msg, localPort);
2244
- } else {
2245
- startHTTPStream(sock, sid, msg, localPort);
2246
- }
2247
- break;
2248
- }
2249
- case "stream_eof": {
2250
- const sid = String(msg.stream_id ?? "");
2251
- const side = msg.side;
2252
- if (side === "client") {
2253
- const f = streams.get(sid);
2254
- f?.endBody();
2255
- }
2256
- break;
2257
- }
2258
- case "stream_close": {
2259
- const sid = String(msg.stream_id ?? "");
2260
- const f = streams.get(sid);
2261
- if (f) {
2262
- f.cancel();
2263
- streams.delete(sid);
2264
- }
2265
- break;
2266
- }
2267
- case "tunnel_error": {
2268
- const result = handleTunnelError(msg, appId, localPort);
2269
- error(result.message);
2270
- if (result.action === "exit") {
2271
- shouldReconnect = false;
2272
- setTimeout(() => process.exit(result.exitCode ?? 1), 500);
2273
- }
2274
- break;
2275
- }
2276
- case "ping":
2277
- sock.write(createWsTextFrame(JSON.stringify({ type: "pong" })));
2278
- break;
944
+ warn(`\uB85C\uCEEC \uC11C\uBC84 \uC5F0\uACB0 \uC2E4\uD328 (${method} ${reqPath}): ${err.message}`);
945
+ });
946
+ if (bodyBase64) {
947
+ localReq.write(Buffer.from(bodyBase64, "base64"));
2279
948
  }
949
+ localReq.end();
2280
950
  }
2281
951
  connect();
2282
952
  await new Promise(() => {
@@ -2284,16 +954,14 @@ ${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
2284
954
  }
2285
955
  function showHelp() {
2286
956
  log(`
2287
- ${colors.cyan}connectbase${colors.reset} - Connect Base SDK & CLI
957
+ ${colors.cyan}connectbase-client${colors.reset} - Connect Base SDK & CLI
2288
958
 
2289
959
  ${colors.yellow}\uC0AC\uC6A9\uBC95:${colors.reset}
2290
- npx connectbase <command> [options]
960
+ npx connectbase-client <command> [options]
2291
961
 
2292
962
  ${colors.yellow}\uBA85\uB839\uC5B4:${colors.reset}
2293
- init \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654 (\uC571 \uC0DD\uC131, MCP \uC124\uC815, SDK \uBB38\uC11C \uB2E4\uC6B4\uB85C\uB4DC)
2294
- update SDK \uBC84\uC804 \uCCB4\uD06C + \uBB38\uC11C/MCP \uC77C\uAD04 \uC5C5\uB370\uC774\uD2B8 (\uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8 \uC9C0\uC6D0)
2295
- docs SDK \uBB38\uC11C \uB2E4\uC6B4\uB85C\uB4DC/\uC5C5\uB370\uC774\uD2B8 (\uBAA8\uB178\uB808\uD3EC \uC790\uB3D9 \uAC10\uC9C0)
2296
- mcp MCP \uC11C\uBC84 \uC124\uC815 (.mcp.json \uC0DD\uC131/\uC5C5\uB370\uC774\uD2B8, \uBAA8\uB178\uB808\uD3EC \uC790\uB3D9 \uAC10\uC9C0)
963
+ init \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654 (\uC124\uC815 \uD30C\uC77C \uC0DD\uC131)
964
+ docs SDK \uBB38\uC11C \uB2E4\uC6B4\uB85C\uB4DC/\uC5C5\uB370\uC774\uD2B8 (--monorepo: git root\uC5D0 \uC0DD\uC131)
2297
965
  deploy <directory> \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0\uC5D0 \uD30C\uC77C \uBC30\uD3EC (--dev: Dev \uD658\uACBD)
2298
966
  tunnel <port> \uB85C\uCEEC \uC11C\uBE44\uC2A4\uB97C \uC778\uD130\uB137\uC5D0 \uB178\uCD9C
2299
967
 
@@ -2306,45 +974,27 @@ ${colors.yellow}\uCF54\uB4DC \uBD84\uC11D (MCP \uD1B5\uD574 \uC0AC\uC6A9):${colo
2306
974
 
2307
975
  ${colors.yellow}\uC635\uC158:${colors.reset}
2308
976
  -s, --storage <id> \uC2A4\uD1A0\uB9AC\uC9C0 ID
2309
- -k, --public-key <key> API Key
977
+ -k, --api-key <key> API Key
2310
978
  -u, --base-url <url> \uC11C\uBC84 URL (\uAE30\uBCF8: ${DEFAULT_BASE_URL})
2311
- -t, --timeout <sec> \uD130\uB110 \uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (\uCD08, tunnel \uC804\uC6A9)
979
+ -t, --timeout <sec> \uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (\uCD08, tunnel/deploy \uACF5\uD1B5. deploy \uBBF8\uC9C0\uC815 \uC2DC \uD30C\uC77C \uD06C\uAE30\uC5D0 \uB530\uB77C \uC790\uB3D9 \uC0B0\uC815)
2312
980
  --max-body <MB> \uD130\uB110 \uCD5C\uB300 \uBC14\uB514 \uD06C\uAE30 (MB, tunnel \uC804\uC6A9)
2313
- --force \uD130\uB110 lockfile \uBB34\uC2DC (\uC911\uBCF5 \uC2E4\uD589 \uAC15\uC81C, tunnel \uC804\uC6A9)
2314
- --public proxy_token \uAC80\uC99D \uBE44\uD65C\uC131\uD654 \u2014 \uC6F9\uD6C5/\uC678\uBD80 \uC9C1\uC811 \uD638\uCD9C\uC6A9 (tunnel \uC804\uC6A9)
2315
- --show-token proxy_token \uACFC curl \uC608\uC2DC\uB97C \uCD9C\uB825 (--public \uC544\uB2CC \uACBD\uC6B0)
2316
- --label <name> Endpoint binding \uC790\uB3D9 \uB4F1\uB85D (tunnel \uC804\uC6A9) \u2014 SDK \uC758 cb.endpoint.call(label) \uD638\uCD9C \uAC00\uB2A5
2317
- --description <text> Endpoint binding \uC124\uBA85 (--label \uB3D9\uBC18 \uC2DC\uB9CC)
2318
981
  -d, --dev Dev \uD658\uACBD\uC5D0 \uBC30\uD3EC (deploy \uC804\uC6A9)
2319
- --check \uBC84\uC804\uB9CC \uD655\uC778 (update \uC804\uC6A9)
2320
- --skip-docs \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8 \uAC74\uB108\uB6F0\uAE30 (update \uC804\uC6A9)
2321
- --skip-mcp MCP \uC5C5\uB370\uC774\uD2B8 \uAC74\uB108\uB6F0\uAE30 (update \uC804\uC6A9)
2322
- --setup-root \uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 (update \uC804\uC6A9)
2323
- (docs, mcp, update\uB294 \uBAA8\uB178\uB808\uD3EC\uB97C \uC790\uB3D9 \uAC10\uC9C0\uD558\uC5EC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 \uC0DD\uC131)
982
+ --monorepo \uBAA8\uB178\uB808\uD3EC git root\uC5D0 \uBB38\uC11C \uC0DD\uC131 (docs \uC804\uC6A9)
2324
983
  -h, --help \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
2325
984
  -v, --version \uBC84\uC804 \uD45C\uC2DC
2326
985
 
2327
986
  ${colors.yellow}\uBE60\uB978 \uC2DC\uC791:${colors.reset}
2328
987
  ${colors.dim}# 1. \uCD08\uAE30\uD654 (\uCD5C\uCD08 1\uD68C)${colors.reset}
2329
- npx connectbase init
988
+ npx connectbase-client init
2330
989
 
2331
- ${colors.dim}# 2. SDK \uC804\uCCB4 \uC5C5\uB370\uC774\uD2B8 (\uBC84\uC804 \uCCB4\uD06C + \uBB38\uC11C + MCP)${colors.reset}
2332
- npx connectbase update
2333
-
2334
- ${colors.dim}# 2-1. \uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8 \uC124\uCE58${colors.reset}
2335
- npx connectbase update --setup-root
2336
-
2337
- ${colors.dim}# 3. SDK \uBB38\uC11C\uB9CC \uC5C5\uB370\uC774\uD2B8${colors.reset}
990
+ ${colors.dim}# 2. SDK \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8${colors.reset}
2338
991
  npx connectbase docs
2339
992
 
2340
- ${colors.dim}# 4. MCP \uC11C\uBC84 \uC124\uC815${colors.reset}
2341
- npx connectbase mcp
2342
-
2343
- ${colors.dim}# 4. Prod \uBC30\uD3EC${colors.reset}
993
+ ${colors.dim}# 3. Prod \uBC30\uD3EC${colors.reset}
2344
994
  npm run deploy
2345
995
 
2346
996
  ${colors.dim}# 4. Dev \uD658\uACBD \uBC30\uD3EC (\uB0B4\uBD80 QA\uC6A9)${colors.reset}
2347
- npx connectbase deploy ./dist --dev
997
+ npx connectbase-client deploy ./dist --dev
2348
998
 
2349
999
  ${colors.dim}# 5. \uD130\uB110 (\uAE30\uBCF8)${colors.reset}
2350
1000
  npx connectbase tunnel 3000
@@ -2353,16 +1003,13 @@ ${colors.yellow}\uBE60\uB978 \uC2DC\uC791:${colors.reset}
2353
1003
  npx connectbase tunnel 7860 --timeout 300 --max-body 50
2354
1004
 
2355
1005
  ${colors.yellow}\uD658\uACBD\uBCC0\uC218:${colors.reset}
2356
- CONNECTBASE_PUBLIC_KEY Public Key (cb_pk_*)
2357
- CONNECTBASE_SECRET_KEY Secret Key (cb_sk_*, \uD130\uB110\uC6A9)
2358
- CONNECTBASE_PUBLIC_KEY (deprecated) \uB808\uAC70\uC2DC \u2014 publicKey \uB610\uB294 secretKey \uB85C \uC0AC\uC6A9
1006
+ CONNECTBASE_API_KEY API Key
2359
1007
  CONNECTBASE_STORAGE_ID \uC2A4\uD1A0\uB9AC\uC9C0 ID
2360
1008
  CONNECTBASE_BASE_URL \uC11C\uBC84 URL
2361
1009
 
2362
1010
  ${colors.yellow}\uC124\uC815 \uD30C\uC77C (.connectbaserc):${colors.reset}
2363
1011
  {
2364
- "publicKey": "cb_pk_...",
2365
- "secretKey": "cb_sk_...",
1012
+ "apiKey": "your-api-key",
2366
1013
  "storageId": "your-storage-id",
2367
1014
  "deployDir": "./dist"
2368
1015
  }
@@ -2378,38 +1025,18 @@ function parseArgs(args) {
2378
1025
  const arg = args[i];
2379
1026
  if (arg === "-s" || arg === "--storage") {
2380
1027
  result.options.storageId = args[++i];
2381
- } else if (arg === "-k" || arg === "--public-key" || arg === "--public-key") {
2382
- result.options.publicKey = args[++i];
2383
- } else if (arg === "--secret-key") {
2384
- result.options.secretKey = args[++i];
1028
+ } else if (arg === "-k" || arg === "--api-key") {
1029
+ result.options.apiKey = args[++i];
2385
1030
  } else if (arg === "-u" || arg === "--base-url") {
2386
1031
  result.options.baseUrl = args[++i];
2387
1032
  } else if (arg === "-t" || arg === "--timeout") {
2388
1033
  result.options.timeout = args[++i];
2389
1034
  } else if (arg === "--max-body") {
2390
1035
  result.options.maxBody = args[++i];
2391
- } else if (arg === "--label") {
2392
- result.options.label = args[++i];
2393
- } else if (arg === "--description") {
2394
- result.options.description = args[++i];
2395
- } else if (arg === "-a" || arg === "--app") {
2396
- result.options.appId = args[++i];
2397
- } else if (arg === "--force") {
2398
- result.options.force = "true";
2399
- } else if (arg === "--public") {
2400
- result.options.public = "true";
2401
- } else if (arg === "--show-token") {
2402
- result.options.showToken = "true";
2403
1036
  } else if (arg === "-d" || arg === "--dev") {
2404
1037
  result.options.dev = "true";
2405
- } else if (arg === "--check") {
2406
- result.options.check = "true";
2407
- } else if (arg === "--skip-docs") {
2408
- result.options.skipDocs = "true";
2409
- } else if (arg === "--skip-mcp") {
2410
- result.options.skipMcp = "true";
2411
- } else if (arg === "--setup-root") {
2412
- result.options.setupRoot = "true";
1038
+ } else if (arg === "--monorepo") {
1039
+ result.options.monorepo = "true";
2413
1040
  } else if (arg === "-h" || arg === "--help") {
2414
1041
  result.options.help = "true";
2415
1042
  } else if (arg === "-v" || arg === "--version") {
@@ -2428,7 +1055,7 @@ function parseArgs(args) {
2428
1055
  async function main() {
2429
1056
  const parsed = parseArgs(process.argv.slice(2));
2430
1057
  if (parsed.options.version) {
2431
- log(`connectbase v${VERSION}`);
1058
+ log(`connectbase-client v${VERSION}`);
2432
1059
  return;
2433
1060
  }
2434
1061
  if (parsed.options.help || !parsed.command) {
@@ -2437,40 +1064,44 @@ async function main() {
2437
1064
  }
2438
1065
  const fileConfig = loadConfig();
2439
1066
  const config = {
2440
- publicKey: parsed.options.publicKey || fileConfig.publicKey,
2441
- secretKey: parsed.options.secretKey || fileConfig.secretKey,
1067
+ apiKey: parsed.options.apiKey || fileConfig.apiKey,
2442
1068
  storageId: parsed.options.storageId || fileConfig.storageId,
2443
1069
  baseUrl: parsed.options.baseUrl || fileConfig.baseUrl || DEFAULT_BASE_URL
2444
1070
  };
2445
1071
  if (parsed.command === "init") {
2446
1072
  await init();
2447
- } else if (parsed.command === "update") {
2448
- await update(config, {
2449
- checkOnly: parsed.options.check === "true",
2450
- skipDocs: parsed.options.skipDocs === "true",
2451
- skipMcp: parsed.options.skipMcp === "true",
2452
- setupRoot: parsed.options.setupRoot === "true"
2453
- });
2454
1073
  } else if (parsed.command === "docs") {
2455
- await downloadDocs(config.publicKey || "");
2456
- } else if (parsed.command === "mcp") {
2457
- await setupMcp();
1074
+ let docsApiKey = config.apiKey;
1075
+ if (!docsApiKey) {
1076
+ docsApiKey = await prompt(`${colors.blue}?${colors.reset} API Key: `);
1077
+ if (!docsApiKey) {
1078
+ error("API Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
1079
+ process.exit(1);
1080
+ }
1081
+ }
1082
+ await downloadDocs(docsApiKey, void 0, parsed.options.monorepo === "true");
2458
1083
  } else if (parsed.command === "deploy") {
2459
1084
  const directory = parsed.args[0] || fileConfig.deployDir || ".";
2460
- if (!config.publicKey) {
2461
- 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');
1085
+ if (!config.apiKey) {
1086
+ error('API Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase-client init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694');
2462
1087
  process.exit(1);
2463
1088
  }
2464
1089
  if (!config.storageId) {
2465
- 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');
1090
+ error('\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase-client init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694');
2466
1091
  process.exit(1);
2467
1092
  }
2468
1093
  const isDev = parsed.options.dev === "true";
2469
- await deploy(directory, config, isDev);
1094
+ const deployOpts = {};
1095
+ if (parsed.options.timeout) {
1096
+ const t = parseInt(parsed.options.timeout, 10);
1097
+ if (!isNaN(t) && t > 0) deployOpts.timeoutMs = t * 1e3;
1098
+ else warn(`--timeout \uAC12\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544 \uBB34\uC2DC\uB429\uB2C8\uB2E4: ${parsed.options.timeout}`);
1099
+ }
1100
+ await deploy(directory, config, isDev, deployOpts);
2470
1101
  } else if (parsed.command === "tunnel") {
2471
1102
  const portStr = parsed.args[0];
2472
1103
  if (!portStr) {
2473
- error("\uD3EC\uD2B8 \uBC88\uD638\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC608: npx connectbase tunnel 8084");
1104
+ error("\uD3EC\uD2B8 \uBC88\uD638\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC608: npx connectbase-client tunnel 8084");
2474
1105
  process.exit(1);
2475
1106
  }
2476
1107
  const port = parseInt(portStr, 10);
@@ -2487,24 +1118,6 @@ async function main() {
2487
1118
  const m = parseInt(parsed.options.maxBody, 10);
2488
1119
  if (!isNaN(m) && m > 0) tunnelOpts.maxBody = m;
2489
1120
  }
2490
- if (parsed.options.appId) {
2491
- tunnelOpts.appId = parsed.options.appId;
2492
- }
2493
- if (parsed.options.force === "true") {
2494
- tunnelOpts.force = true;
2495
- }
2496
- if (parsed.options.public === "true") {
2497
- tunnelOpts.public = true;
2498
- }
2499
- if (parsed.options.showToken === "true") {
2500
- tunnelOpts.showToken = true;
2501
- }
2502
- if (parsed.options.label) {
2503
- tunnelOpts.label = parsed.options.label;
2504
- }
2505
- if (parsed.options.description) {
2506
- tunnelOpts.description = parsed.options.description;
2507
- }
2508
1121
  await startTunnel(port, config, tunnelOpts);
2509
1122
  } else {
2510
1123
  error(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839\uC5B4: ${parsed.command}`);
@@ -2516,11 +1129,3 @@ main().catch((err) => {
2516
1129
  error(err.message);
2517
1130
  process.exit(1);
2518
1131
  });
2519
- // Annotate the CommonJS export names for ESM import in node:
2520
- 0 && (module.exports = {
2521
- computeDeployDiff,
2522
- normalizeRelativePath,
2523
- parseArgs,
2524
- registerEndpointBinding,
2525
- sha256Hex
2526
- });