connectbase-client 0.10.3 → 0.10.4

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
@@ -369,7 +369,7 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
369
369
  return;
370
370
  }
371
371
  }
372
- log(`${colors.dim}Secret Key (cb_sk_): \uCF58\uC194 > \uD504\uB85C\uD544 > MCP Key \uD0ED\uC5D0\uC11C \uBC1C\uAE09${colors.reset}
372
+ log(`${colors.dim}Secret Key (cb_sk_): \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uBC1C\uAE09${colors.reset}
373
373
  `);
374
374
  const secretKey = await promptSecret(`${colors.blue}?${colors.reset} Secret Key: `);
375
375
  if (!secretKey) {
@@ -378,7 +378,7 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
378
378
  }
379
379
  if (secretKey.startsWith("cb_pk_")) {
380
380
  error("Public Key\uAC00 \uC544\uB2CC Secret Key(cb_sk_)\uB97C \uC785\uB825\uD558\uC138\uC694");
381
- info("Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > MCP Key \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
381
+ info("Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
382
382
  process.exit(1);
383
383
  }
384
384
  if (!secretKey.startsWith("cb_sk_")) {
@@ -712,21 +712,12 @@ function updateRootClaudeMd(claudeMdPath) {
712
712
  ## ConnectBase SDK
713
713
 
714
714
  \uC774 \uD504\uB85C\uC81D\uD2B8\uB294 **ConnectBase**\uB97C \uBC31\uC5D4\uB4DC\uB85C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.
715
- \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.
716
715
 
717
716
  ### AI \uD544\uB3C5 \u2014 \uCF54\uB529 \uC804 \uBC18\uB4DC\uC2DC \uC218\uD589
718
717
 
719
- 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)
720
- 2. \uC0AC\uC6A9\uC790 \uC694\uCCAD\uC5D0 \uB9DE\uB294 \uAE30\uB2A5\uC744 \uB9E4\uD551\uD45C\uC5D0\uC11C \uCC3E\uC73C\uC138\uC694
721
- 3. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
722
- 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)
723
-
724
- ### \uBE44\uAC1C\uBC1C\uC790 \uC751\uB300 \uC6D0\uCE59
725
-
726
- - \uC804\uBB38 \uC6A9\uC5B4 \uB300\uC2E0 \uC26C\uC6B4 \uB9D0 \uC0AC\uC6A9 ("API" \u2192 "\uAE30\uB2A5", "DB" \u2192 "\uC800\uC7A5\uC18C")
727
- - \uC5D0\uB7EC \uBC1C\uC0DD \uC2DC \uC6D0\uC778\uACFC \uD574\uACB0\uCC45\uC744 \uD55C \uC904\uB85C \uC124\uBA85
728
- - \uCF54\uB4DC \uC791\uC131 \uC804 "\uC774\uB807\uAC8C \uB9CC\uB4E4\uAC8C\uC694~" \uD55C \uC904 \uC694\uC57D \uBA3C\uC800
729
- - \uBAA8\uB974\uB294 \uAC8C \uC788\uC73C\uBA74 \uB9CC\uB4E4\uAE30 \uC804\uC5D0 \uBA3C\uC800 \uC9C8\uBB38
718
+ 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)
719
+ 2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
720
+ 3. \uAC80\uC0C9 \uACB0\uACFC\uC758 \uCF54\uB4DC \uD328\uD134\uC744 \uB530\uB77C \uAD6C\uD604\uD558\uC138\uC694
730
721
  ${endMarker}`;
731
722
  if (fs.existsSync(claudeMdPath)) {
732
723
  let content = fs.readFileSync(claudeMdPath, "utf-8");
@@ -735,7 +726,7 @@ ${endMarker}`;
735
726
  if (startIdx !== -1 && endIdx !== -1) {
736
727
  content = content.substring(0, startIdx) + sdkBlock + content.substring(endIdx + endMarker.length);
737
728
  } else {
738
- content = content.trimEnd() + "\n\n" + sdkBlock + "\n";
729
+ content = sdkBlock + "\n\n" + content.trimStart();
739
730
  }
740
731
  fs.writeFileSync(claudeMdPath, content);
741
732
  info("CLAUDE.md\uC5D0 ConnectBase \uCC38\uC870 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
@@ -949,15 +940,89 @@ function getTunnelServerUrl(baseUrl) {
949
940
  }
950
941
  return baseUrl.replace(/:\d+/, ":8090");
951
942
  }
943
+ async function resolveAppForTunnel(apiKey, baseUrl, appIdOption) {
944
+ if (appIdOption) {
945
+ return appIdOption;
946
+ }
947
+ let apps = [];
948
+ try {
949
+ info("\uC571 \uBAA9\uB85D \uC870\uD68C \uC911...");
950
+ const appsRes = await makeRequest(
951
+ `${baseUrl}/v1/public/cli/apps`,
952
+ "GET",
953
+ { "X-API-Key": apiKey }
954
+ );
955
+ if (appsRes.status === 401) {
956
+ error("Secret Key\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uCF58\uC194\uC5D0\uC11C \uD0A4\uB97C \uD655\uC778\uD558\uC138\uC694");
957
+ process.exit(1);
958
+ }
959
+ if (appsRes.status !== 200) {
960
+ throw new Error(`HTTP ${appsRes.status}`);
961
+ }
962
+ const appsData = appsRes.data;
963
+ apps = appsData.apps || [];
964
+ } catch (err) {
965
+ error(`\uC571 \uBAA9\uB85D \uC870\uD68C \uC2E4\uD328: ${err instanceof Error ? err.message : err}`);
966
+ process.exit(1);
967
+ }
968
+ if (apps.length === 1) {
969
+ success(`\uC571 \uC790\uB3D9 \uC120\uD0DD: ${apps[0].name}`);
970
+ return apps[0].id;
971
+ }
972
+ if (apps.length > 0) {
973
+ log(`
974
+ ${colors.dim}\uB0B4 \uC571 \uBAA9\uB85D:${colors.reset}`);
975
+ apps.forEach((a, i) => {
976
+ const date = a.created_at ? a.created_at.substring(0, 10) : "";
977
+ log(` ${colors.cyan}${i + 1}${colors.reset}) ${a.name} ${colors.dim}(${date})${colors.reset}`);
978
+ });
979
+ }
980
+ log(` ${colors.cyan}0${colors.reset}) \uC0C8 \uC571 \uB9CC\uB4E4\uAE30`);
981
+ const choice = await prompt(`
982
+ ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
983
+ const num = parseInt(choice, 10);
984
+ if (num > 0 && num <= apps.length) {
985
+ success(`\uC120\uD0DD\uB428: ${apps[num - 1].name}`);
986
+ return apps[num - 1].id;
987
+ }
988
+ const projectName = path.basename(process.cwd());
989
+ const appName = await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
990
+ info("\uC571 \uC0DD\uC131 \uC911...");
991
+ const createRes = await makeRequest(
992
+ `${baseUrl}/v1/public/cli/apps`,
993
+ "POST",
994
+ { "X-API-Key": apiKey },
995
+ JSON.stringify({ name: appName })
996
+ );
997
+ if (createRes.status === 402) {
998
+ error("\uC571 \uC0DD\uC131 \uD55C\uB3C4 \uCD08\uACFC. \uD50C\uB79C \uC5C5\uADF8\uB808\uC774\uB4DC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4");
999
+ process.exit(1);
1000
+ }
1001
+ if (createRes.status !== 201) {
1002
+ const data = createRes.data;
1003
+ error(`\uC571 \uC0DD\uC131 \uC2E4\uD328: ${data?.error || `HTTP ${createRes.status}`}`);
1004
+ process.exit(1);
1005
+ }
1006
+ const createData = createRes.data;
1007
+ success(`\uC571 \uC0DD\uC131 \uC644\uB8CC: ${createData.app_name}`);
1008
+ return createData.app_id;
1009
+ }
952
1010
  async function startTunnel(port, config, tunnelOpts) {
953
1011
  if (!config.apiKey) {
954
- 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");
1012
+ error("Secret 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");
1013
+ info("Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
955
1014
  process.exit(1);
956
1015
  }
1016
+ if (!config.apiKey.startsWith("cb_sk_")) {
1017
+ error("\uD130\uB110\uC740 \uC720\uC800 Secret Key(cb_sk_)\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_)\uB294 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
1018
+ info("Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
1019
+ process.exit(1);
1020
+ }
1021
+ const appId = await resolveAppForTunnel(config.apiKey, config.baseUrl, tunnelOpts?.appId);
957
1022
  const tunnelServerUrl = getTunnelServerUrl(config.baseUrl);
958
1023
  const parsedUrl = new URL(tunnelServerUrl);
959
1024
  const isHttps = parsedUrl.protocol === "https:";
960
- let wsPath = `/v1/tunnel/connect?api_key=${encodeURIComponent(config.apiKey)}`;
1025
+ let wsPath = `/v1/tunnel/connect?app_id=${encodeURIComponent(appId)}`;
961
1026
  if (tunnelOpts?.timeout) {
962
1027
  wsPath += `&timeout=${tunnelOpts.timeout}`;
963
1028
  }
@@ -1003,7 +1068,8 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
1003
1068
  "Upgrade": "websocket",
1004
1069
  "Connection": "Upgrade",
1005
1070
  "Sec-WebSocket-Key": wsKey,
1006
- "Sec-WebSocket-Version": "13"
1071
+ "Sec-WebSocket-Version": "13",
1072
+ "Authorization": `Bearer ${config.apiKey}`
1007
1073
  }
1008
1074
  };
1009
1075
  const req = lib.request(reqOptions);
@@ -1129,29 +1195,78 @@ ${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
1129
1195
  headers: localHeaders
1130
1196
  };
1131
1197
  const localReq = http.request(reqOptions, (res) => {
1132
- const chunks = [];
1133
- res.on("data", (chunk) => chunks.push(chunk));
1134
- res.on("end", () => {
1135
- const body = Buffer.concat(chunks);
1136
- const responseHeaders = {};
1137
- for (const [key, value] of Object.entries(res.headers)) {
1138
- if (value) responseHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
1139
- }
1140
- const response = {
1141
- type: "http_response",
1142
- request_id: requestId,
1143
- status: res.statusCode || 200,
1144
- headers: responseHeaders,
1145
- body: body.length > 0 ? body.toString("base64") : ""
1146
- };
1198
+ const responseHeaders = {};
1199
+ for (const [key, value] of Object.entries(res.headers)) {
1200
+ if (value) responseHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
1201
+ }
1202
+ const contentType = (responseHeaders["content-type"] || "").toLowerCase();
1203
+ const transferEncoding = (responseHeaders["transfer-encoding"] || "").toLowerCase();
1204
+ const isStreaming = contentType.includes("text/event-stream") || transferEncoding.includes("chunked");
1205
+ if (isStreaming) {
1147
1206
  try {
1148
- sock.write(createWsTextFrame(JSON.stringify(response)));
1149
- const methodColor = method === "GET" ? colors.green : method === "POST" ? colors.blue : colors.yellow;
1150
- log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode}`);
1207
+ sock.write(createWsTextFrame(JSON.stringify({
1208
+ type: "http_response_start",
1209
+ request_id: requestId,
1210
+ status: res.statusCode || 200,
1211
+ headers: responseHeaders
1212
+ })));
1151
1213
  } catch {
1152
- warn(`\uC751\uB2F5 \uC804\uC1A1 \uC2E4\uD328: ${requestId}`);
1214
+ warn(`\uC2A4\uD2B8\uB9AC\uBC0D \uC2DC\uC791 \uC804\uC1A1 \uC2E4\uD328: ${requestId}`);
1215
+ return;
1153
1216
  }
1154
- });
1217
+ const methodColor = method === "GET" ? colors.green : method === "POST" ? colors.blue : colors.yellow;
1218
+ log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode} ${colors.cyan}[stream]${colors.reset}`);
1219
+ res.on("data", (chunk) => {
1220
+ try {
1221
+ sock.write(createWsTextFrame(JSON.stringify({
1222
+ type: "http_response_chunk",
1223
+ request_id: requestId,
1224
+ data: chunk.toString("base64")
1225
+ })));
1226
+ } catch {
1227
+ warn(`\uC2A4\uD2B8\uB9AC\uBC0D \uCCAD\uD06C \uC804\uC1A1 \uC2E4\uD328: ${requestId}`);
1228
+ }
1229
+ });
1230
+ res.on("end", () => {
1231
+ try {
1232
+ sock.write(createWsTextFrame(JSON.stringify({
1233
+ type: "http_response_end",
1234
+ request_id: requestId
1235
+ })));
1236
+ } catch {
1237
+ }
1238
+ });
1239
+ res.on("error", (err) => {
1240
+ try {
1241
+ sock.write(createWsTextFrame(JSON.stringify({
1242
+ type: "http_response_error",
1243
+ request_id: requestId,
1244
+ error: err.message
1245
+ })));
1246
+ } catch {
1247
+ }
1248
+ });
1249
+ } else {
1250
+ const chunks = [];
1251
+ res.on("data", (chunk) => chunks.push(chunk));
1252
+ res.on("end", () => {
1253
+ const body = Buffer.concat(chunks);
1254
+ const response = {
1255
+ type: "http_response",
1256
+ request_id: requestId,
1257
+ status: res.statusCode || 200,
1258
+ headers: responseHeaders,
1259
+ body: body.length > 0 ? body.toString("base64") : ""
1260
+ };
1261
+ try {
1262
+ sock.write(createWsTextFrame(JSON.stringify(response)));
1263
+ const methodColor = method === "GET" ? colors.green : method === "POST" ? colors.blue : colors.yellow;
1264
+ log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode}`);
1265
+ } catch {
1266
+ warn(`\uC751\uB2F5 \uC804\uC1A1 \uC2E4\uD328: ${requestId}`);
1267
+ }
1268
+ });
1269
+ }
1155
1270
  });
1156
1271
  localReq.on("error", (err) => {
1157
1272
  const response = {
@@ -1261,6 +1376,8 @@ function parseArgs(args) {
1261
1376
  result.options.timeout = args[++i];
1262
1377
  } else if (arg === "--max-body") {
1263
1378
  result.options.maxBody = args[++i];
1379
+ } else if (arg === "-a" || arg === "--app") {
1380
+ result.options.appId = args[++i];
1264
1381
  } else if (arg === "-d" || arg === "--dev") {
1265
1382
  result.options.dev = "true";
1266
1383
  } else if (arg === "-h" || arg === "--help") {
@@ -1340,6 +1457,9 @@ async function main() {
1340
1457
  const m = parseInt(parsed.options.maxBody, 10);
1341
1458
  if (!isNaN(m) && m > 0) tunnelOpts.maxBody = m;
1342
1459
  }
1460
+ if (parsed.options.appId) {
1461
+ tunnelOpts.appId = parsed.options.appId;
1462
+ }
1343
1463
  await startTunnel(port, config, tunnelOpts);
1344
1464
  } else {
1345
1465
  error(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839\uC5B4: ${parsed.command}`);