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.
- package/CHANGELOG.md +32 -0
- package/dist/cli.js +367 -172
- 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
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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(
|
|
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
|
-
|
|
946
|
-
if (
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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
|
-
|
|
1078
|
-
|
|
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
|
|
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
|
-
|
|
1193
|
+
rcData.secretKey = secretKey;
|
|
1116
1194
|
}
|
|
1117
|
-
fs2.writeFileSync(rcPath, JSON.stringify(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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
|
|
1261
|
-
|
|
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
|
-
|
|
1264
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1476
|
+
@.claude/docs/project-rules.md
|
|
1296
1477
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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\`\
|
|
1332
|
-
2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C SDK \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
|
|
1333
|
-
3. \
|
|
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(
|
|
1620
|
+
async function setupClaudeCode(secretKey, projectRoot) {
|
|
1431
1621
|
const root = projectRoot || getProjectRoot();
|
|
1432
|
-
await downloadDocs(
|
|
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
|
|
1452
|
-
const
|
|
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
|
-
|
|
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
|
|
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(
|
|
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) {
|