connectbase-client 3.43.0 → 3.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/cli.js +367 -172
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,38 @@
3
3
  본 SDK 의 모든 주요 변경사항을 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) 형식으로 기록합니다.
4
4
  버전은 [Semantic Versioning](https://semver.org/lang/ko/) 을 따릅니다.
5
5
 
6
+ ## [3.44.0] - 2026-07-03
7
+
8
+ ### Added — CLI 비대화형 init + SDK 문서 수명주기 정비
9
+
10
+ AI 에이전트(Claude Code 등)가 `init` 을 직접 실행할 수 있도록 비대화형 모드를 추가하고,
11
+ `.claude/docs/` 문서의 신선도/정리 체계를 도입했다.
12
+
13
+ - **비대화형 init**: `connectbase init --yes --secret-key cb_sk_...` — 프롬프트 없이 진행.
14
+ 비TTY 환경에서 `--yes` 없이 실행하면 hang 대신 필요한 플래그를 안내하고 즉시 종료.
15
+ `--app <앱ID>`/`--storage <스토리지ID>`/`--deploy-dir <dir>` 로 명시 지정, `--yes` 는
16
+ 프로젝트 이름과 같은 앱/스토리지가 있으면 재사용(반복 실행에 멱등). Secret Key 는
17
+ `CONNECTBASE_SECRET_KEY` 환경변수로도 전달 가능.
18
+ - **Public Key 스프롤 방지**: `.connectbaserc` 에 `appId` 를 저장하고, 같은 앱을 다시 init
19
+ 하면 기존 Public Key 를 재사용 (이전에는 실행마다 새 키 발급).
20
+ - **문서 생성 스탬프 + 매니페스트**: `.claude/docs/` 파일에 생성 CLI 버전/날짜 스탬프를 넣고
21
+ `.connectbase-manifest.json` 으로 추적 — 콘텐츠 해시로 신선도 비교, 서버에서 사라진 문서
22
+ 자동 정리(레거시 fullstack 파일 포함), `deploy` 성공 후 변경이 있을 때만 조용히 자동 갱신
23
+ (`--skip-docs` 로 생략 가능).
24
+ - **CLAUDE.md @import**: 참조 블록이 `@.claude/docs/project-rules.md` import 로 규칙을 매
25
+ 세션 자동 로드. MCP 미연결 시 `npx connectbase mcp` 안내 지시 포함. `mcp` 명령도 비대화형
26
+ 지원(`--secret-key`/env/.connectbaserc 자동 사용).
27
+
28
+ ### Changed / Security
29
+
30
+ - **키를 쿼리스트링에 싣지 않음**: 문서 다운로드가 `public_key` 쿼리 파라미터를 더 이상
31
+ 전송하지 않는다 (서버 측 키 치환은 플레이스홀더 불일치로 오래전부터 무동작 — 액세스 로그에
32
+ 키가 남는 위험만 있었음). `update` 의 secretKey fallback 도 제거.
33
+ - 모든 CLI 요청에 `User-Agent: connectbase-cli/<version>` 명시.
34
+ - `detectBuildDir`: Next.js 는 `output: 'export'` 미설정 시 경고 안내.
35
+ - 버전 비교가 prerelease(`-beta.1`) 형식에서 NaN 이 되던 문제 수정.
36
+ - `.gitignore` 항목 추가가 부분 문자열 오탐으로 건너뛰던 문제 수정 (라인 단위 정확 매칭).
37
+
6
38
  ## [3.43.0] - 2026-07-02
7
39
 
8
40
  ### Added — `--dev` 배포도 대용량 바이너리 지원
package/dist/cli.js CHANGED
@@ -525,6 +525,8 @@ async function makeRequest(url, method, headers, body, reqOpts = {}) {
525
525
  family: 4,
526
526
  autoSelectFamily: false,
527
527
  headers: {
528
+ // 명시적 UA: 관측성 + WAF 봇 휴리스틱(비브라우저 UA 차단 규칙)과의 충돌 방지
529
+ "User-Agent": `connectbase-cli/${VERSION}`,
528
530
  ...headers,
529
531
  "Content-Type": "application/json",
530
532
  ...bodyBuffer ? { "Content-Length": bodyBuffer.length } : {}
@@ -905,7 +907,7 @@ ${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC13
905
907
  error("\uC778\uC99D \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
906
908
  process.exit(1);
907
909
  }
908
- async function init() {
910
+ async function init(config = {}, opts = {}) {
909
911
  log(`
910
912
  ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.reset}
911
913
  `);
@@ -916,49 +918,104 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
916
918
  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
917
919
  `);
918
920
  }
921
+ const nonInteractive = opts.yes === true;
922
+ if (!process.stdin.isTTY && !nonInteractive) {
923
+ error("\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC785\uB2C8\uB2E4. --yes \uD50C\uB798\uADF8\uC640 \uD568\uAED8 \uD544\uC694\uD55C \uAC12\uC744 \uC9C0\uC815\uD558\uC138\uC694");
924
+ log(`${colors.dim}\uC608: npx connectbase init --yes --secret-key cb_sk_... [--app <\uC571ID>] [--storage <\uC2A4\uD1A0\uB9AC\uC9C0ID>] [--deploy-dir ./dist]${colors.reset}`);
925
+ log(`${colors.dim}Secret Key\uB294 CONNECTBASE_SECRET_KEY \uD658\uACBD\uBCC0\uC218\uB85C\uB3C4 \uC804\uB2EC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4${colors.reset}`);
926
+ process.exit(1);
927
+ }
919
928
  const rcPath = path2.join(cwd, ".connectbaserc");
929
+ let existingRc = {};
920
930
  if (fs2.existsSync(rcPath)) {
921
- 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): `);
922
- if (overwrite.toLowerCase() !== "y") {
923
- info("\uCD08\uAE30\uD654\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
924
- return;
931
+ try {
932
+ existingRc = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
933
+ } catch {
934
+ }
935
+ if (nonInteractive) {
936
+ if (!opts.force) {
937
+ error(".connectbaserc\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force\uB97C \uD568\uAED8 \uC0AC\uC6A9\uD558\uC138\uC694");
938
+ process.exit(1);
939
+ }
940
+ } else {
941
+ 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): `);
942
+ if (overwrite.toLowerCase() !== "y") {
943
+ info("\uCD08\uAE30\uD654\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
944
+ return;
945
+ }
925
946
  }
926
947
  }
927
- log(`
928
- ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
929
- log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
930
- log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
931
- `);
932
- const authChoice = await prompt(`${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `);
933
- let secretKey = "";
934
- if (authChoice === "2") {
935
- try {
936
- secretKey = await browserAuthFlow();
937
- } catch (err) {
938
- error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
939
- 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");
948
+ let secretKey = config.secretKey || "";
949
+ if (secretKey) {
950
+ if (!secretKey.startsWith("cb_sk_")) {
951
+ error("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uD0A4 \uD615\uC2DD\uC785\uB2C8\uB2E4. cb_sk_\uB85C \uC2DC\uC791\uD558\uB294 Secret Key\uB97C \uC9C0\uC815\uD558\uC138\uC694");
940
952
  process.exit(1);
941
953
  }
954
+ info("Secret Key \uD655\uC778\uB428 (\uD50C\uB798\uADF8/\uD658\uACBD\uBCC0\uC218/.connectbaserc)");
955
+ } else if (nonInteractive) {
956
+ error("Secret Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: --secret-key cb_sk_... \uB610\uB294 CONNECTBASE_SECRET_KEY \uD658\uACBD\uBCC0\uC218");
957
+ process.exit(1);
942
958
  } else {
943
- log(`${colors.dim}Secret Key (cb_sk_): \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uBC1C\uAE09${colors.reset}
959
+ log(`
960
+ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
961
+ log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
962
+ log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
944
963
  `);
945
- secretKey = await promptSecret(`${colors.blue}?${colors.reset} Secret Key: `);
946
- if (!secretKey) {
947
- error("Secret Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
948
- process.exit(1);
949
- }
950
- if (secretKey.startsWith("cb_pk_")) {
951
- error("Public Key\uAC00 \uC544\uB2CC Secret Key(cb_sk_)\uB97C \uC785\uB825\uD558\uC138\uC694");
952
- info("Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
953
- process.exit(1);
954
- }
955
- if (!secretKey.startsWith("cb_sk_")) {
956
- 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");
957
- process.exit(1);
964
+ const authChoice = await prompt(`${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `);
965
+ if (authChoice === "2") {
966
+ try {
967
+ secretKey = await browserAuthFlow();
968
+ } catch (err) {
969
+ error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
970
+ 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");
971
+ process.exit(1);
972
+ }
973
+ } else {
974
+ log(`${colors.dim}Secret Key (cb_sk_): \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uBC1C\uAE09${colors.reset}
975
+ `);
976
+ secretKey = await promptSecret(`${colors.blue}?${colors.reset} Secret Key: `);
977
+ if (!secretKey) {
978
+ error("Secret Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
979
+ process.exit(1);
980
+ }
981
+ if (secretKey.startsWith("cb_pk_")) {
982
+ error("Public Key\uAC00 \uC544\uB2CC Secret Key(cb_sk_)\uB97C \uC785\uB825\uD558\uC138\uC694");
983
+ info("Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
984
+ process.exit(1);
985
+ }
986
+ if (!secretKey.startsWith("cb_sk_")) {
987
+ 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");
988
+ process.exit(1);
989
+ }
958
990
  }
959
991
  }
960
992
  let appId = "";
961
993
  let publicKey = "";
994
+ const projectName = path2.basename(cwd);
995
+ const reuseKeyIfSameApp = (selectedAppId) => {
996
+ if (existingRc.appId === selectedAppId && existingRc.publicKey?.startsWith("cb_pk_")) {
997
+ publicKey = existingRc.publicKey;
998
+ success("\uAE30\uC874 Public Key \uC7AC\uC0AC\uC6A9 (.connectbaserc)");
999
+ return true;
1000
+ }
1001
+ return false;
1002
+ };
1003
+ const issuePublicKey = async (targetAppId) => {
1004
+ info("Public Key \uC0DD\uC131 \uC911...");
1005
+ const newKeyRes = await makeRequest(
1006
+ `${DEFAULT_BASE_URL}/v1/public/cli/apps/${targetAppId}/public-keys`,
1007
+ "POST",
1008
+ { "X-Public-Key": secretKey },
1009
+ JSON.stringify({})
1010
+ );
1011
+ if (newKeyRes.status === 201) {
1012
+ const keyData = newKeyRes.data;
1013
+ publicKey = keyData.key;
1014
+ success("Public Key \uBC1C\uAE09 \uC644\uB8CC");
1015
+ } else {
1016
+ warn("Public Key \uC790\uB3D9 \uC0DD\uC131 \uC2E4\uD328. \uC218\uB3D9\uC73C\uB85C \uC0DD\uC131\uD558\uC138\uC694");
1017
+ }
1018
+ };
962
1019
  try {
963
1020
  info("\uC571 \uBAA9\uB85D \uC870\uD68C \uC911...");
964
1021
  const appsRes = await makeRequest(
@@ -975,7 +1032,23 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
975
1032
  }
976
1033
  const appsData = appsRes.data;
977
1034
  const apps = appsData.apps || [];
978
- if (apps.length > 0) {
1035
+ if (opts.appId) {
1036
+ const found = apps.find((a) => a.id === opts.appId);
1037
+ if (!found) {
1038
+ error(`\uC9C0\uC815\uD55C \uC571\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${opts.appId}`);
1039
+ process.exit(1);
1040
+ }
1041
+ appId = found.id;
1042
+ success(`\uC120\uD0DD\uB428: ${found.name}`);
1043
+ if (!reuseKeyIfSameApp(appId)) await issuePublicKey(appId);
1044
+ } else if (nonInteractive) {
1045
+ const found = apps.find((a) => a.name === projectName);
1046
+ if (found) {
1047
+ appId = found.id;
1048
+ success(`\uAE30\uC874 \uC571 \uC7AC\uC0AC\uC6A9: ${found.name}`);
1049
+ if (!reuseKeyIfSameApp(appId)) await issuePublicKey(appId);
1050
+ }
1051
+ } else if (apps.length > 0) {
979
1052
  log(`
980
1053
  ${colors.dim}\uB0B4 \uC571 \uBAA9\uB85D:${colors.reset}`);
981
1054
  apps.forEach((a, i) => {
@@ -989,25 +1062,11 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
989
1062
  if (num > 0 && num <= apps.length) {
990
1063
  appId = apps[num - 1].id;
991
1064
  success(`\uC120\uD0DD\uB428: ${apps[num - 1].name}`);
992
- info("Public Key \uC0DD\uC131 \uC911...");
993
- const newKeyRes = await makeRequest(
994
- `${DEFAULT_BASE_URL}/v1/public/cli/apps/${appId}/public-keys`,
995
- "POST",
996
- { "X-Public-Key": secretKey },
997
- JSON.stringify({})
998
- );
999
- if (newKeyRes.status === 201) {
1000
- const keyData = newKeyRes.data;
1001
- publicKey = keyData.key;
1002
- success("Public Key \uBC1C\uAE09 \uC644\uB8CC");
1003
- } else {
1004
- warn("Public Key \uC790\uB3D9 \uC0DD\uC131 \uC2E4\uD328. \uC218\uB3D9\uC73C\uB85C \uC0DD\uC131\uD558\uC138\uC694");
1005
- }
1065
+ if (!reuseKeyIfSameApp(appId)) await issuePublicKey(appId);
1006
1066
  }
1007
1067
  }
1008
1068
  if (!appId) {
1009
- const projectName = path2.basename(cwd);
1010
- const appName = await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
1069
+ const appName = nonInteractive ? projectName : await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
1011
1070
  info("\uC571 \uC0DD\uC131 \uC911...");
1012
1071
  const createRes = await makeRequest(
1013
1072
  `${DEFAULT_BASE_URL}/v1/public/cli/apps`,
@@ -1034,6 +1093,10 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1034
1093
  }
1035
1094
  } catch (err) {
1036
1095
  error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
1096
+ if (nonInteractive) {
1097
+ info("\uB124\uD2B8\uC6CC\uD06C \uBCF5\uAD6C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uAC70\uB098, \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uD55C \uAC12\uC73C\uB85C .connectbaserc\uB97C \uC9C1\uC811 \uC791\uC131\uD558\uC138\uC694");
1098
+ process.exit(1);
1099
+ }
1037
1100
  log(`${colors.dim}\uC218\uB3D9\uC73C\uB85C \uC785\uB825\uD558\uC138\uC694${colors.reset}`);
1038
1101
  appId = await prompt(`${colors.blue}?${colors.reset} App ID: `);
1039
1102
  publicKey = await prompt(`${colors.blue}?${colors.reset} Public Key (cb_pk_): `);
@@ -1048,77 +1111,92 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1048
1111
  }
1049
1112
  const publicKeyForSdk = publicKey;
1050
1113
  let storageId = "";
1051
- try {
1052
- info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC870\uD68C \uC911...");
1053
- const listRes = await makeRequest(
1054
- `${DEFAULT_BASE_URL}/v1/public/storages/webs`,
1055
- "GET",
1056
- { "X-Public-Key": publicKeyForSdk }
1057
- );
1058
- if (listRes.status === 200) {
1059
- const listData = listRes.data;
1060
- const storages = listData.storages || [];
1061
- if (storages.length > 0) {
1062
- log(`
1114
+ if (opts.storageId) {
1115
+ storageId = opts.storageId;
1116
+ success(`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC9C0\uC815\uB428: ${storageId}`);
1117
+ } else {
1118
+ try {
1119
+ info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC870\uD68C \uC911...");
1120
+ const listRes = await makeRequest(
1121
+ `${DEFAULT_BASE_URL}/v1/public/storages/webs`,
1122
+ "GET",
1123
+ { "X-Public-Key": publicKeyForSdk }
1124
+ );
1125
+ if (listRes.status === 200) {
1126
+ const listData = listRes.data;
1127
+ const storages = listData.storages || [];
1128
+ if (nonInteractive) {
1129
+ const found = storages.find((s) => s.name === projectName);
1130
+ if (found) {
1131
+ storageId = found.id;
1132
+ success(`\uAE30\uC874 \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC7AC\uC0AC\uC6A9: ${found.name}`);
1133
+ }
1134
+ } else if (storages.length > 0) {
1135
+ log(`
1063
1136
  ${colors.dim}\uAE30\uC874 \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0:${colors.reset}`);
1064
- storages.forEach((s, i) => {
1065
- log(` ${colors.cyan}${i + 1}${colors.reset}) ${s.name} (${colors.dim}${s.id}${colors.reset})`);
1066
- });
1067
- log(` ${colors.cyan}0${colors.reset}) \uC0C8\uB85C \uC0DD\uC131`);
1068
- const choice = await prompt(`
1137
+ storages.forEach((s, i) => {
1138
+ log(` ${colors.cyan}${i + 1}${colors.reset}) ${s.name} (${colors.dim}${s.id}${colors.reset})`);
1139
+ });
1140
+ log(` ${colors.cyan}0${colors.reset}) \uC0C8\uB85C \uC0DD\uC131`);
1141
+ const choice = await prompt(`
1069
1142
  ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
1070
- const num = parseInt(choice, 10);
1071
- if (num > 0 && num <= storages.length) {
1072
- storageId = storages[num - 1].id;
1073
- success(`\uC120\uD0DD\uB428: ${storages[num - 1].name}`);
1143
+ const num = parseInt(choice, 10);
1144
+ if (num > 0 && num <= storages.length) {
1145
+ storageId = storages[num - 1].id;
1146
+ success(`\uC120\uD0DD\uB428: ${storages[num - 1].name}`);
1147
+ }
1074
1148
  }
1149
+ if (!storageId) {
1150
+ const name = nonInteractive ? projectName : await prompt(`${colors.blue}?${colors.reset} \uC2A4\uD1A0\uB9AC\uC9C0 \uC774\uB984 (${projectName}): `) || projectName;
1151
+ info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC911...");
1152
+ const createRes = await makeRequest(
1153
+ `${DEFAULT_BASE_URL}/v1/public/storages/webs`,
1154
+ "POST",
1155
+ { "X-Public-Key": publicKeyForSdk },
1156
+ JSON.stringify({ name })
1157
+ );
1158
+ if (createRes.status !== 200) {
1159
+ const data = createRes.data;
1160
+ error(`\uC0DD\uC131 \uC2E4\uD328: ${data?.error || data?.message || `HTTP ${createRes.status}`}`);
1161
+ process.exit(1);
1162
+ }
1163
+ const createData = createRes.data;
1164
+ storageId = createData.storage_web_id;
1165
+ success(`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC644\uB8CC: ${createData.name}`);
1166
+ }
1167
+ } else {
1168
+ throw new Error(`HTTP ${listRes.status}`);
1169
+ }
1170
+ } catch (err) {
1171
+ error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
1172
+ if (nonInteractive) {
1173
+ info("\uB124\uD2B8\uC6CC\uD06C \uBCF5\uAD6C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uAC70\uB098, --storage <\uC2A4\uD1A0\uB9AC\uC9C0ID>\uC640 \uD568\uAED8 \uC2E4\uD589\uD558\uC138\uC694");
1174
+ process.exit(1);
1075
1175
  }
1176
+ log(`${colors.dim}\uC218\uB3D9\uC73C\uB85C Storage ID\uB97C \uC785\uB825\uD558\uC138\uC694${colors.reset}`);
1177
+ storageId = await prompt(`${colors.blue}?${colors.reset} Storage ID: `);
1076
1178
  if (!storageId) {
1077
- const projectName = path2.basename(cwd);
1078
- const name = await prompt(`${colors.blue}?${colors.reset} \uC2A4\uD1A0\uB9AC\uC9C0 \uC774\uB984 (${projectName}): `) || projectName;
1079
- info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC911...");
1080
- const createRes = await makeRequest(
1081
- `${DEFAULT_BASE_URL}/v1/public/storages/webs`,
1082
- "POST",
1083
- { "X-Public-Key": publicKeyForSdk },
1084
- JSON.stringify({ name })
1085
- );
1086
- if (createRes.status !== 200) {
1087
- const data = createRes.data;
1088
- error(`\uC0DD\uC131 \uC2E4\uD328: ${data?.error || data?.message || `HTTP ${createRes.status}`}`);
1089
- process.exit(1);
1090
- }
1091
- const createData = createRes.data;
1092
- storageId = createData.storage_web_id;
1093
- success(`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC644\uB8CC: ${createData.name}`);
1179
+ error("Storage ID\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
1180
+ process.exit(1);
1094
1181
  }
1095
- } else {
1096
- throw new Error(`HTTP ${listRes.status}`);
1097
- }
1098
- } catch (err) {
1099
- error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
1100
- log(`${colors.dim}\uC218\uB3D9\uC73C\uB85C Storage ID\uB97C \uC785\uB825\uD558\uC138\uC694${colors.reset}`);
1101
- storageId = await prompt(`${colors.blue}?${colors.reset} Storage ID: `);
1102
- if (!storageId) {
1103
- error("Storage ID\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
1104
- process.exit(1);
1105
1182
  }
1106
1183
  }
1107
- const defaultDir = detectBuildDir();
1108
- const deployDir = await prompt(`${colors.blue}?${colors.reset} \uBC30\uD3EC \uB514\uB809\uD1A0\uB9AC (${defaultDir}): `) || defaultDir;
1109
- const config = {
1184
+ const defaultDir = opts.deployDir || detectBuildDir();
1185
+ const deployDir = nonInteractive ? defaultDir : await prompt(`${colors.blue}?${colors.reset} \uBC30\uD3EC \uB514\uB809\uD1A0\uB9AC (${defaultDir}): `) || defaultDir;
1186
+ const rcData = {
1187
+ appId,
1110
1188
  publicKey: publicKey || "",
1111
1189
  storageId,
1112
1190
  deployDir
1113
1191
  };
1114
1192
  if (secretKey) {
1115
- config.secretKey = secretKey;
1193
+ rcData.secretKey = secretKey;
1116
1194
  }
1117
- fs2.writeFileSync(rcPath, JSON.stringify(config, null, 2) + "\n");
1195
+ fs2.writeFileSync(rcPath, JSON.stringify(rcData, null, 2) + "\n");
1118
1196
  success(".connectbaserc \uC0DD\uC131 \uC644\uB8CC");
1119
1197
  addToGitignore(".connectbaserc");
1120
1198
  addDeployScript(deployDir);
1121
- await setupClaudeCode(publicKeyForSdk, secretKey, projectRoot);
1199
+ await setupClaudeCode(secretKey, projectRoot);
1122
1200
  log(`
1123
1201
  ${colors.green}\uCD08\uAE30\uD654 \uC644\uB8CC!${colors.reset}
1124
1202
  `);
@@ -1132,9 +1210,20 @@ function detectBuildDir() {
1132
1210
  if (fs2.existsSync(path2.join(process.cwd(), "dist"))) return "./dist";
1133
1211
  if (fs2.existsSync(path2.join(process.cwd(), "build"))) return "./build";
1134
1212
  if (fs2.existsSync(path2.join(process.cwd(), "out"))) return "./out";
1135
- if (fs2.existsSync(path2.join(process.cwd(), ".next"))) return "./out";
1213
+ const nextConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].map((f) => path2.join(process.cwd(), f)).find((f) => fs2.existsSync(f));
1214
+ if (nextConfig || fs2.existsSync(path2.join(process.cwd(), ".next"))) {
1215
+ if (nextConfig) {
1216
+ try {
1217
+ const src = fs2.readFileSync(nextConfig, "utf-8");
1218
+ if (!/output\s*:\s*['"]export['"]/.test(src)) {
1219
+ warn(`Next.js \uAC10\uC9C0: \uC815\uC801 \uBC30\uD3EC\uD558\uB824\uBA74 ${path2.basename(nextConfig)}\uC5D0 output: 'export' \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4`);
1220
+ }
1221
+ } catch {
1222
+ }
1223
+ }
1224
+ return "./out";
1225
+ }
1136
1226
  if (fs2.existsSync(path2.join(process.cwd(), "vite.config.ts")) || fs2.existsSync(path2.join(process.cwd(), "vite.config.js"))) return "./dist";
1137
- if (fs2.existsSync(path2.join(process.cwd(), "next.config.js")) || fs2.existsSync(path2.join(process.cwd(), "next.config.mjs"))) return "./out";
1138
1227
  return "./dist";
1139
1228
  }
1140
1229
  function addToGitignore(entry, basePath) {
@@ -1142,7 +1231,8 @@ function addToGitignore(entry, basePath) {
1142
1231
  const gitignorePath = path2.join(dir, ".gitignore");
1143
1232
  if (fs2.existsSync(gitignorePath)) {
1144
1233
  const content = fs2.readFileSync(gitignorePath, "utf-8");
1145
- if (!content.includes(entry)) {
1234
+ const lines = content.split(/\r?\n/).map((l) => l.trim());
1235
+ if (!lines.includes(entry)) {
1146
1236
  fs2.appendFileSync(gitignorePath, `
1147
1237
  # Connect Base
1148
1238
  ${entry}
@@ -1223,13 +1313,66 @@ function detectMonorepo(gitRoot) {
1223
1313
  }
1224
1314
  return result;
1225
1315
  }
1226
- async function downloadDocs(publicKey, templates, baseDir) {
1316
+ var DOCS_MANIFEST_FILE = ".connectbase-manifest.json";
1317
+ function readDocsManifest(docsDir) {
1318
+ try {
1319
+ const parsed = JSON.parse(fs2.readFileSync(path2.join(docsDir, DOCS_MANIFEST_FILE), "utf-8"));
1320
+ if (parsed && Array.isArray(parsed.files)) return parsed;
1321
+ } catch {
1322
+ }
1323
+ return null;
1324
+ }
1325
+ var LEGACY_DOC_FILES = [
1326
+ "intro.md",
1327
+ "mcp-workflow.md",
1328
+ "sdk-basic.md",
1329
+ "sdk-database.md",
1330
+ "sdk-storage.md",
1331
+ "sdk-realtime.md",
1332
+ "sdk-auth.md",
1333
+ "sdk-payment.md",
1334
+ "example.md",
1335
+ "mcp-config.md",
1336
+ "mcp-tools.md",
1337
+ "resources.md",
1338
+ "sdk-game.md",
1339
+ "sdk-push.md",
1340
+ "sdk-video.md",
1341
+ "sdk-ads.md",
1342
+ "sdk-ai.md",
1343
+ "sdk-error-tracker.md",
1344
+ "sdk-webrtc.md",
1345
+ "sdk-subscription.md",
1346
+ "sdk-functions.md",
1347
+ "sdk-webhooks.md",
1348
+ "deploy-guide.md",
1349
+ "app-packaging.md",
1350
+ "best-practices.md",
1351
+ "code-analysis.md",
1352
+ "sdk-queue.md",
1353
+ "sdk-knowledge.md",
1354
+ "sdk-workflow.md",
1355
+ "sdk-tunnel.md",
1356
+ "sdk-native.md",
1357
+ "sdk-analytics.md",
1358
+ "sdk-domain.md",
1359
+ "sdk-public-key.md",
1360
+ "sdk-invoice.md",
1361
+ "web-performance.md",
1362
+ "sdk-analytics-advanced.md",
1363
+ "sdk-endpoint.md",
1364
+ "sdk-web-search.md",
1365
+ "sdk-oauth-provider.md",
1366
+ "sdk-oauth-integration.md"
1367
+ ];
1368
+ async function downloadDocs(baseDir, opts = {}) {
1369
+ const quiet = opts.quiet === true;
1227
1370
  if (!baseDir) {
1228
1371
  baseDir = getProjectRoot();
1229
1372
  }
1230
1373
  const gitRoot = getGitRoot() || baseDir;
1231
1374
  const monorepo = detectMonorepo(gitRoot);
1232
- if (monorepo.type !== "none") {
1375
+ if (!quiet && monorepo.type !== "none") {
1233
1376
  info(`\uBAA8\uB178\uB808\uD3EC \uAC10\uC9C0: ${monorepo.type} (\uB8E8\uD2B8: ${gitRoot})`);
1234
1377
  if (monorepo.isSubPackage) {
1235
1378
  info(`\uD604\uC7AC \uC11C\uBE0C \uD328\uD0A4\uC9C0: ${monorepo.subPackageName || path2.basename(process.cwd())}`);
@@ -1240,14 +1383,10 @@ async function downloadDocs(publicKey, templates, baseDir) {
1240
1383
  if (!fs2.existsSync(docsDir)) {
1241
1384
  fs2.mkdirSync(docsDir, { recursive: true });
1242
1385
  }
1243
- if (!templates) {
1244
- templates = ["rules"];
1245
- }
1246
- info("SDK \uAC1C\uBC1C \uAC00\uC774\uB4DC \uB2E4\uC6B4\uB85C\uB4DC \uC911...");
1386
+ if (!quiet) info("SDK \uAC1C\uBC1C \uAC00\uC774\uB4DC \uB2E4\uC6B4\uB85C\uB4DC \uC911...");
1247
1387
  try {
1248
- const templateParam = templates.join(",");
1249
1388
  const res = await makeRequest(
1250
- `${DEFAULT_BASE_URL}/v1/storages/webs/claude-md?template=${encodeURIComponent(templateParam)}&format=sections&public_key=${encodeURIComponent(publicKey)}`,
1389
+ `${DEFAULT_BASE_URL}/v1/storages/webs/claude-md?template=rules&format=sections`,
1251
1390
  "GET",
1252
1391
  {}
1253
1392
  );
@@ -1256,18 +1395,59 @@ async function downloadDocs(publicKey, templates, baseDir) {
1256
1395
  }
1257
1396
  const data = typeof res.data === "string" ? JSON.parse(res.data) : res.data;
1258
1397
  const sections = data.sections || [];
1398
+ const hasher = crypto.createHash("sha256");
1399
+ const newFiles = [];
1259
1400
  for (const section of sections) {
1260
- const filePath = path2.join(docsDir, section.filename);
1261
- fs2.writeFileSync(filePath, section.content);
1401
+ const safeName = path2.basename(section.filename);
1402
+ newFiles.push(safeName);
1403
+ hasher.update(safeName).update("\0").update(section.content).update("\0");
1404
+ }
1405
+ const contentHash = hasher.digest("hex");
1406
+ const prevManifest = readDocsManifest(docsDir);
1407
+ const allPresent = newFiles.every((f) => fs2.existsSync(path2.join(docsDir, f)));
1408
+ if (prevManifest && prevManifest.contentHash === contentHash && allPresent) {
1409
+ if (!quiet) success("SDK \uBB38\uC11C\uAC00 \uC774\uBBF8 \uCD5C\uC2E0\uC785\uB2C8\uB2E4");
1410
+ updateRootClaudeMd(rootClaudeMd, quiet);
1411
+ if (monorepo.isSubPackage) {
1412
+ createSubPackageClaudeMd(path2.join(process.cwd(), "CLAUDE.md"), gitRoot, quiet);
1413
+ }
1414
+ return "unchanged";
1262
1415
  }
1263
- success(`${gitRoot}/.claude/docs/ \uC5D0 ${sections.length}\uAC1C \uBB38\uC11C \uC800\uC7A5 \uC644\uB8CC`);
1264
- updateRootClaudeMd(rootClaudeMd);
1416
+ const stamp = `<!-- generated by connectbase-cli v${VERSION} (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}) \u2014 \uC790\uB3D9 \uC0DD\uC131 \uD30C\uC77C\uC785\uB2C8\uB2E4. \uC9C1\uC811 \uC218\uC815\uD558\uC9C0 \uB9D0\uACE0 'npx connectbase update'\uB85C \uAC31\uC2E0\uD558\uC138\uC694 -->
1417
+
1418
+ `;
1419
+ for (const section of sections) {
1420
+ const safeName = path2.basename(section.filename);
1421
+ fs2.writeFileSync(path2.join(docsDir, safeName), stamp + section.content);
1422
+ }
1423
+ const staleCandidates = prevManifest ? prevManifest.files.map((f) => path2.basename(f)) : LEGACY_DOC_FILES;
1424
+ for (const old of staleCandidates) {
1425
+ if (newFiles.includes(old)) continue;
1426
+ const oldPath = path2.join(docsDir, old);
1427
+ if (fs2.existsSync(oldPath)) {
1428
+ try {
1429
+ fs2.unlinkSync(oldPath);
1430
+ if (!quiet) info(`\uC2A4\uD14C\uC77C \uBB38\uC11C \uC0AD\uC81C: ${old}`);
1431
+ } catch {
1432
+ }
1433
+ }
1434
+ }
1435
+ const manifest = {
1436
+ cliVersion: VERSION,
1437
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1438
+ contentHash,
1439
+ files: newFiles
1440
+ };
1441
+ fs2.writeFileSync(path2.join(docsDir, DOCS_MANIFEST_FILE), JSON.stringify(manifest, null, 2) + "\n");
1442
+ if (!quiet) success(`${gitRoot}/.claude/docs/ \uC5D0 ${sections.length}\uAC1C \uBB38\uC11C \uC800\uC7A5 \uC644\uB8CC`);
1443
+ updateRootClaudeMd(rootClaudeMd, quiet);
1265
1444
  if (monorepo.isSubPackage) {
1266
- const subClaudeMd = path2.join(process.cwd(), "CLAUDE.md");
1267
- createSubPackageClaudeMd(subClaudeMd, gitRoot);
1445
+ createSubPackageClaudeMd(path2.join(process.cwd(), "CLAUDE.md"), gitRoot, quiet);
1268
1446
  }
1269
- log(`${colors.dim}\u203B SDK \uBB38\uC11C\uB294 MCP search_sdk_docs\uB85C \uAC80\uC0C9\uD558\uC138\uC694${colors.reset}`);
1447
+ if (!quiet) log(`${colors.dim}\u203B SDK \uBB38\uC11C\uB294 MCP search_sdk_docs\uB85C \uAC80\uC0C9\uD558\uC138\uC694${colors.reset}`);
1448
+ return "updated";
1270
1449
  } catch {
1450
+ if (quiet) return "failed";
1271
1451
  warn("SDK \uAC00\uC774\uB4DC \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328, \uAE30\uBCF8 \uBB38\uC11C\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4");
1272
1452
  const fallbackContent = `# ConnectBase \uD504\uB85C\uC81D\uD2B8
1273
1453
 
@@ -1282,9 +1462,10 @@ async function downloadDocs(publicKey, templates, baseDir) {
1282
1462
  createSubPackageClaudeMd(subClaudeMd, gitRoot);
1283
1463
  }
1284
1464
  success(`.claude/docs/ \uC0DD\uC131 \uC644\uB8CC (\uAE30\uBCF8)`);
1465
+ return "failed";
1285
1466
  }
1286
1467
  }
1287
- function updateRootClaudeMd(claudeMdPath) {
1468
+ function updateRootClaudeMd(claudeMdPath, quiet = false) {
1288
1469
  const startMarker = "<!-- CONNECTBASE_START -->";
1289
1470
  const endMarker = "<!-- CONNECTBASE_END -->";
1290
1471
  const sdkBlock = `${startMarker}
@@ -1292,11 +1473,13 @@ function updateRootClaudeMd(claudeMdPath) {
1292
1473
 
1293
1474
  \uC774 \uD504\uB85C\uC81D\uD2B8\uB294 **ConnectBase**\uB97C \uBC31\uC5D4\uB4DC\uB85C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.
1294
1475
 
1295
- ### AI \uD544\uB3C5 \u2014 \uCF54\uB529 \uC804 \uBC18\uB4DC\uC2DC \uC218\uD589
1476
+ @.claude/docs/project-rules.md
1296
1477
 
1297
- 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)
1298
- 2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
1299
- 3. \uAC80\uC0C9 \uACB0\uACFC\uC758 \uCF54\uB4DC \uD328\uD134\uC744 \uB530\uB77C \uAD6C\uD604\uD558\uC138\uC694
1478
+ ### AI \uD544\uC218 \uC6CC\uD06C\uD50C\uB85C\uC6B0
1479
+
1480
+ 1. \uC704\uC5D0 import\uB41C project-rules.md\uC758 \uAE30\uB2A5 \uB9E4\uD551\uD45C\xB7\uBCF4\uC548 \uADDC\uCE59\uC744 \uB530\uB974\uC138\uC694
1481
+ 2. \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uAE30 \uC804 \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C SDK \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uACE0, \uAC80\uC0C9 \uACB0\uACFC\uC758 \uCF54\uB4DC \uD328\uD134\uC744 \uADF8\uB300\uB85C \uB530\uB974\uC138\uC694
1482
+ 3. \`search_sdk_docs\` \uB3C4\uAD6C\uAC00 \uBCF4\uC774\uC9C0 \uC54A\uC73C\uBA74 MCP\uAC00 \uC5F0\uACB0\uB418\uC9C0 \uC54A\uC740 \uC0C1\uD0DC\uC785\uB2C8\uB2E4 \u2014 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \`npx connectbase mcp\` \uC2E4\uD589\uC744 \uC548\uB0B4\uD558\uC138\uC694 (Secret Key \uD544\uC694)
1300
1483
  ${endMarker}`;
1301
1484
  if (fs2.existsSync(claudeMdPath)) {
1302
1485
  let content = fs2.readFileSync(claudeMdPath, "utf-8");
@@ -1308,16 +1491,16 @@ ${endMarker}`;
1308
1491
  content = sdkBlock + "\n\n" + content.trimStart();
1309
1492
  }
1310
1493
  fs2.writeFileSync(claudeMdPath, content);
1311
- info("CLAUDE.md\uC5D0 ConnectBase \uCC38\uC870 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
1494
+ if (!quiet) info("CLAUDE.md\uC5D0 ConnectBase \uCC38\uC870 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
1312
1495
  } else {
1313
1496
  fs2.writeFileSync(claudeMdPath, `# \uD504\uB85C\uC81D\uD2B8
1314
1497
 
1315
1498
  ${sdkBlock}
1316
1499
  `);
1317
- success("CLAUDE.md \uC0DD\uC131 \uC644\uB8CC");
1500
+ if (!quiet) success("CLAUDE.md \uC0DD\uC131 \uC644\uB8CC");
1318
1501
  }
1319
1502
  }
1320
- function createSubPackageClaudeMd(subClaudeMdPath, gitRoot) {
1503
+ function createSubPackageClaudeMd(subClaudeMdPath, gitRoot, quiet = false) {
1321
1504
  const startMarker = "<!-- CONNECTBASE_SUB_START -->";
1322
1505
  const endMarker = "<!-- CONNECTBASE_SUB_END -->";
1323
1506
  const relPath = path2.relative(path2.dirname(subClaudeMdPath), gitRoot);
@@ -1328,9 +1511,9 @@ function createSubPackageClaudeMd(subClaudeMdPath, gitRoot) {
1328
1511
 
1329
1512
  ### AI \uD544\uB3C5
1330
1513
 
1331
- 1. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC758 \`${relPath}/.claude/docs/project-rules.md\`\uB97C **Read tool\uB85C \uC77D\uC73C\uC138\uC694**
1332
- 2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C SDK \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
1333
- 3. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC758 \`${relPath}/CLAUDE.md\`\uB3C4 \uCC38\uACE0\uD558\uC138\uC694
1514
+ 1. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC758 \`${relPath}/.claude/docs/project-rules.md\`\uC758 \uAE30\uB2A5 \uB9E4\uD551\uD45C\xB7\uBCF4\uC548 \uADDC\uCE59\uC744 \uB530\uB974\uC138\uC694
1515
+ 2. \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uAE30 \uC804 \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C SDK \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
1516
+ 3. \`search_sdk_docs\` \uB3C4\uAD6C\uAC00 \uBCF4\uC774\uC9C0 \uC54A\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \`npx connectbase mcp\` \uC2E4\uD589\uC744 \uC548\uB0B4\uD558\uC138\uC694
1334
1517
  ${endMarker}`;
1335
1518
  if (fs2.existsSync(subClaudeMdPath)) {
1336
1519
  let content = fs2.readFileSync(subClaudeMdPath, "utf-8");
@@ -1350,7 +1533,7 @@ ${endMarker}`;
1350
1533
  ${subBlock}
1351
1534
  `);
1352
1535
  }
1353
- success(`\uC11C\uBE0C \uD328\uD0A4\uC9C0 CLAUDE.md \uC0DD\uC131 \uC644\uB8CC: ${subClaudeMdPath}`);
1536
+ if (!quiet) success(`\uC11C\uBE0C \uD328\uD0A4\uC9C0 CLAUDE.md \uC0DD\uC131 \uC644\uB8CC: ${subClaudeMdPath}`);
1354
1537
  }
1355
1538
  async function setupMcp(secretKey) {
1356
1539
  const gitRoot = getGitRoot();
@@ -1364,6 +1547,13 @@ async function setupMcp(secretKey) {
1364
1547
  }
1365
1548
  const mcpConfigPath = path2.join(root, ".mcp.json");
1366
1549
  if (!secretKey) {
1550
+ if (!process.stdin.isTTY) {
1551
+ error("Secret Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (\uBE44\uB300\uD654\uD615 \uD658\uACBD)");
1552
+ log(`${colors.dim}\uB2E4\uC74C \uC911 \uD558\uB098\uB85C \uD0A4\uB97C \uC804\uB2EC\uD558\uC138\uC694:${colors.reset}`);
1553
+ log(` ${colors.cyan}npx connectbase mcp --secret-key cb_sk_...${colors.reset}`);
1554
+ log(` ${colors.cyan}CONNECTBASE_SECRET_KEY=cb_sk_... npx connectbase mcp${colors.reset}`);
1555
+ process.exit(1);
1556
+ }
1367
1557
  log(`
1368
1558
  ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
1369
1559
  log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
@@ -1427,9 +1617,9 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
1427
1617
  log(`
1428
1618
  ${colors.dim}Claude Code\uC5D0\uC11C ConnectBase MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.${colors.reset}`);
1429
1619
  }
1430
- async function setupClaudeCode(publicKey, secretKey, projectRoot) {
1620
+ async function setupClaudeCode(secretKey, projectRoot) {
1431
1621
  const root = projectRoot || getProjectRoot();
1432
- await downloadDocs(publicKey, void 0, root);
1622
+ await downloadDocs(root);
1433
1623
  await setupMcp(secretKey);
1434
1624
  }
1435
1625
  async function fetchLatestVersion(packageName) {
@@ -1448,13 +1638,18 @@ async function fetchLatestVersion(packageName) {
1448
1638
  return null;
1449
1639
  }
1450
1640
  function compareVersions(a, b) {
1451
- const pa = a.split(".").map(Number);
1452
- const pb = b.split(".").map(Number);
1641
+ const [aMain, aPre] = a.split("-", 2);
1642
+ const [bMain, bPre] = b.split("-", 2);
1643
+ const pa = aMain.split(".").map(Number);
1644
+ const pb = bMain.split(".").map(Number);
1453
1645
  for (let i = 0; i < 3; i++) {
1454
1646
  const diff = (pa[i] || 0) - (pb[i] || 0);
1455
1647
  if (diff !== 0) return diff > 0 ? 1 : -1;
1456
1648
  }
1457
- return 0;
1649
+ if (aPre === bPre) return 0;
1650
+ if (!aPre) return 1;
1651
+ if (!bPre) return -1;
1652
+ return aPre < bPre ? -1 : aPre > bPre ? 1 : 0;
1458
1653
  }
1459
1654
  function findSubPackagesWithConfig(gitRoot) {
1460
1655
  const results = [];
@@ -1526,36 +1721,7 @@ ${colors.cyan}ConnectBase Update${colors.reset}
1526
1721
  if (!opts.skipDocs) {
1527
1722
  log("");
1528
1723
  info("SDK \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8 \uC911...");
1529
- let docsKey = config.publicKey || config.secretKey;
1530
- if (!docsKey) {
1531
- const rootRcPath = path2.join(projectRoot, ".connectbaserc");
1532
- if (fs2.existsSync(rootRcPath)) {
1533
- try {
1534
- const rc = JSON.parse(fs2.readFileSync(rootRcPath, "utf-8"));
1535
- docsKey = rc.publicKey || rc.secretKey;
1536
- } catch {
1537
- }
1538
- }
1539
- }
1540
- if (!docsKey && monorepo.type !== "none") {
1541
- const subPackages = findSubPackagesWithConfig(projectRoot);
1542
- for (const sp of subPackages) {
1543
- try {
1544
- const rc = JSON.parse(fs2.readFileSync(path2.join(sp, ".connectbaserc"), "utf-8"));
1545
- if (rc.publicKey || rc.secretKey) {
1546
- docsKey = rc.publicKey || rc.secretKey;
1547
- break;
1548
- }
1549
- } catch {
1550
- }
1551
- }
1552
- }
1553
- if (docsKey) {
1554
- await downloadDocs(docsKey, void 0, projectRoot);
1555
- } else {
1556
- warn("Public Key\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC5B4 \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4");
1557
- info(".connectbaserc \uD30C\uC77C\uC774\uB098 --public-key \uC635\uC158\uC73C\uB85C \uD0A4\uB97C \uC9C0\uC815\uD558\uC138\uC694");
1558
- }
1724
+ await downloadDocs(projectRoot);
1559
1725
  }
1560
1726
  if (!opts.skipMcp) {
1561
1727
  log("");
@@ -2642,8 +2808,12 @@ ${colors.yellow}\uC635\uC158:${colors.reset}
2642
2808
  --label <name> Endpoint binding \uC790\uB3D9 \uB4F1\uB85D (tunnel \uC804\uC6A9) \u2014 SDK \uC758 cb.endpoint.call(label) \uD638\uCD9C \uAC00\uB2A5
2643
2809
  --description <text> Endpoint binding \uC124\uBA85 (--label \uB3D9\uBC18 \uC2DC\uB9CC)
2644
2810
  -d, --dev Dev \uD658\uACBD\uC5D0 \uBC30\uD3EC (deploy \uC804\uC6A9)
2811
+ -y, --yes \uBE44\uB300\uD654\uD615 \uCD08\uAE30\uD654 \u2014 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 \uAE30\uBCF8\uAC12/\uD50C\uB798\uADF8\uB85C \uC9C4\uD589 (init \uC804\uC6A9, CI/AI \uC5D0\uC774\uC804\uD2B8\uC6A9)
2812
+ -a, --app <id> \uAE30\uC874 \uC571 ID \uC9C0\uC815 (init \uC804\uC6A9)
2813
+ --secret-key <key> Secret Key (cb_sk_) \uC9C0\uC815 (init/mcp)
2814
+ --deploy-dir <dir> \uBC30\uD3EC \uB514\uB809\uD1A0\uB9AC \uC9C0\uC815 (init \uC804\uC6A9, \uAE30\uBCF8: \uC790\uB3D9 \uAC10\uC9C0)
2645
2815
  --check \uBC84\uC804\uB9CC \uD655\uC778 (update \uC804\uC6A9)
2646
- --skip-docs \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8 \uAC74\uB108\uB6F0\uAE30 (update \uC804\uC6A9)
2816
+ --skip-docs \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8 \uAC74\uB108\uB6F0\uAE30 (update/deploy)
2647
2817
  --skip-mcp MCP \uC5C5\uB370\uC774\uD2B8 \uAC74\uB108\uB6F0\uAE30 (update \uC804\uC6A9)
2648
2818
  --setup-root \uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 (update \uC804\uC6A9)
2649
2819
  (docs, mcp, update\uB294 \uBAA8\uB178\uB808\uD3EC\uB97C \uC790\uB3D9 \uAC10\uC9C0\uD558\uC5EC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 \uC0DD\uC131)
@@ -2654,6 +2824,9 @@ ${colors.yellow}\uBE60\uB978 \uC2DC\uC791:${colors.reset}
2654
2824
  ${colors.dim}# 1. \uCD08\uAE30\uD654 (\uCD5C\uCD08 1\uD68C)${colors.reset}
2655
2825
  npx connectbase init
2656
2826
 
2827
+ ${colors.dim}# 1-1. \uBE44\uB300\uD654\uD615 \uCD08\uAE30\uD654 (CI/AI \uC5D0\uC774\uC804\uD2B8)${colors.reset}
2828
+ npx connectbase init --yes --secret-key cb_sk_...
2829
+
2657
2830
  ${colors.dim}# 2. SDK \uC804\uCCB4 \uC5C5\uB370\uC774\uD2B8 (\uBC84\uC804 \uCCB4\uD06C + \uBB38\uC11C + MCP)${colors.reset}
2658
2831
  npx connectbase update
2659
2832
 
@@ -2724,6 +2897,10 @@ function parseArgs(args) {
2724
2897
  result.options.appId = args[++i];
2725
2898
  } else if (arg === "--force") {
2726
2899
  result.options.force = "true";
2900
+ } else if (arg === "-y" || arg === "--yes") {
2901
+ result.options.yes = "true";
2902
+ } else if (arg === "--deploy-dir") {
2903
+ result.options.deployDir = args[++i];
2727
2904
  } else if (arg === "--public") {
2728
2905
  result.options.public = "true";
2729
2906
  } else if (arg === "--show-token") {
@@ -2771,7 +2948,13 @@ async function main() {
2771
2948
  baseUrl: parsed.options.baseUrl || fileConfig.baseUrl || DEFAULT_BASE_URL
2772
2949
  };
2773
2950
  if (parsed.command === "init") {
2774
- await init();
2951
+ await init(config, {
2952
+ yes: parsed.options.yes === "true",
2953
+ force: parsed.options.force === "true",
2954
+ appId: parsed.options.appId,
2955
+ storageId: parsed.options.storageId,
2956
+ deployDir: parsed.options.deployDir
2957
+ });
2775
2958
  } else if (parsed.command === "update") {
2776
2959
  await update(config, {
2777
2960
  checkOnly: parsed.options.check === "true",
@@ -2780,9 +2963,9 @@ async function main() {
2780
2963
  setupRoot: parsed.options.setupRoot === "true"
2781
2964
  });
2782
2965
  } else if (parsed.command === "docs") {
2783
- await downloadDocs(config.publicKey || "");
2966
+ await downloadDocs();
2784
2967
  } else if (parsed.command === "mcp") {
2785
- await setupMcp();
2968
+ await setupMcp(config.secretKey);
2786
2969
  } else if (parsed.command === "deploy") {
2787
2970
  const directory = parsed.args[0] || fileConfig.deployDir || ".";
2788
2971
  if (!config.publicKey) {
@@ -2801,6 +2984,18 @@ async function main() {
2801
2984
  else warn(`--timeout \uAC12\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544 \uBB34\uC2DC\uB429\uB2C8\uB2E4: ${parsed.options.timeout}`);
2802
2985
  }
2803
2986
  await deploy(directory, config, isDev, deployOpts);
2987
+ if (parsed.options.skipDocs !== "true") {
2988
+ try {
2989
+ const docsRoot = getGitRoot() || getProjectRoot();
2990
+ if (fs2.existsSync(path2.join(docsRoot, ".claude", "docs", DOCS_MANIFEST_FILE))) {
2991
+ const refreshed = await downloadDocs(docsRoot, { quiet: true });
2992
+ if (refreshed === "updated") {
2993
+ info("SDK \uBB38\uC11C\uAC00 \uC0C8 \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (.claude/docs/)");
2994
+ }
2995
+ }
2996
+ } catch {
2997
+ }
2998
+ }
2804
2999
  } else if (parsed.command === "tunnel") {
2805
3000
  const portStr = parsed.args[0];
2806
3001
  if (!portStr) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "3.43.0",
3
+ "version": "3.44.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",