connectbase-client 6.2.0 → 6.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/CHANGELOG.md +106 -5
  2. package/README.md +24 -19
  3. package/dist/cli.js +253 -195
  4. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,107 @@
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
+ ## [6.3.1] - 2026-09-08
7
+
8
+ ### Fixed — `init` 의 웹 스토리지 생성 실패가 `[object Object]` 로만 찍히던 문제
9
+
10
+ 앱에 이미 같은 이름의 웹 스토리지가 있는 상태에서 `init` 이 같은 이름으로 생성을 시도하면
11
+ 서버는 409 를 돌려주는데, CLI 가 그 응답 본문(`{ error: { code, message } }`)을 문자열로
12
+ 찍어 `생성 실패: [object Object]` 만 보였습니다. 원인이 이름 충돌이라는 사실을 알 길이
13
+ 없었고, 스토리지 선택 프롬프트에서 Enter 만 치면 정확히 이 경로로 들어갔습니다.
14
+
15
+ - 에러 응답 형식이 `{ error: "문자열" }` 이든 표준 봉투 `{ error: { code, message,
16
+ suggestion } }` 이든 사람이 읽을 메시지로 정리하는 헬퍼(`apiErrorMessage`)를 두고,
17
+ `init` / `deploy` / `promote` / 배포 이력 / presign / 앱 생성 / 인증 세션의 에러 출력을
18
+ 전부 이 헬퍼로 통일했습니다. 서버가 제안(suggestion)을 보내면 함께 보여 줍니다.
19
+ - 대화형 `init` 도 `--yes` 와 같은 규칙으로, 입력한 이름과 같은 웹 스토리지가 이미 있으면
20
+ 생성 대신 그 스토리지를 재사용합니다. 서버 요청 없이 즉시 넘어가므로 409 자체가 나지
21
+ 않습니다.
22
+ - 서버(core-server)도 같은 앱의 이름 중복을 미리 검사해 `같은 이름의 웹 스토리지가 이미
23
+ 있습니다: "<이름>"` 이라는 409 를 돌려줍니다. 구버전 CLI 로도 이제 이 문장이 보입니다.
24
+
25
+ ## [6.3.0] - 2026-09-07
26
+
27
+ ### Fixed — Secret Key 가 만료되면 `init` 을 다시 돌릴 수 없던 문제
28
+
29
+ `init` 은 `.connectbaserc` 에 저장된 Secret Key 를 그대로 집어 씁니다. 그 키가 만료되거나
30
+ 취소되면 앱 목록 조회가 401 을 받고 **그대로 종료**했는데, 다시 실행해도 같은 죽은 키를
31
+ 다시 집어 오므로 같은 자리에서 또 죽었습니다. `.connectbaserc` 를 손으로 지우는 것 말고는
32
+ 빠져나올 길이 없었습니다.
33
+
34
+ 이제 401 을 받으면 **그 자리에서 재인증**합니다 (브라우저 로그인 또는 직접 입력). 새 키는
35
+ 즉시 `.connectbaserc` 에 저장되고 `.mcp.json` 에도 반영되므로, 파일을 지우는 절차가
36
+ 사라졌습니다. 비대화형(`--yes`)에서는 프롬프트 대신 새 키를 넘기라고 명확히 안내합니다.
37
+
38
+ `connectbase mcp` 도 같습니다 — 넘겨받은 키가 살아 있는지 **먼저 확인**한 뒤 `.mcp.json`
39
+ 에 씁니다. 종전에는 만료된 키를 그대로 적고 "설정 완료" 라고 말해서, 실패가 Claude Code 의
40
+ 401 로만 드러났습니다. (네트워크 오류로는 막지 않습니다 — 오프라인에서도 설정은 써집니다.)
41
+
42
+ 재초기화 확인 프롬프트의 기본값도 "계속" 으로 바꿨습니다. 같은 앱을 고르면 Public Key 를
43
+ 재사용하므로 재실행은 안전한 동작입니다.
44
+
45
+ ### Fixed — `npm run deploy` 가 `command not found` 로 죽던 문제
46
+
47
+ `init` 이 `package.json` 에 deploy 스크립트만 넣고 CLI 를 의존성으로 선언하지 않아서,
48
+ SDK 가 설치되지 않은 프로젝트에서 마지막 안내대로 `npm run deploy` 를 치면
49
+ `connectbase: command not found` 가 났습니다. 이제 `devDependencies` 에
50
+ `connectbase-client` 를 함께 선언하고, 설치가 필요하면 마지막 안내에 `npm install` 을
51
+ 포함합니다. (`npx --yes` 를 스크립트에 박는 우회는 쓰지 않았습니다 — 빌드마다
52
+ 레지스트리에서 최신본을 끌어오면 재현 가능한 빌드가 아닙니다.)
53
+
54
+ 빌드 스크립트가 있으면 deploy 스크립트는 이제 build 명령을 **복사하지 않고**
55
+ `npm run build && connectbase deploy <dir>` 로 위임합니다. 복사본은 build 가 바뀌어도
56
+ 갱신되지 않아 조용히 낡습니다.
57
+
58
+ ### Fixed — `--base-url` / `CONNECTBASE_BASE_URL` 이 `init` 에서 무시되던 문제
59
+
60
+ `init` 과 브라우저 로그인, SDK 문서 다운로드, MCP 설정이 상수를 직접 참조해, 로컬이나
61
+ 스테이징 스택을 지정해도 프로덕션 API 로 갔습니다. 브라우저 로그인 URL 도 같은 환경을
62
+ 가리키도록 API URL 에서 파생시킵니다.
63
+
64
+ ### Fixed — CLI 버전을 빌드 시점에 주입
65
+
66
+ 종전에는 런타임에 `package.json` 을 찾아 읽고, 실패하면 하드코딩된 옛 버전(`3.25.1`)으로
67
+ 폴백했습니다. 그 폴백은 조용히 낡았고, 단순히 `--version` 이 틀리는 데서 끝나지 않았습니다 —
68
+ `init` 이 사용자의 `package.json` 에 적는 의존성 범위에까지 그 값이 들어갑니다.
69
+
70
+ ### Fixed — git 저장소가 아닌 폴더에서 `fatal:` 오류가 출력되던 문제
71
+
72
+ `init` 이 프로젝트 루트를 찾을 때 쓰는 `git rev-parse` 의 stderr 가 그대로 새어 나와,
73
+ 정상 경로인데도 화면에 `fatal: … 깃 저장소가 아닙니다` 가 세 번 찍혔습니다.
74
+
75
+ ### Changed — 문서의 `npx` 예제가 패키지 이름을 쓰도록 통일
76
+
77
+ `connectbase` 는 이 패키지가 설치하는 **bin 이름**이지 npm 패키지 이름이 아닙니다. npx 는
78
+ 로컬 `node_modules/.bin` 을 먼저 보고, 없으면 그 이름의 **패키지**를 레지스트리에서 받으려
79
+ 합니다. 그래서 `npx connectbase init` 은 이 패키지가 이미 설치된 프로젝트에서만 동작하고,
80
+ 아무것도 없는 새 폴더 — 즉 `init` 을 처음 실행하는 바로 그 상황 — 에서는 npm 404 가 났습니다.
81
+
82
+ ```
83
+ $ npx connectbase init
84
+ npm error 404 Not Found - GET https://registry.npmjs.org/connectbase
85
+ ```
86
+
87
+ 전역 설치(`npm i -g connectbase-client`)로 PATH 에 `connectbase` 가 있어도 마찬가지입니다.
88
+ npx 는 PATH 를 보지 않습니다.
89
+
90
+ 규칙을 하나로 정리했습니다:
91
+
92
+ | 형태 | 이름 | 왜 |
93
+ |---|---|---|
94
+ | `npx …` | `connectbase-client` | npx 인자는 **패키지** 이름이다. 설치 여부와 무관하게 항상 동작 |
95
+ | 전역 설치 후 / `npm run` 스크립트 / `node_modules/.bin` | `connectbase` | 짧은 bin 이름. 0.16.1 에서 정한 용도 그대로 |
96
+
97
+ ```bash
98
+ npx connectbase-client init # 설치 전 — 패키지 이름
99
+ npm install # init 이 devDependencies 에 넣어 둔 것을 설치
100
+ npm run deploy # 내부적으로 `connectbase deploy ./dist` — 짧은 이름 그대로
101
+ ```
102
+
103
+ `connectbase` 를 실제 패키지로도 발행하려 했으나, npm 이 2013 년의 `connect-base` 와
104
+ 이름이 유사하다는 이유로 거절했습니다 (`@connectbase` 스코프도 타인이 사용 중). 이름을
105
+ 확보하면 `npx connectbase` 도 함께 동작하게 할 예정입니다.
106
+
6
107
  ## [6.2.0] - 2026-09-06
7
108
 
8
109
  ### Added — 웹 스토리지 관리 API 에 Secret Key 전송
@@ -23,7 +124,7 @@ production 승격이 가능했습니다 — 공개된 사이트에서 키를 뽑
23
124
 
24
125
  **이 버전은 아무것도 깨뜨리지 않습니다.** Secret Key 가 없으면 경고만 하고 그대로 진행합니다.
25
126
  CLI 가 서버보다 먼저 조이면, 우리가 생성해 드리는 GitHub Actions 워크플로처럼 버전을 고정하지
26
- 않는(`npx --yes connectbase deploy`) 환경이 서버 변경 전에 깨지기 때문입니다.
127
+ 않는(`npx --yes connectbase-client deploy`) 환경이 서버 변경 전에 깨지기 때문입니다.
27
128
 
28
129
  **지금 해두실 것.** CI 를 쓰신다면 저장소 시크릿에 `CONNECTBASE_SECRET_KEY` 를 추가하고
29
130
  워크플로 env 에 넣으세요. 워크플로 파일에 값을 직접 적으면 저장소에 커밋됩니다.
@@ -554,7 +655,7 @@ await ctx.cbAdmin.appMembers.remove(ctx.appId, memberId)
554
655
  ```bash
555
656
  # 이제 dist 에 두기만 하면 함께 올라간다
556
657
  echo "/blog /blog.html 200" > dist/_redirects
557
- npx connectbase deploy ./dist
658
+ npx connectbase-client deploy ./dist
558
659
  ```
559
660
 
560
661
  - `_redirects`, `_headers` 만 이름 완전일치로 허용한다. `Dockerfile`·`Makefile`·`LICENSE`
@@ -1152,7 +1253,7 @@ AI 에이전트(Claude Code 등)가 `init` 을 직접 실행할 수 있도록
1152
1253
  자동 정리(레거시 fullstack 파일 포함), `deploy` 성공 후 변경이 있을 때만 조용히 자동 갱신
1153
1254
  (`--skip-docs` 로 생략 가능).
1154
1255
  - **CLAUDE.md @import**: 참조 블록이 `@.claude/docs/project-rules.md` import 로 규칙을 매
1155
- 세션 자동 로드. MCP 미연결 시 `npx connectbase mcp` 안내 지시 포함. `mcp` 명령도 비대화형
1256
+ 세션 자동 로드. MCP 미연결 시 `npx connectbase-client mcp` 안내 지시 포함. `mcp` 명령도 비대화형
1156
1257
  지원(`--secret-key`/env/.connectbaserc 자동 사용).
1157
1258
 
1158
1259
  ### Changed / Security
@@ -2793,7 +2894,7 @@ ComfyUI × 웹스토리지 같은 e2e 통합 패턴 (작업 제출 → 폴링
2793
2894
 
2794
2895
  ### Fixed
2795
2896
 
2796
- - `npx connectbase docs` 가 인증 없이 곧장 문서를 받도록 수정. 백엔드 `/v1/storages/webs/claude-md`
2897
+ - `npx connectbase-client docs` 가 인증 없이 곧장 문서를 받도록 수정. 백엔드 `/v1/storages/webs/claude-md`
2797
2898
  는 public 라우트인데 CLI 가 불필요하게 브라우저 인증 → 앱 선택 → Public Key 발급을 강제하던
2798
2899
  흐름을 제거. 캐시된 publicKey 가 있으면 문서에 박아주고, 없으면 백엔드가 placeholder
2799
2900
  (`YOUR_PUBLIC_KEY_HERE`) 로 대체해 그대로 다운로드.
@@ -3225,7 +3326,7 @@ sisun 팀(`019d85c7-...`) 의 운영 피드백 3건이 시작점이며, 백엔
3225
3326
 
3226
3327
  ## [1.4.2] - 2026-04-18
3227
3328
 
3228
- `npx connectbase docs` 명령이 init/deploy/tunnel 과 달리 brower auth 흐름을 거치지 않아, Public Key 가 없는 사용자가 키 발급 경로를 모른 채 prompt 만 보던 UX 문제 해결.
3329
+ `npx connectbase-client docs` 명령이 init/deploy/tunnel 과 달리 brower auth 흐름을 거치지 않아, Public Key 가 없는 사용자가 키 발급 경로를 모른 채 prompt 만 보던 UX 문제 해결.
3229
3330
 
3230
3331
  ### Fixed
3231
3332
 
package/README.md CHANGED
@@ -27,7 +27,7 @@ Connect Base provides **two types** of Keys. Use the right key for your use case
27
27
  |---------|----------|---------|
28
28
  | Frontend SDK (`new ConnectBase()`) | **Public Key** (`cb_pk_`) | Web/app: DB queries, auth, file uploads |
29
29
  | `.env` file (`VITE_CONNECTBASE_PUBLIC_KEY`) | **Public Key** (`cb_pk_`) | React, Vue, etc. |
30
- | CLI deploy (`.connectbaserc`) | **Public Key + Secret Key** (`cb_pk_` + `cb_sk_`) | `npx connectbase deploy` |
30
+ | CLI deploy (`.connectbaserc`) | **Public Key + Secret Key** (`cb_pk_` + `cb_sk_`) | `npx connectbase-client deploy` |
31
31
  | MCP server (AI tools) | **Secret Key** (`cb_sk_`) | Claude, Cursor, Windsurf |
32
32
  | Server-side admin tasks | **Secret Key** (`cb_sk_`) | Backend full data access |
33
33
 
@@ -131,22 +131,27 @@ try {
131
131
 
132
132
  Deploy your web application to Connect Base Web Storage with a single command.
133
133
 
134
- > 🔐 **Deploy commands require a Secret Key (`cb_sk_*`).** The web storage deploy, promote,
135
- > create and list routes require `Authorization: Bearer cb_sk_*` on top of `X-Public-Key`,
136
- > because a Public Key is a public identifier that ships in your browser bundle and cannot
137
- > prove deploy permission. The CLI reads `secretKey` from `.connectbaserc` or the
138
- > `CONNECTBASE_SECRET_KEY` environment variable and sends it automatically — **just update to
139
- > the latest version** (`npm i -g connectbase-client@latest`). Older CLIs fail with
140
- > **401 `SECRET_KEY_REQUIRED`**.
134
+ > **After `npx`, always use the package name `connectbase-client`.** This package installs two
135
+ > bins — `connectbase` and `connectbase-client` — but `connectbase` is a *bin* name, not a
136
+ > package name. npx checks the local `node_modules/.bin` first and otherwise fetches a
137
+ > **package** by that name, so `npx connectbase init` only works in a project that already has
138
+ > this package installed; in an empty folder it fails with npm 404. A global install does not
139
+ > help — npx does not look at `PATH`.
141
140
  >
142
- > **Never commit a Secret Key.** `init` adds `.connectbaserc` to `.gitignore`; in CI, inject it
143
- > from a repository secret instead.
141
+ > Use the short `connectbase` **after** installing: globally, inside `npm run` scripts, or via
142
+ > `node_modules/.bin`.
143
+ >
144
+ > ```bash
145
+ > npx connectbase-client init # nothing installed yet — package name
146
+ > npm install # installs what init added to devDependencies
147
+ > npm run deploy # runs `connectbase deploy ./dist` — short name is fine here
148
+ > ```
144
149
 
145
150
  ### Quick Start
146
151
 
147
152
  ```bash
148
153
  # 1. Initialize (one-time setup)
149
- npx connectbase init
154
+ npx connectbase-client init
150
155
 
151
156
  # 2. Deploy
152
157
  npm run deploy
@@ -173,14 +178,14 @@ The `init` command will:
173
178
  If you prefer not to use `init`, you can pass options directly:
174
179
 
175
180
  ```bash
176
- npx connectbase deploy ./dist -s <storage-id> -k <public-key> --secret-key <secret-key>
181
+ npx connectbase-client deploy ./dist -s <storage-id> -k <public-key> --secret-key <secret-key>
177
182
  ```
178
183
 
179
184
  The Secret Key can also come from `CONNECTBASE_SECRET_KEY` — preferred in CI, so it never
180
185
  appears in shell history or CI logs:
181
186
 
182
187
  ```bash
183
- CONNECTBASE_SECRET_KEY=cb_sk_... npx connectbase deploy ./dist -s <storage-id> -k <public-key>
188
+ CONNECTBASE_SECRET_KEY=cb_sk_... npx connectbase-client deploy ./dist -s <storage-id> -k <public-key>
184
189
  ```
185
190
 
186
191
  ### Options
@@ -204,14 +209,14 @@ Expose a local server to the internet through a secure WebSocket tunnel. Useful
204
209
 
205
210
  ```bash
206
211
  # Expose local port 8084 to the internet
207
- npx connectbase tunnel 8084 -k <public-key>
212
+ npx connectbase-client tunnel 8084 -k <public-key>
208
213
 
209
214
  # With environment variable
210
215
  export CONNECTBASE_PUBLIC_KEY=your-public-key
211
- npx connectbase tunnel 8084
216
+ npx connectbase-client tunnel 8084
212
217
 
213
218
  # For GPU servers or long-running tasks (e.g., image generation)
214
- npx connectbase tunnel 7860 --timeout 300 --max-body 50
219
+ npx connectbase-client tunnel 7860 --timeout 300 --max-body 50
215
220
  ```
216
221
 
217
222
  The tunnel creates a public URL like `https://tunnel.connectbase.world/<tunnel-id>/` that proxies all HTTP requests to your local service.
@@ -241,7 +246,7 @@ on the server, so your SDK only needs the Public Key.
241
246
 
242
247
  ```bash
243
248
  # Start ComfyUI on port 8188, expose it as endpoint label "comfyui-main"
244
- npx connectbase tunnel 8188 --label comfyui-main --description "ComfyUI on my desktop"
249
+ npx connectbase-client tunnel 8188 --label comfyui-main --description "ComfyUI on my desktop"
245
250
  ```
246
251
 
247
252
  Authentication uses your User Secret Key (`cb_sk_*`); the CLI calls the dual-auth
@@ -272,14 +277,14 @@ The `init` command creates `.connectbaserc` automatically. You can also create i
272
277
  export CONNECTBASE_PUBLIC_KEY=cb_pk_your-public-key
273
278
  export CONNECTBASE_SECRET_KEY=cb_sk_your-secret-key # required by deploy / promote
274
279
  export CONNECTBASE_STORAGE_ID=your-storage-id
275
- npx connectbase deploy ./dist
280
+ npx connectbase-client deploy ./dist
276
281
  ```
277
282
 
278
283
  In CI, read the Secret Key from a repository secret:
279
284
 
280
285
  ```yaml
281
286
  # GitHub Actions
282
- - run: npx connectbase deploy ./dist
287
+ - run: npx connectbase-client deploy ./dist
283
288
  env:
284
289
  CONNECTBASE_PUBLIC_KEY: ${{ vars.CONNECTBASE_PUBLIC_KEY }}
285
290
  CONNECTBASE_SECRET_KEY: ${{ secrets.CONNECTBASE_SECRET_KEY }}
package/dist/cli.js CHANGED
@@ -32,6 +32,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  var cli_exports = {};
33
33
  __export(cli_exports, {
34
34
  TunnelConnectSelector: () => TunnelConnectSelector,
35
+ apiErrorMessage: () => apiErrorMessage,
35
36
  computeDeployDiff: () => computeDeployDiff,
36
37
  getTunnelConnectCandidates: () => getTunnelConnectCandidates,
37
38
  guessMimeType: () => guessMimeType,
@@ -147,18 +148,7 @@ function handleTunnelError(msg, appId, localPort) {
147
148
  }
148
149
 
149
150
  // src/cli.ts
150
- function getPackageVersion() {
151
- try {
152
- const pkgPath = path2.join(__dirname, "..", "package.json");
153
- if (fs2.existsSync(pkgPath)) {
154
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
155
- return pkg.version || "0.0.0";
156
- }
157
- } catch {
158
- }
159
- return "3.25.1";
160
- }
161
- var VERSION = getPackageVersion();
151
+ var VERSION = "6.3.1" ? "6.3.1" : getPackageVersion();
162
152
  var DEFAULT_BASE_URL = "https://api.connectbase.world";
163
153
  var colors = {
164
154
  reset: "\x1B[0m",
@@ -311,7 +301,7 @@ function storageAdminHeaders(config) {
311
301
  );
312
302
  log(" \uACE7 \uC11C\uBC84\uAC00 Secret Key \uB97C \uC694\uAD6C\uD569\uB2C8\uB2E4. \uC9C0\uAE08 \uC124\uC815\uD574 \uB450\uC138\uC694.");
313
303
  log(
314
- ` ${colors.cyan}npx connectbase init${colors.reset} \uC774 \uBC1C\uAE09\uD574 .connectbaserc \uC5D0 \uC800\uC7A5\uD569\uB2C8\uB2E4.`
304
+ ` ${colors.cyan}npx connectbase-client init${colors.reset} \uC774 \uBC1C\uAE09\uD574 .connectbaserc \uC5D0 \uC800\uC7A5\uD569\uB2C8\uB2E4.`
315
305
  );
316
306
  log(
317
307
  ` \uC774\uBBF8 \uD0A4\uAC00 \uC788\uB2E4\uBA74 ${colors.cyan}CONNECTBASE_SECRET_KEY${colors.reset} \uD658\uACBD\uBCC0\uC218\uB85C \uC9C0\uC815\uD558\uC138\uC694.`
@@ -431,9 +421,9 @@ async function requestDeployPresign(baseStorageUrl, headers, files, timeoutMs) {
431
421
  );
432
422
  if (res.status === 404) return null;
433
423
  if (res.status < 200 || res.status >= 300) {
434
- const data = res.data;
435
- const msg = typeof data === "object" && data !== null ? data.message || data.error || JSON.stringify(data) : `HTTP ${res.status}`;
436
- throw new Error(`presign \uC694\uCCAD \uC2E4\uD328: ${msg}`);
424
+ throw new Error(
425
+ `presign \uC694\uCCAD \uC2E4\uD328: ${apiErrorMessage(res.data, res.status)}`
426
+ );
437
427
  }
438
428
  const parsed = res.data;
439
429
  return parsed?.urls ?? [];
@@ -591,6 +581,27 @@ async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, ti
591
581
  }
592
582
  }
593
583
  var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
584
+ function apiErrorMessage(data, status) {
585
+ const fallback = `HTTP ${status}`;
586
+ if (typeof data === "string") return data.trim() || fallback;
587
+ if (typeof data !== "object" || data === null) return fallback;
588
+ const body = data;
589
+ const str = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
590
+ let message;
591
+ let suggestion;
592
+ if (typeof body.error === "object" && body.error !== null) {
593
+ const detail = body.error;
594
+ message = str(detail.message) ?? str(detail.code);
595
+ suggestion = str(detail.suggestion);
596
+ } else {
597
+ message = str(body.message) ?? str(body.error);
598
+ }
599
+ if (!message) {
600
+ const raw = JSON.stringify(body);
601
+ return raw === "{}" ? fallback : `${fallback}: ${raw}`;
602
+ }
603
+ return suggestion ? `${message} (${suggestion})` : message;
604
+ }
594
605
  async function makeRequest(url, method, headers, body, reqOpts = {}) {
595
606
  return new Promise((resolve2, reject) => {
596
607
  const parsedUrl = new URL(url);
@@ -667,10 +678,10 @@ async function deploy(directory, config, isDev = false, deployOpts = {}) {
667
678
  const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
668
679
  if (pkg.scripts?.build) {
669
680
  warn(
670
- `\uBC30\uD3EC \uC804 \uBE4C\uB4DC\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694: ${colors.cyan}npm run build && npx connectbase deploy${colors.reset}`
681
+ `\uBC30\uD3EC \uC804 \uBE4C\uB4DC\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694: ${colors.cyan}npm run build && npx connectbase-client deploy${colors.reset}`
671
682
  );
672
683
  warn(
673
- `\uB610\uB294 package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uB4F1\uB85D\uD558\uC138\uC694: ${colors.cyan}npx connectbase init${colors.reset}`
684
+ `\uB610\uB294 package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uB4F1\uB85D\uD558\uC138\uC694: ${colors.cyan}npx connectbase-client init${colors.reset}`
674
685
  );
675
686
  }
676
687
  } catch {
@@ -702,7 +713,7 @@ async function deploy(directory, config, isDev = false, deployOpts = {}) {
702
713
  const envLabel = isDev ? "QA" : "Prod";
703
714
  if (isDev) {
704
715
  warn(
705
- `${colors.yellow}--dev \uB294 deprecated${colors.reset} \uC785\uB2C8\uB2E4 \u2014 QA \uCC44\uB110\uB85C \uBC30\uD3EC\uD569\uB2C8\uB2E4(production \uBBF8\uBC18\uC601). \uC774\uD6C4 ${colors.cyan}npx connectbase promote${colors.reset} \uB85C production \uC5D0 \uBC18\uC601\uD558\uC138\uC694. promote_mode=manual \uC774\uBA74 \uC77C\uBC18 ${colors.cyan}deploy${colors.reset} \uB3C4 \uB3D9\uC77C\uD558\uAC8C QA \uB85C \uBC30\uD3EC\uB429\uB2C8\uB2E4.`
716
+ `${colors.yellow}--dev \uB294 deprecated${colors.reset} \uC785\uB2C8\uB2E4 \u2014 QA \uCC44\uB110\uB85C \uBC30\uD3EC\uD569\uB2C8\uB2E4(production \uBBF8\uBC18\uC601). \uC774\uD6C4 ${colors.cyan}npx connectbase-client promote${colors.reset} \uB85C production \uC5D0 \uBC18\uC601\uD558\uC138\uC694. promote_mode=manual \uC774\uBA74 \uC77C\uBC18 ${colors.cyan}deploy${colors.reset} \uB3C4 \uB3D9\uC77C\uD558\uAC8C QA \uB85C \uBC30\uD3EC\uB429\uB2C8\uB2E4.`
706
717
  );
707
718
  }
708
719
  const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
@@ -779,9 +790,9 @@ async function tryFetchManifest(baseStorageUrl, headers) {
779
790
  );
780
791
  if (res.status === 404) return null;
781
792
  if (res.status < 200 || res.status >= 300) {
782
- const data = res.data;
783
- const msg = typeof data === "object" && data !== null ? data.message || data.error || JSON.stringify(data) : `HTTP ${res.status}`;
784
- throw new Error(`manifest \uC870\uD68C \uC2E4\uD328: ${msg}`);
793
+ throw new Error(
794
+ `manifest \uC870\uD68C \uC2E4\uD328: ${apiErrorMessage(res.data, res.status)}`
795
+ );
785
796
  }
786
797
  const parsed = res.data;
787
798
  if (!parsed || !Array.isArray(parsed.files)) {
@@ -918,7 +929,7 @@ ${colors.cyan}Production: ${dep.production_url}${colors.reset}`
918
929
  log(`
919
930
  ${colors.yellow}QA URL: ${dep.qa_url}${colors.reset}`);
920
931
  log(
921
- `${colors.dim}\uAC80\uC99D \uD6C4 \uBC18\uC601: ${colors.cyan}npx connectbase promote${colors.reset}
932
+ `${colors.dim}\uAC80\uC99D \uD6C4 \uBC18\uC601: ${colors.cyan}npx connectbase-client promote${colors.reset}
922
933
  `
923
934
  );
924
935
  }
@@ -931,8 +942,7 @@ ${colors.cyan}URL: ${data.url}${colors.reset}
931
942
  `);
932
943
  }
933
944
  } else {
934
- const data = response.data;
935
- const errorMsg = typeof data === "object" && data !== null ? data.message || data.error || JSON.stringify(data) : typeof data === "string" ? data : `HTTP ${response.status}`;
945
+ const errorMsg = apiErrorMessage(response.data, response.status);
936
946
  if (!reportDeployAuthError(response.status, response.data)) {
937
947
  error(`\uBC30\uD3EC \uC2E4\uD328: ${errorMsg}`);
938
948
  }
@@ -961,8 +971,7 @@ async function promote(config, deploymentId) {
961
971
  process.exit(1);
962
972
  }
963
973
  if (res.status < 200 || res.status >= 300) {
964
- const data2 = res.data;
965
- const msg = typeof data2 === "object" && data2 !== null ? data2.error || data2.message || JSON.stringify(data2) : `HTTP ${res.status}`;
974
+ const msg = apiErrorMessage(res.data, res.status);
966
975
  if (!reportDeployAuthError(res.status, res.data)) {
967
976
  error(`promote \uC2E4\uD328: ${msg}`);
968
977
  }
@@ -985,8 +994,7 @@ async function listReleases(config) {
985
994
  headers
986
995
  );
987
996
  if (res.status < 200 || res.status >= 300) {
988
- const data2 = res.data;
989
- const msg = typeof data2 === "object" && data2 !== null ? data2.error || data2.message || JSON.stringify(data2) : `HTTP ${res.status}`;
997
+ const msg = apiErrorMessage(res.data, res.status);
990
998
  if (!reportDeployAuthError(res.status, res.data)) {
991
999
  error(`\uBC30\uD3EC \uC774\uB825 \uC870\uD68C \uC2E4\uD328: ${msg}`);
992
1000
  }
@@ -1019,7 +1027,7 @@ ${colors.cyan}\uBC30\uD3EC \uC774\uB825${colors.reset} (promote_mode: ${data.pro
1019
1027
  if (data.qa_url)
1020
1028
  log(`${colors.yellow}QA: ${data.qa_url}${colors.reset}`);
1021
1029
  log(
1022
- `${colors.dim}\uB864\uBC31: npx connectbase promote <deployment_id>${colors.reset}
1030
+ `${colors.dim}\uB864\uBC31: npx connectbase-client promote <deployment_id>${colors.reset}
1023
1031
  `
1024
1032
  );
1025
1033
  }
@@ -1086,7 +1094,9 @@ function getProjectRoot() {
1086
1094
  const gitRoot = getGitRoot();
1087
1095
  return gitRoot || process.cwd();
1088
1096
  }
1089
- var CONSOLE_URL = DEFAULT_BASE_URL.replace("api.", "");
1097
+ function consoleUrlFor(baseUrl) {
1098
+ return baseUrl.replace("api.", "");
1099
+ }
1090
1100
  function openBrowser(url) {
1091
1101
  const { exec } = require("child_process");
1092
1102
  const platform2 = process.platform;
@@ -1101,21 +1111,21 @@ function openBrowser(url) {
1101
1111
  exec(command, () => {
1102
1112
  });
1103
1113
  }
1104
- async function browserAuthFlow() {
1114
+ async function browserAuthFlow(baseUrl = DEFAULT_BASE_URL) {
1105
1115
  info("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D \uC138\uC158\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4...");
1106
1116
  const startRes = await makeRequest(
1107
- `${DEFAULT_BASE_URL}/v1/public/cli-auth/start`,
1117
+ `${baseUrl}/v1/public/cli-auth/start`,
1108
1118
  "POST",
1109
1119
  {}
1110
1120
  );
1111
1121
  if (startRes.status !== 200) {
1112
- const errData = startRes.data;
1113
- const detail = errData?.error || errData?.message || `HTTP ${startRes.status}`;
1114
- error(`\uC778\uC99D \uC138\uC158 \uC2DC\uC791\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: ${detail}`);
1122
+ error(
1123
+ `\uC778\uC99D \uC138\uC158 \uC2DC\uC791\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: ${apiErrorMessage(startRes.data, startRes.status)}`
1124
+ );
1115
1125
  process.exit(1);
1116
1126
  }
1117
1127
  const { session_id } = startRes.data;
1118
- const authUrl = `${CONSOLE_URL}/auth/cli-auth?session=${session_id}`;
1128
+ const authUrl = `${consoleUrlFor(baseUrl)}/auth/cli-auth?session=${session_id}`;
1119
1129
  log(`
1120
1130
  ${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC138\uC694:${colors.reset}`);
1121
1131
  log(`${colors.dim}${authUrl}${colors.reset}
@@ -1135,7 +1145,7 @@ ${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC13
1135
1145
  await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
1136
1146
  try {
1137
1147
  const pollRes = await makeRequest(
1138
- `${DEFAULT_BASE_URL}/v1/public/cli-auth/poll/${session_id}`,
1148
+ `${baseUrl}/v1/public/cli-auth/poll/${session_id}`,
1139
1149
  "GET",
1140
1150
  {}
1141
1151
  );
@@ -1182,10 +1192,73 @@ ${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC13
1182
1192
  error("\uC778\uC99D \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
1183
1193
  process.exit(1);
1184
1194
  }
1195
+ async function promptForSecretKey(baseUrl = DEFAULT_BASE_URL) {
1196
+ log(`
1197
+ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
1198
+ log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
1199
+ log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
1200
+ `);
1201
+ const authChoice = await prompt(
1202
+ `${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `
1203
+ );
1204
+ if (authChoice === "2") {
1205
+ try {
1206
+ return await browserAuthFlow(baseUrl);
1207
+ } catch (err) {
1208
+ error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
1209
+ info(
1210
+ "\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"
1211
+ );
1212
+ process.exit(1);
1213
+ }
1214
+ }
1215
+ log(
1216
+ `${colors.dim}Secret Key (cb_sk_): \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uBC1C\uAE09${colors.reset}
1217
+ `
1218
+ );
1219
+ const entered = await promptSecret(
1220
+ `${colors.blue}?${colors.reset} Secret Key: `
1221
+ );
1222
+ if (!entered) {
1223
+ error("Secret Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
1224
+ process.exit(1);
1225
+ }
1226
+ if (entered.startsWith("cb_pk_")) {
1227
+ error("Public Key\uAC00 \uC544\uB2CC Secret Key(cb_sk_)\uB97C \uC785\uB825\uD558\uC138\uC694");
1228
+ info("Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
1229
+ process.exit(1);
1230
+ }
1231
+ if (!entered.startsWith("cb_sk_")) {
1232
+ error(
1233
+ "\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"
1234
+ );
1235
+ process.exit(1);
1236
+ }
1237
+ return entered;
1238
+ }
1239
+ function saveSecretKeyToRc(secretKey, dir = process.cwd()) {
1240
+ const rcPath = path2.join(dir, ".connectbaserc");
1241
+ let rcData = {};
1242
+ if (fs2.existsSync(rcPath)) {
1243
+ try {
1244
+ rcData = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
1245
+ } catch {
1246
+ }
1247
+ }
1248
+ rcData.secretKey = secretKey;
1249
+ fs2.writeFileSync(rcPath, `${JSON.stringify(rcData, null, 2)}
1250
+ `);
1251
+ }
1252
+ async function fetchAppsWithSecretKey(baseUrl, secretKey) {
1253
+ return makeRequest(`${baseUrl}/v1/public/cli/apps`, "GET", {
1254
+ "X-Public-Key": secretKey
1255
+ });
1256
+ }
1185
1257
  async function init(config = {}, opts = {}) {
1186
1258
  log(`
1187
1259
  ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.reset}
1188
1260
  `);
1261
+ const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
1189
1262
  const cwd = process.cwd();
1190
1263
  const projectRoot = getProjectRoot();
1191
1264
  if (cwd !== projectRoot) {
@@ -1199,7 +1272,7 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
1199
1272
  if (!process.stdin.isTTY && !nonInteractive) {
1200
1273
  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");
1201
1274
  log(
1202
- `${colors.dim}\uC608: npx connectbase init --yes --secret-key cb_sk_... [--app <\uC571ID>] [--storage <\uC2A4\uD1A0\uB9AC\uC9C0ID>] [--deploy-dir ./dist]${colors.reset}`
1275
+ `${colors.dim}\uC608: npx connectbase-client init --yes --secret-key cb_sk_... [--app <\uC571ID>] [--storage <\uC2A4\uD1A0\uB9AC\uC9C0ID>] [--deploy-dir ./dist]${colors.reset}`
1203
1276
  );
1204
1277
  log(
1205
1278
  `${colors.dim}Secret Key\uB294 CONNECTBASE_SECRET_KEY \uD658\uACBD\uBCC0\uC218\uB85C\uB3C4 \uC804\uB2EC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4${colors.reset}`
@@ -1221,10 +1294,14 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
1221
1294
  process.exit(1);
1222
1295
  }
1223
1296
  } else {
1297
+ info(
1298
+ ".connectbaserc\uAC00 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uAE30\uC874 \uC571/\uD0A4 \uC124\uC815\uC744 \uC7AC\uC0AC\uC6A9\uD574 \uB2E4\uC2DC \uAD6C\uC131\uD569\uB2C8\uB2E4"
1299
+ );
1224
1300
  const overwrite = await prompt(
1225
- `${colors.yellow}\u26A0${colors.reset} .connectbaserc\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? (y/N): `
1301
+ `${colors.blue}?${colors.reset} \uACC4\uC18D\uD560\uAE4C\uC694? (Y/n): `
1226
1302
  );
1227
- if (overwrite.toLowerCase() !== "y") {
1303
+ const answer = overwrite.toLowerCase();
1304
+ if (answer === "n" || answer === "no") {
1228
1305
  info("\uCD08\uAE30\uD654\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
1229
1306
  return;
1230
1307
  }
@@ -1238,57 +1315,14 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
1238
1315
  );
1239
1316
  process.exit(1);
1240
1317
  }
1241
- info("Secret Key \uD655\uC778\uB428 (\uD50C\uB798\uADF8/\uD658\uACBD\uBCC0\uC218/.connectbaserc)");
1318
+ info("\uC800\uC7A5\uB41C Secret Key \uC0AC\uC6A9 (\uD50C\uB798\uADF8/\uD658\uACBD\uBCC0\uC218/.connectbaserc)");
1242
1319
  } else if (nonInteractive) {
1243
1320
  error(
1244
1321
  "Secret Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: --secret-key cb_sk_... \uB610\uB294 CONNECTBASE_SECRET_KEY \uD658\uACBD\uBCC0\uC218"
1245
1322
  );
1246
1323
  process.exit(1);
1247
1324
  } else {
1248
- log(`
1249
- ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
1250
- log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
1251
- log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
1252
- `);
1253
- const authChoice = await prompt(
1254
- `${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `
1255
- );
1256
- if (authChoice === "2") {
1257
- try {
1258
- secretKey = await browserAuthFlow();
1259
- } catch (err) {
1260
- error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
1261
- info(
1262
- "\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"
1263
- );
1264
- process.exit(1);
1265
- }
1266
- } else {
1267
- log(
1268
- `${colors.dim}Secret Key (cb_sk_): \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uBC1C\uAE09${colors.reset}
1269
- `
1270
- );
1271
- secretKey = await promptSecret(
1272
- `${colors.blue}?${colors.reset} Secret Key: `
1273
- );
1274
- if (!secretKey) {
1275
- error("Secret Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
1276
- process.exit(1);
1277
- }
1278
- if (secretKey.startsWith("cb_pk_")) {
1279
- error("Public Key\uAC00 \uC544\uB2CC Secret Key(cb_sk_)\uB97C \uC785\uB825\uD558\uC138\uC694");
1280
- info(
1281
- "Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4"
1282
- );
1283
- process.exit(1);
1284
- }
1285
- if (!secretKey.startsWith("cb_sk_")) {
1286
- error(
1287
- "\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"
1288
- );
1289
- process.exit(1);
1290
- }
1291
- }
1325
+ secretKey = await promptForSecretKey(baseUrl);
1292
1326
  }
1293
1327
  let appId = "";
1294
1328
  let publicKey = "";
@@ -1304,7 +1338,7 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
1304
1338
  const issuePublicKey = async (targetAppId) => {
1305
1339
  info("Public Key \uC0DD\uC131 \uC911...");
1306
1340
  const newKeyRes = await makeRequest(
1307
- `${DEFAULT_BASE_URL}/v1/public/cli/apps/${targetAppId}/public-keys`,
1341
+ `${baseUrl}/v1/public/cli/apps/${targetAppId}/public-keys`,
1308
1342
  "POST",
1309
1343
  { "X-Public-Key": secretKey },
1310
1344
  JSON.stringify({})
@@ -1319,14 +1353,26 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
1319
1353
  };
1320
1354
  try {
1321
1355
  info("\uC571 \uBAA9\uB85D \uC870\uD68C \uC911...");
1322
- const appsRes = await makeRequest(
1323
- `${DEFAULT_BASE_URL}/v1/public/cli/apps`,
1324
- "GET",
1325
- { "X-Public-Key": secretKey }
1326
- );
1356
+ let appsRes = await fetchAppsWithSecretKey(baseUrl, secretKey);
1327
1357
  if (appsRes.status === 401) {
1328
- error("Secret Key\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uCF58\uC194\uC5D0\uC11C \uD0A4\uB97C \uD655\uC778\uD558\uC138\uC694");
1329
- process.exit(1);
1358
+ warn("\uC800\uC7A5\uB41C Secret Key\uAC00 \uB354 \uC774\uC0C1 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (\uB9CC\uB8CC \uB610\uB294 \uCDE8\uC18C)");
1359
+ if (nonInteractive) {
1360
+ error(
1361
+ "\uC0C8 Secret Key\uB97C --secret-key cb_sk_... \uB610\uB294 CONNECTBASE_SECRET_KEY \uB85C \uC804\uB2EC\uD558\uC138\uC694"
1362
+ );
1363
+ process.exit(1);
1364
+ }
1365
+ info("\uC0C8 Secret Key\uB85C \uB2E4\uC2DC \uC778\uC99D\uD569\uB2C8\uB2E4");
1366
+ secretKey = await promptForSecretKey(baseUrl);
1367
+ appsRes = await fetchAppsWithSecretKey(baseUrl, secretKey);
1368
+ if (appsRes.status === 401) {
1369
+ error(
1370
+ "\uC0C8\uB85C \uBC1B\uC740 Secret Key\uB3C4 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uD0A4 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694"
1371
+ );
1372
+ process.exit(1);
1373
+ }
1374
+ saveSecretKeyToRc(secretKey, cwd);
1375
+ success("\uC0C8 Secret Key\uB97C .connectbaserc\uC5D0 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4");
1330
1376
  }
1331
1377
  if (appsRes.status !== 200) {
1332
1378
  throw new Error(`HTTP ${appsRes.status}`);
@@ -1376,7 +1422,7 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `
1376
1422
  ) || projectName;
1377
1423
  info("\uC571 \uC0DD\uC131 \uC911...");
1378
1424
  const createRes = await makeRequest(
1379
- `${DEFAULT_BASE_URL}/v1/public/cli/apps`,
1425
+ `${baseUrl}/v1/public/cli/apps`,
1380
1426
  "POST",
1381
1427
  { "X-Public-Key": secretKey },
1382
1428
  JSON.stringify({ name: appName })
@@ -1386,8 +1432,9 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `
1386
1432
  process.exit(1);
1387
1433
  }
1388
1434
  if (createRes.status !== 201) {
1389
- const data = createRes.data;
1390
- error(`\uC571 \uC0DD\uC131 \uC2E4\uD328: ${data?.error || `HTTP ${createRes.status}`}`);
1435
+ error(
1436
+ `\uC571 \uC0DD\uC131 \uC2E4\uD328: ${apiErrorMessage(createRes.data, createRes.status)}`
1437
+ );
1391
1438
  process.exit(1);
1392
1439
  }
1393
1440
  const createData = createRes.data;
@@ -1431,7 +1478,7 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `
1431
1478
  try {
1432
1479
  info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC870\uD68C \uC911...");
1433
1480
  const listRes = await makeRequest(
1434
- `${DEFAULT_BASE_URL}/v1/public/storages/webs`,
1481
+ `${baseUrl}/v1/public/storages/webs`,
1435
1482
  "GET",
1436
1483
  {
1437
1484
  "X-Public-Key": publicKeyForSdk,
@@ -1471,27 +1518,34 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `
1471
1518
  const name = nonInteractive ? projectName : await prompt(
1472
1519
  `${colors.blue}?${colors.reset} \uC2A4\uD1A0\uB9AC\uC9C0 \uC774\uB984 (${projectName}): `
1473
1520
  ) || projectName;
1474
- info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC911...");
1475
- const createRes = await makeRequest(
1476
- `${DEFAULT_BASE_URL}/v1/public/storages/webs`,
1477
- "POST",
1478
- {
1479
- "X-Public-Key": publicKeyForSdk,
1480
- "X-User-Secret-Key": secretKey,
1481
- Authorization: `Bearer ${secretKey}`
1482
- },
1483
- JSON.stringify({ name })
1484
- );
1485
- if (createRes.status !== 200) {
1486
- const data = createRes.data;
1487
- error(
1488
- `\uC0DD\uC131 \uC2E4\uD328: ${data?.error || data?.message || `HTTP ${createRes.status}`}`
1521
+ const sameName = storages.find((s) => s.name === name);
1522
+ if (sameName) {
1523
+ storageId = sameName.id;
1524
+ success(
1525
+ `\uAC19\uC740 \uC774\uB984\uC758 \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0\uAC00 \uC774\uBBF8 \uC788\uC5B4 \uC7AC\uC0AC\uC6A9\uD569\uB2C8\uB2E4: ${sameName.name}`
1526
+ );
1527
+ } else {
1528
+ info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC911...");
1529
+ const createRes = await makeRequest(
1530
+ `${baseUrl}/v1/public/storages/webs`,
1531
+ "POST",
1532
+ {
1533
+ "X-Public-Key": publicKeyForSdk,
1534
+ "X-User-Secret-Key": secretKey,
1535
+ Authorization: `Bearer ${secretKey}`
1536
+ },
1537
+ JSON.stringify({ name })
1489
1538
  );
1490
- process.exit(1);
1539
+ if (createRes.status !== 200) {
1540
+ error(
1541
+ `\uC0DD\uC131 \uC2E4\uD328: ${apiErrorMessage(createRes.data, createRes.status)}`
1542
+ );
1543
+ process.exit(1);
1544
+ }
1545
+ const createData = createRes.data;
1546
+ storageId = createData.storage_web_id;
1547
+ success(`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC644\uB8CC: ${createData.name}`);
1491
1548
  }
1492
- const createData = createRes.data;
1493
- storageId = createData.storage_web_id;
1494
- success(`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC644\uB8CC: ${createData.name}`);
1495
1549
  }
1496
1550
  } else {
1497
1551
  throw new Error(`HTTP ${listRes.status}`);
@@ -1529,12 +1583,17 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `
1529
1583
  `);
1530
1584
  success(".connectbaserc \uC0DD\uC131 \uC644\uB8CC");
1531
1585
  addToGitignore(".connectbaserc");
1532
- addDeployScript(deployDir);
1533
- await setupClaudeCode(secretKey, projectRoot);
1586
+ const { needsInstall } = addDeployScript(deployDir);
1587
+ await setupClaudeCode(secretKey, projectRoot, baseUrl);
1534
1588
  log(`
1535
1589
  ${colors.green}\uCD08\uAE30\uD654 \uC644\uB8CC!${colors.reset}
1536
1590
  `);
1537
1591
  log(`${colors.dim}\uBC30\uD3EC\uD558\uB824\uBA74:${colors.reset}`);
1592
+ if (needsInstall) {
1593
+ log(
1594
+ ` ${colors.cyan}npm install${colors.reset} ${colors.dim}# connectbase-client (CLI \uD3EC\uD568) \uC124\uCE58${colors.reset}`
1595
+ );
1596
+ }
1538
1597
  log(` ${colors.cyan}npm run deploy${colors.reset}`);
1539
1598
  log(`
1540
1599
  ${colors.dim}Claude Code\uC5D0\uC11C MCP\uAC00 \uC790\uB3D9 \uC5F0\uACB0\uB429\uB2C8\uB2E4${colors.reset}
@@ -1589,31 +1648,46 @@ function addDeployScript(deployDir) {
1589
1648
  const pkgPath = path2.join(process.cwd(), "package.json");
1590
1649
  if (!fs2.existsSync(pkgPath)) {
1591
1650
  warn("package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
1592
- return;
1651
+ return { needsInstall: false };
1593
1652
  }
1594
1653
  try {
1595
1654
  const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
1596
1655
  if (!pkg.scripts) pkg.scripts = {};
1656
+ const declared = (name) => Boolean(pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
1657
+ let needsInstall = false;
1658
+ if (!declared("connectbase-client")) {
1659
+ if (!pkg.devDependencies) pkg.devDependencies = {};
1660
+ pkg.devDependencies["connectbase-client"] = VERSION === "0.0.0" ? "latest" : `^${VERSION}`;
1661
+ needsInstall = true;
1662
+ }
1597
1663
  if (pkg.scripts.deploy) {
1598
1664
  info(
1599
1665
  `package.json\uC5D0 \uC774\uBBF8 deploy \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC788\uC2B5\uB2C8\uB2E4: "${pkg.scripts.deploy}"`
1600
1666
  );
1601
- return;
1667
+ } else {
1668
+ const deployCmd = `connectbase deploy ${deployDir}`;
1669
+ pkg.scripts.deploy = pkg.scripts.build ? `npm run build && ${deployCmd}` : deployCmd;
1670
+ success("package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC");
1602
1671
  }
1603
- const deployCmd = `connectbase deploy ${deployDir}`;
1604
- pkg.scripts.deploy = pkg.scripts.build ? `${pkg.scripts.build} && ${deployCmd}` : deployCmd;
1605
1672
  fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
1606
1673
  `);
1607
- success(`package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC`);
1674
+ if (needsInstall) {
1675
+ success(
1676
+ `package.json devDependencies\uC5D0 connectbase-client@${pkg.devDependencies["connectbase-client"]} \uCD94\uAC00 \uC644\uB8CC`
1677
+ );
1678
+ }
1679
+ return { needsInstall };
1608
1680
  } catch {
1609
1681
  warn("package.json \uC218\uC815\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4");
1682
+ return { needsInstall: false };
1610
1683
  }
1611
1684
  }
1612
1685
  function getGitRoot() {
1613
1686
  try {
1614
1687
  const { execSync } = require("child_process");
1615
1688
  return execSync("git rev-parse --show-toplevel", {
1616
- encoding: "utf-8"
1689
+ encoding: "utf-8",
1690
+ stdio: ["ignore", "pipe", "ignore"]
1617
1691
  }).trim();
1618
1692
  } catch {
1619
1693
  return null;
@@ -1715,6 +1789,7 @@ var LEGACY_DOC_FILES = [
1715
1789
  ];
1716
1790
  async function downloadDocs(baseDir, opts = {}) {
1717
1791
  const quiet = opts.quiet === true;
1792
+ const baseUrl = opts.baseUrl || DEFAULT_BASE_URL;
1718
1793
  if (!baseDir) {
1719
1794
  baseDir = getProjectRoot();
1720
1795
  }
@@ -1736,7 +1811,7 @@ async function downloadDocs(baseDir, opts = {}) {
1736
1811
  if (!quiet) info("SDK \uAC1C\uBC1C \uAC00\uC774\uB4DC \uB2E4\uC6B4\uB85C\uB4DC \uC911...");
1737
1812
  try {
1738
1813
  const res = await makeRequest(
1739
- `${DEFAULT_BASE_URL}/v1/storages/webs/claude-md?template=rules&format=sections`,
1814
+ `${baseUrl}/v1/storages/webs/claude-md?template=rules&format=sections`,
1740
1815
  "GET",
1741
1816
  {}
1742
1817
  );
@@ -1769,7 +1844,7 @@ async function downloadDocs(baseDir, opts = {}) {
1769
1844
  }
1770
1845
  return "unchanged";
1771
1846
  }
1772
- 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 -->
1847
+ 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-client update'\uB85C \uAC31\uC2E0\uD558\uC138\uC694 -->
1773
1848
 
1774
1849
  `;
1775
1850
  for (const section of sections) {
@@ -1849,7 +1924,7 @@ function updateRootClaudeMd(claudeMdPath, quiet = false) {
1849
1924
 
1850
1925
  1. \uC704\uC5D0 import\uB41C project-rules.md\uC758 \uAE30\uB2A5 \uB9E4\uD551\uD45C\xB7\uBCF4\uC548 \uADDC\uCE59\uC744 \uB530\uB974\uC138\uC694
1851
1926
  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
1852
- 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)
1927
+ 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-client mcp\` \uC2E4\uD589\uC744 \uC548\uB0B4\uD558\uC138\uC694 (Secret Key \uD544\uC694)
1853
1928
  ${endMarker}`;
1854
1929
  if (fs2.existsSync(claudeMdPath)) {
1855
1930
  let content = fs2.readFileSync(claudeMdPath, "utf-8");
@@ -1885,7 +1960,7 @@ function createSubPackageClaudeMd(subClaudeMdPath, gitRoot, quiet = false) {
1885
1960
 
1886
1961
  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
1887
1962
  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
1888
- 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
1963
+ 3. \`search_sdk_docs\` \uB3C4\uAD6C\uAC00 \uBCF4\uC774\uC9C0 \uC54A\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \`npx connectbase-client mcp\` \uC2E4\uD589\uC744 \uC548\uB0B4\uD558\uC138\uC694
1889
1964
  ${endMarker}`;
1890
1965
  if (fs2.existsSync(subClaudeMdPath)) {
1891
1966
  let content = fs2.readFileSync(subClaudeMdPath, "utf-8");
@@ -1913,7 +1988,7 @@ ${subBlock}
1913
1988
  }
1914
1989
  if (!quiet) success(`\uC11C\uBE0C \uD328\uD0A4\uC9C0 CLAUDE.md \uC0DD\uC131 \uC644\uB8CC: ${subClaudeMdPath}`);
1915
1990
  }
1916
- async function setupMcp(secretKey) {
1991
+ async function setupMcp(secretKey, baseUrl = DEFAULT_BASE_URL) {
1917
1992
  const gitRoot = getGitRoot();
1918
1993
  const root = gitRoot || process.cwd();
1919
1994
  const monorepo = detectMonorepo(root);
@@ -1924,51 +1999,31 @@ async function setupMcp(secretKey) {
1924
1999
  }
1925
2000
  }
1926
2001
  const mcpConfigPath = path2.join(root, ".mcp.json");
2002
+ if (secretKey) {
2003
+ const check = await fetchAppsWithSecretKey(baseUrl, secretKey).catch(
2004
+ () => null
2005
+ );
2006
+ if (check?.status === 401) {
2007
+ warn("\uC800\uC7A5\uB41C Secret Key\uAC00 \uB354 \uC774\uC0C1 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (\uB9CC\uB8CC \uB610\uB294 \uCDE8\uC18C)");
2008
+ secretKey = "";
2009
+ }
2010
+ }
1927
2011
  if (!secretKey) {
1928
2012
  if (!process.stdin.isTTY) {
1929
- error("Secret Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (\uBE44\uB300\uD654\uD615 \uD658\uACBD)");
2013
+ error("\uC720\uD6A8\uD55C Secret Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (\uBE44\uB300\uD654\uD615 \uD658\uACBD)");
1930
2014
  log(`${colors.dim}\uB2E4\uC74C \uC911 \uD558\uB098\uB85C \uD0A4\uB97C \uC804\uB2EC\uD558\uC138\uC694:${colors.reset}`);
1931
2015
  log(
1932
- ` ${colors.cyan}npx connectbase mcp --secret-key cb_sk_...${colors.reset}`
2016
+ ` ${colors.cyan}npx connectbase-client mcp --secret-key cb_sk_...${colors.reset}`
1933
2017
  );
1934
2018
  log(
1935
- ` ${colors.cyan}CONNECTBASE_SECRET_KEY=cb_sk_... npx connectbase mcp${colors.reset}`
2019
+ ` ${colors.cyan}CONNECTBASE_SECRET_KEY=cb_sk_... npx connectbase-client mcp${colors.reset}`
1936
2020
  );
1937
2021
  process.exit(1);
1938
2022
  }
1939
- log(`
1940
- ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
1941
- log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
1942
- log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
1943
- `);
1944
- const authChoice = await prompt(
1945
- `${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `
1946
- );
1947
- if (authChoice === "2") {
1948
- try {
1949
- secretKey = await browserAuthFlow();
1950
- const skRcPath = path2.join(process.cwd(), ".connectbaserc");
1951
- let rcData = {};
1952
- if (fs2.existsSync(skRcPath)) {
1953
- try {
1954
- rcData = JSON.parse(fs2.readFileSync(skRcPath, "utf-8"));
1955
- } catch {
1956
- }
1957
- }
1958
- rcData.secretKey = secretKey;
1959
- fs2.writeFileSync(skRcPath, `${JSON.stringify(rcData, null, 2)}
1960
- `);
1961
- addToGitignore(".connectbaserc", root);
1962
- success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
1963
- } catch (err) {
1964
- error(`\uC778\uC99D \uC2E4\uD328: ${err instanceof Error ? err.message : err}`);
1965
- process.exit(1);
1966
- }
1967
- } else {
1968
- secretKey = await promptSecret(
1969
- `${colors.blue}?${colors.reset} Secret Key: `
1970
- );
1971
- }
2023
+ secretKey = await promptForSecretKey(baseUrl);
2024
+ saveSecretKeyToRc(secretKey);
2025
+ addToGitignore(".connectbaserc", root);
2026
+ success("Secret Key\uB97C .connectbaserc\uC5D0 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4");
1972
2027
  }
1973
2028
  const mcpEntry = {
1974
2029
  type: "http",
@@ -2007,10 +2062,10 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
2007
2062
  ${colors.dim}Claude Code\uC5D0\uC11C ConnectBase MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.${colors.reset}`
2008
2063
  );
2009
2064
  }
2010
- async function setupClaudeCode(secretKey, projectRoot) {
2065
+ async function setupClaudeCode(secretKey, projectRoot, baseUrl = DEFAULT_BASE_URL) {
2011
2066
  const root = projectRoot || getProjectRoot();
2012
- await downloadDocs(root);
2013
- await setupMcp(secretKey);
2067
+ await downloadDocs(root, { baseUrl });
2068
+ await setupMcp(secretKey, baseUrl);
2014
2069
  }
2015
2070
  async function fetchLatestVersion(packageName) {
2016
2071
  try {
@@ -2118,7 +2173,7 @@ ${colors.cyan}ConnectBase Update${colors.reset}
2118
2173
  if (!opts.skipDocs) {
2119
2174
  log("");
2120
2175
  info("SDK \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8 \uC911...");
2121
- await downloadDocs(projectRoot);
2176
+ await downloadDocs(projectRoot, { baseUrl: config.baseUrl });
2122
2177
  }
2123
2178
  if (!opts.skipMcp) {
2124
2179
  log("");
@@ -2203,15 +2258,15 @@ function setupMonorepoRoot(projectRoot, monorepo) {
2203
2258
  if (!pkg.scripts) pkg.scripts = {};
2204
2259
  let changed = false;
2205
2260
  if (!pkg.scripts["cb:update"]) {
2206
- pkg.scripts["cb:update"] = "npx connectbase update";
2261
+ pkg.scripts["cb:update"] = "npx connectbase-client update";
2207
2262
  changed = true;
2208
2263
  }
2209
2264
  if (!pkg.scripts["cb:docs"]) {
2210
- pkg.scripts["cb:docs"] = "npx connectbase docs";
2265
+ pkg.scripts["cb:docs"] = "npx connectbase-client docs";
2211
2266
  changed = true;
2212
2267
  }
2213
2268
  if (!pkg.scripts["cb:mcp"]) {
2214
- pkg.scripts["cb:mcp"] = "npx connectbase mcp";
2269
+ pkg.scripts["cb:mcp"] = "npx connectbase-client mcp";
2215
2270
  changed = true;
2216
2271
  }
2217
2272
  if (changed) {
@@ -2636,8 +2691,7 @@ ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `
2636
2691
  process.exit(1);
2637
2692
  }
2638
2693
  if (createRes.status !== 201) {
2639
- const data = createRes.data;
2640
- error(`\uC571 \uC0DD\uC131 \uC2E4\uD328: ${data?.error || `HTTP ${createRes.status}`}`);
2694
+ error(`\uC571 \uC0DD\uC131 \uC2E4\uD328: ${apiErrorMessage(createRes.data, createRes.status)}`);
2641
2695
  process.exit(1);
2642
2696
  }
2643
2697
  const createData = createRes.data;
@@ -3403,7 +3457,7 @@ function showHelp() {
3403
3457
  ${colors.cyan}connectbase${colors.reset} - Connect Base SDK & CLI
3404
3458
 
3405
3459
  ${colors.yellow}\uC0AC\uC6A9\uBC95:${colors.reset}
3406
- npx connectbase <command> [options]
3460
+ npx connectbase-client <command> [options]
3407
3461
 
3408
3462
  ${colors.yellow}\uBA85\uB839\uC5B4:${colors.reset}
3409
3463
  init \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654 (\uC571 \uC0DD\uC131, MCP \uC124\uC815, SDK \uBB38\uC11C \uB2E4\uC6B4\uB85C\uB4DC)
@@ -3451,36 +3505,36 @@ ${colors.yellow}\uC635\uC158:${colors.reset}
3451
3505
 
3452
3506
  ${colors.yellow}\uBE60\uB978 \uC2DC\uC791:${colors.reset}
3453
3507
  ${colors.dim}# 1. \uCD08\uAE30\uD654 (\uCD5C\uCD08 1\uD68C)${colors.reset}
3454
- npx connectbase init
3508
+ npx connectbase-client init
3455
3509
 
3456
3510
  ${colors.dim}# 1-1. \uBE44\uB300\uD654\uD615 \uCD08\uAE30\uD654 (CI/AI \uC5D0\uC774\uC804\uD2B8)${colors.reset}
3457
- npx connectbase init --yes --secret-key cb_sk_...
3511
+ npx connectbase-client init --yes --secret-key cb_sk_...
3458
3512
 
3459
3513
  ${colors.dim}# 2. SDK \uC804\uCCB4 \uC5C5\uB370\uC774\uD2B8 (\uBC84\uC804 \uCCB4\uD06C + \uBB38\uC11C + MCP)${colors.reset}
3460
- npx connectbase update
3514
+ npx connectbase-client update
3461
3515
 
3462
3516
  ${colors.dim}# 2-1. \uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8 \uC124\uCE58${colors.reset}
3463
- npx connectbase update --setup-root
3517
+ npx connectbase-client update --setup-root
3464
3518
 
3465
3519
  ${colors.dim}# 3. SDK \uBB38\uC11C\uB9CC \uC5C5\uB370\uC774\uD2B8${colors.reset}
3466
- npx connectbase docs
3520
+ npx connectbase-client docs
3467
3521
 
3468
3522
  ${colors.dim}# 4. MCP \uC11C\uBC84 \uC124\uC815${colors.reset}
3469
- npx connectbase mcp
3523
+ npx connectbase-client mcp
3470
3524
 
3471
3525
  ${colors.dim}# 4. Prod \uBC30\uD3EC${colors.reset}
3472
3526
  npm run deploy
3473
3527
 
3474
3528
  ${colors.dim}# 4-1. QA \uC6CC\uD06C\uD50C\uB85C\uC6B0 (\uC2E0\uADDC \uC2A4\uD1A0\uB9AC\uC9C0 \uAE30\uBCF8 promote_mode=manual)${colors.reset}
3475
- npx connectbase deploy ./dist ${colors.dim}# QA \uD658\uACBD\uC5D0\uB9CC \uBC30\uD3EC${colors.reset}
3476
- npx connectbase releases ${colors.dim}# \uBC30\uD3EC \uC774\uB825 \uD655\uC778${colors.reset}
3477
- npx connectbase promote ${colors.dim}# QA \uAC80\uC99D \uD6C4 production \uBC18\uC601${colors.reset}
3529
+ npx connectbase-client deploy ./dist ${colors.dim}# QA \uD658\uACBD\uC5D0\uB9CC \uBC30\uD3EC${colors.reset}
3530
+ npx connectbase-client releases ${colors.dim}# \uBC30\uD3EC \uC774\uB825 \uD655\uC778${colors.reset}
3531
+ npx connectbase-client promote ${colors.dim}# QA \uAC80\uC99D \uD6C4 production \uBC18\uC601${colors.reset}
3478
3532
 
3479
3533
  ${colors.dim}# 5. \uD130\uB110 (\uAE30\uBCF8)${colors.reset}
3480
- npx connectbase tunnel 3000
3534
+ npx connectbase-client tunnel 3000
3481
3535
 
3482
3536
  ${colors.dim}# 6. \uD130\uB110 (GPU \uC11C\uBC84 \uB4F1 \uAE34 \uC751\uB2F5 \uC2DC)${colors.reset}
3483
- npx connectbase tunnel 7860 --timeout 300 --max-body 50
3537
+ npx connectbase-client tunnel 7860 --timeout 300 --max-body 50
3484
3538
 
3485
3539
  ${colors.yellow}\uD658\uACBD\uBCC0\uC218:${colors.reset}
3486
3540
  CONNECTBASE_PUBLIC_KEY Public Key (cb_pk_*)
@@ -3594,20 +3648,20 @@ async function main() {
3594
3648
  setupRoot: parsed.options.setupRoot === "true"
3595
3649
  });
3596
3650
  } else if (parsed.command === "docs") {
3597
- await downloadDocs();
3651
+ await downloadDocs(void 0, { baseUrl: config.baseUrl });
3598
3652
  } else if (parsed.command === "mcp") {
3599
- await setupMcp(config.secretKey);
3653
+ await setupMcp(config.secretKey, config.baseUrl);
3600
3654
  } else if (parsed.command === "deploy") {
3601
3655
  const directory = parsed.args[0] || fileConfig.deployDir || ".";
3602
3656
  if (!config.publicKey) {
3603
3657
  error(
3604
- 'Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3658
+ 'Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase-client init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3605
3659
  );
3606
3660
  process.exit(1);
3607
3661
  }
3608
3662
  if (!config.storageId) {
3609
3663
  error(
3610
- '\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3664
+ '\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase-client init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3611
3665
  );
3612
3666
  process.exit(1);
3613
3667
  }
@@ -3628,7 +3682,10 @@ async function main() {
3628
3682
  if (fs2.existsSync(
3629
3683
  path2.join(docsRoot, ".claude", "docs", DOCS_MANIFEST_FILE)
3630
3684
  )) {
3631
- const refreshed = await downloadDocs(docsRoot, { quiet: true });
3685
+ const refreshed = await downloadDocs(docsRoot, {
3686
+ quiet: true,
3687
+ baseUrl: config.baseUrl
3688
+ });
3632
3689
  if (refreshed === "updated") {
3633
3690
  info("SDK \uBB38\uC11C\uAC00 \uC0C8 \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (.claude/docs/)");
3634
3691
  }
@@ -3639,13 +3696,13 @@ async function main() {
3639
3696
  } else if (parsed.command === "promote" || parsed.command === "releases") {
3640
3697
  if (!config.publicKey) {
3641
3698
  error(
3642
- 'Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3699
+ 'Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase-client init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3643
3700
  );
3644
3701
  process.exit(1);
3645
3702
  }
3646
3703
  if (!config.storageId) {
3647
3704
  error(
3648
- '\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3705
+ '\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase-client init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
3649
3706
  );
3650
3707
  process.exit(1);
3651
3708
  }
@@ -3657,7 +3714,7 @@ async function main() {
3657
3714
  } else if (parsed.command === "tunnel") {
3658
3715
  const portStr = parsed.args[0];
3659
3716
  if (!portStr) {
3660
- error("\uD3EC\uD2B8 \uBC88\uD638\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC608: npx connectbase tunnel 8084");
3717
+ error("\uD3EC\uD2B8 \uBC88\uD638\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC608: npx connectbase-client tunnel 8084");
3661
3718
  process.exit(1);
3662
3719
  }
3663
3720
  const port = parseInt(portStr, 10);
@@ -3713,6 +3770,7 @@ main().catch((err) => {
3713
3770
  // Annotate the CommonJS export names for ESM import in node:
3714
3771
  0 && (module.exports = {
3715
3772
  TunnelConnectSelector,
3773
+ apiErrorMessage,
3716
3774
  computeDeployDiff,
3717
3775
  getTunnelConnectCandidates,
3718
3776
  guessMimeType,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "6.2.0",
3
+ "version": "6.3.1",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",