bizrouter 0.2.0 → 0.3.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/AGENT_MANUAL.md CHANGED
@@ -34,6 +34,11 @@ If a management command fails with "콘솔 세션이 없습니다", ask the user
34
34
  `bizrouter login` (it opens a browser; the user approves an 8-character code).
35
35
  You cannot complete that approval yourself — it is deliberately a human step.
36
36
 
37
+ The session grants exactly what the approving user can do in the web console,
38
+ nothing more. The console's separate **Usage API key** (설정 → API 연동 → 사용량
39
+ 조회 API) is a third, read-only credential for the usage export endpoints; this
40
+ CLI neither uses nor manages it, and it is not an admin key.
41
+
37
42
  ## 3. Command cheat sheet
38
43
 
39
44
  ```
@@ -43,8 +48,10 @@ bizrouter logout revoke the console session, delet
43
48
  bizrouter doctor connectivity, key, session, installed agents, conflicts
44
49
  bizrouter models [query] [--json] models this key may use, KRW prices per 1M tokens
45
50
 
46
- bizrouter claude|codex|opencode|hermes [--model ID] [--reasoning-effort L] [--no-mcp] [tool args…]
51
+ bizrouter claude|codex|opencode|hermes [--model ID] [--reasoning-effort L] [--search] [--no-mcp] [tool args…]
47
52
  run a coding agent through BizRouter (config files untouched)
53
+ bizrouter search "question" [--model ID] [--max-tokens N] [--json] [--no-stream]
54
+ BizRouter Search: answer grounded in 50+ live data sources (see §7a)
48
55
 
49
56
  bizrouter api --list [keyword] every console API operation (from the live OpenAPI)
50
57
  bizrouter api --schema [METHOD] <path> request/response fields of one operation
@@ -174,13 +181,43 @@ setting, everything after our flags is passed through untouched.
174
181
  When a console session exists, the launcher also registers this CLI as an MCP
175
182
  server named `bizrouter` inside Claude Code, Codex and OpenCode for that run
176
183
  (`--no-mcp` disables it). The agent then has tools `bizrouter_api`,
177
- `bizrouter_openapi`, `bizrouter_docs`, `bizrouter_models` and `bizrouter_whoami`
178
- with the same rules as above — `bizrouter_api` refuses mutating calls unless
179
- `confirm: true` is passed after the user agreed.
184
+ `bizrouter_openapi`, `bizrouter_docs`, `bizrouter_models`, `bizrouter_whoami`
185
+ and `bizrouter_search` with the same rules as above — `bizrouter_api` refuses
186
+ mutating calls unless `confirm: true` is passed after the user agreed.
180
187
 
181
188
  Claude Code can only run Anthropic models (it always sends Anthropic-only
182
189
  fields); use Codex/OpenCode/Hermes for GPT or Gemini.
183
190
 
191
+ ## 7a. BizRouter Search — answers grounded in live data
192
+
193
+ `POST /v1/search` (docs: https://bizrouter.ai/docs/search) makes the model
194
+ look facts up before it answers. The gateway embeds the question, picks the
195
+ matching sources out of 50+ agent.store data servers, lets the model call them
196
+ as tools, and returns a normal chat completion plus a `bizrouter_search`
197
+ block. Sources include Korean law and National Assembly bills, corporate
198
+ filings and financials (DART, FSC), real-estate transactions, national
199
+ statistics and Bank of Korea rates, transport (flights, transit, maps),
200
+ papers and clinical trials, web search and news, weather and air quality, US
201
+ market data. Questions that need no data are answered without tools.
202
+
203
+ Use it whenever an answer must be current or authoritative instead of model
204
+ memory — especially anything about Korean public data, prices, laws, filings
205
+ or news. Lookups are free; only the model's tokens are billed (tool results
206
+ count as input tokens, so keep `max_tokens` ≥ 1500).
207
+
208
+ Three ways to reach it, all using the API key:
209
+
210
+ | Where | How |
211
+ | --- | --- |
212
+ | Terminal | `bizrouter search "질문" [--model ID] [--max-tokens N] [--json] [--no-stream]` — answer on stdout, a one-line summary (sources · calls · rounds · model · cost) on stderr. `--json` prints the full completion including `bizrouter_search.sources[]` and `calls[]`. |
213
+ | Inside an agent (MCP) | Tool `bizrouter_search {query, model?, max_tokens?}` → `{answer, sources, calls, rounds, routed_model, usage}`. Available in Claude Code, Codex and OpenCode launched by this CLI when a console session exists. This is the **only** route for Claude Code (Messages API) and Codex (Responses API), because Search rides on Chat Completions. |
214
+ | Harness model | `bizrouter opencode --search` / `bizrouter hermes --search` pins `<model>:search` so every turn of that session is grounded. Any Chat Completions client can do the same with `model: "openai/gpt-5.5:search"`, `model: "bizrouter/search"`, or body `"search": true`. |
215
+
216
+ Default model is `bizrouter/route` (the organization's Smart Routing policy
217
+ chooses the concrete model). `--model`/`model` accepts any chat model id.
218
+ When the answer cites sources, keep the citation when you relay it to the
219
+ user; when `calls` is empty, say the answer came from the model alone.
220
+
184
221
  ## 8. Troubleshooting
185
222
 
186
223
  - `콘솔 세션이 없습니다` / 401 → `bizrouter login` (human step).
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # bizrouter CLI
2
2
 
3
- 쓰던 코딩 에이전트를 그대로 두고 명령 앞에 `bizrouter` 붙이면 BizRouter 로 연결됩니다. 로그인한 뒤에는 BizRouter 콘솔(API 키·정책·감사 로그·통계)도 명령으로, 또는 에이전트의 MCP 도구로 다룰 수 있습니다.
3
+ 쓰던 코딩 에이전트를 그대로 두고 명령 앞에 `bizrouter`만 붙이면 BizRouter로 연결됩니다. 로그인한 뒤에는 BizRouter 콘솔(API 키·정책·감사 로그·통계)도 명령으로, 또는 에이전트의 MCP 도구로 다룰 수 있습니다.
4
4
 
5
5
  ```bash
6
6
  npm install -g bizrouter # 또는: curl -fsSL https://bizrouter.ai/install.sh | bash
@@ -15,10 +15,10 @@ bizrouter hermes # Hermes
15
15
 
16
16
  ## 로그인이 해 주는 것
17
17
 
18
- `bizrouter login` 터미널에 8자리 코드를 보여주고 브라우저에서 BizRouter 콘솔을 엽니다. 콘솔에 로그인(비밀번호·Google·웍스AI·2차 인증 그대로)한 뒤 코드를 확인하고 「승인」하면
18
+ `bizrouter login`은 터미널에 8자리 코드를 보여주고 브라우저에서 BizRouter 콘솔을 엽니다. 콘솔에 로그인(비밀번호·Google·웍스AI·2차 인증 그대로)한 뒤 코드를 확인하고 「승인」하면
19
19
 
20
20
  - 이 기기용 **콘솔 세션**(30일)과
21
- - 이 기기 이름의 **API 키**(모델 호출용)가 함께 발급되어 `~/.bizrouter/credentials.json` 권한 600 으로 저장됩니다.
21
+ - 이 기기 이름의 **API 키**(모델 호출용)가 함께 발급되어 `~/.bizrouter/credentials.json`에 권한 600으로 저장됩니다.
22
22
 
23
23
  키만 붙여 넣던 방식도 그대로 됩니다: `bizrouter login --with-key`. CI·컨테이너에서는 `BIZROUTER_API_KEY`(모델 호출)·`BIZROUTER_SESSION_TOKEN`(콘솔) 환경 변수가 저장된 값보다 우선합니다. 세션은 콘솔 「계정 → 로그인된 기기」에서 언제든 종료할 수 있습니다.
24
24
 
@@ -26,12 +26,12 @@ bizrouter hermes # Hermes
26
26
 
27
27
  | 도구 | 실행 시 하는 일 | 설정 파일 변경 |
28
28
  | --- | --- | --- |
29
- | Claude Code | `ANTHROPIC_BASE_URL=https://api.bizrouter.ai/claude`, 토큰, 빈 `ANTHROPIC_API_KEY`, 모델 선택창 자동 채움을 넘기고, 사용자 settings.json 의 다른 게이트웨이 설정이 이기지 못하도록 명령행 settings 로 고정 | 없음 |
29
+ | Claude Code | `ANTHROPIC_BASE_URL=https://api.bizrouter.ai/claude`, 토큰, 빈 `ANTHROPIC_API_KEY`, 모델 선택창 자동 채움을 넘기고, 사용자 settings.json의 다른 게이트웨이 설정이 이기지 못하도록 명령행 settings로 고정 | 없음 |
30
30
  | Codex CLI | `-c` 오버라이드로 BizRouter provider(Responses API)와 모델·추론 강도 지정 | 없음 |
31
- | OpenCode | `OPENCODE_CONFIG_CONTENT` 이 키가 쓸 수 있는 모델 전체가 담긴 `bizrouter` provider 를 주입 | 없음 |
31
+ | OpenCode | `OPENCODE_CONFIG_CONTENT`로 이 키가 쓸 수 있는 모델 전체가 담긴 `bizrouter` provider를 주입 | 없음 |
32
32
  | Hermes | `custom` provider(`CUSTOM_BASE_URL`/`CUSTOM_API_KEY`)와 모델을 이번 실행에만 지정 | 없음 |
33
33
 
34
- 우리 옵션 뒤의 인자는 도구에 그대로 전달됩니다.
34
+ 도구는 평소처럼 실행됩니다. 바뀌는 것은 그 실행에서 모델을 호출하는 주소와 인증뿐이어서, 화면과 사용법은 같고 과금·모델 정책·감사 로그는 BizRouter 콘솔에 남습니다. `bizrouter` 없이 실행하면 평소대로 동작합니다. 우리 옵션 뒤의 인자는 도구에 그대로 전달됩니다.
35
35
 
36
36
  ```bash
37
37
  bizrouter claude --model anthropic/claude-opus-5 -p "실패하는 테스트 고쳐줘"
@@ -40,11 +40,27 @@ bizrouter opencode --model google/gemini-3.5-pro
40
40
  bizrouter claude --dry-run # 실행 대신 넘겨줄 환경 변수와 명령을 보여줍니다
41
41
  ```
42
42
 
43
- 콘솔 세션이 있으면 Claude Code·Codex·OpenCode 에 이 CLI 가 **MCP 서버 `bizrouter`** 로 함께 연결됩니다. 에이전트는 `bizrouter_api`(콘솔 API 호출)·`bizrouter_openapi`(API 탐색)·`bizrouter_docs`(문서)·`bizrouter_models`·`bizrouter_whoami` 도구로 API 키를 만들고, 정책을 바꾸고, 사용량을 읽습니다. 데이터를 바꾸는 호출은 사용자 확인 뒤 `confirm: true` 받아야만 실행됩니다. `--no-mcp` 끕니다.
43
+ 콘솔 세션이 있으면 Claude Code·Codex·OpenCode에 이 CLI가 **MCP 서버 `bizrouter`** 로 함께 연결됩니다. 에이전트는 `bizrouter_api`(콘솔 API 호출)·`bizrouter_openapi`(API 탐색)·`bizrouter_docs`(문서)·`bizrouter_models`·`bizrouter_whoami`·`bizrouter_search`(Search) 도구로 API 키를 만들고, 정책을 바꾸고, 사용량을 읽고, 실제 데이터를 찾아 답합니다. 데이터를 바꾸는 호출은 사용자 확인 뒤 `confirm: true`를 받아야만 실행됩니다. `--no-mcp`로 끕니다.
44
+
45
+ ## Search — 실제 데이터를 찾아 답하기
46
+
47
+ `bizrouter search`는 [BizRouter Search](https://bizrouter.ai/docs/search)(`POST /v1/search`)를 호출합니다. 모델이 질문에 맞는 출처(법령·국회, 기업 공시·재무, 부동산 실거래, 국가통계·금리, 교통, 논문·임상, 웹 검색·뉴스, 날씨, 지도, 미국 주식 등 50종 이상)를 골라 조회한 뒤 출처와 함께 답합니다. 조회는 무료이고 모델 토큰만 과금됩니다.
48
+
49
+ ```bash
50
+ bizrouter search "근로기준법 60조 연차 휴가 조문"
51
+ bizrouter search "한국은행 기준금리 최근 결정" --model openai/gpt-5.5
52
+ bizrouter search "삼성전자 2024년 매출" --json | jq '.bizrouter_search.sources'
53
+ echo "인천공항 오늘 출발 지연 편" | bizrouter search --no-stream
54
+
55
+ bizrouter opencode --search # OpenCode 세션 전체에 Search 를 켬 (<모델>:search)
56
+ bizrouter hermes --search --model openai/gpt-5.5
57
+ ```
58
+
59
+ 답은 stdout, 출처·조회 횟수·라운드·모델·비용 한 줄은 stderr로 나옵니다. 로그인한 뒤 실행한 Claude Code·Codex·OpenCode 안에서는 MCP 도구 `bizrouter_search`로 같은 일을 합니다. Claude Code(Messages API)·Codex(Responses API)는 `:search` 접미사를 쓸 수 없으므로 이 도구가 두 에이전트에서 Search를 쓰는 길입니다.
44
60
 
45
61
  ## 콘솔 관리 명령
46
62
 
47
- 모두 `--json` 지원하고, 데이터를 바꾸는 명령은 터미널에서 확인을 묻거나 `--yes` 요구합니다.
63
+ 모두 `--json`을 지원하고, 데이터를 바꾸는 명령은 터미널에서 확인을 묻거나 `--yes`를 요구합니다.
48
64
 
49
65
  ```bash
50
66
  bizrouter api --list [검색어] # 콘솔 API 전체 목록 (라이브 OpenAPI 기준)
@@ -63,21 +79,25 @@ bizrouter docs [주제] # 에이전트 매뉴얼(AG
63
79
  bizrouter mcp # MCP 서버(stdio)로 직접 실행
64
80
  ```
65
81
 
66
- 에이전트에게 맡길 때는 `bizrouter docs` 출력하는 매뉴얼을 먼저 읽히면 됩니다. 사람용 개발 문서(https://bizrouter.ai/docs)는 같은 소스에서 만든 https://bizrouter.ai/llms-full.txt 로도 제공됩니다.
82
+ 에이전트에게 맡길 때는 `bizrouter docs`가 출력하는 매뉴얼을 먼저 읽히면 됩니다. 사람용 개발 문서(https://bizrouter.ai/docs)는 같은 소스에서 만든 https://bizrouter.ai/llms-full.txt로도 제공됩니다.
67
83
 
68
84
  ## 그 밖의 명령
69
85
 
70
86
  ```bash
71
87
  bizrouter models [검색어] # 이 키로 쓸 수 있는 모델·컨텍스트·원화 단가
88
+ bizrouter search "질문" # Search 호출 (--model, --max-tokens, --json, --no-stream)
72
89
  bizrouter doctor # 키·세션·연결·설치·충돌 점검
73
- bizrouter setup claude|codex|opencode # 도구 설정 파일에 BizRouter 를 영구 등록 (백업 생성)
90
+ bizrouter setup claude|codex|opencode # 도구 설정 파일에 BizRouter를 영구 등록 (백업 생성)
74
91
  bizrouter auth / logout / env / update
75
92
  ```
76
93
 
77
94
  ## 알아둘 것
78
95
 
79
- - Claude Code 는 Anthropic 전용 필드(`thinking`, `cache_control`)를 항상 보내므로 Claude 모델만 실행할 수 있습니다. GPT·Gemini 는 Codex·OpenCode·Hermes 로 실행하십시오.
96
+ - Claude Code는 Anthropic 전용 필드(`thinking`, `cache_control`)를 항상 보내므로 Claude 모델만 실행할 수 있습니다. GPT·Gemini는 Codex·OpenCode·Hermes로 실행하십시오.
80
97
  - 콘솔 세션은 로그인한 사용자의 권한(owner/member)을 그대로 따릅니다. 구성원(member)은 자기 API 키와 사용량만 다룰 수 있습니다.
98
+ - API 키(`sk-br-v1-…`)의 권한은 예전과 같습니다. 모델 호출만 되고, 콘솔 관리는 브라우저에서 승인한 사람의 콘솔 세션으로만 됩니다. 범위는 그 사람이 웹 콘솔에서 할 수 있는 일과 같습니다.
99
+ - 콘솔의 「사용량 조회 API」 키는 별개의 읽기 전용 키입니다. CLI는 쓰지도 바꾸지도 않습니다.
100
+ - `credentials.json`의 세션 토큰은 30일짜리 콘솔 로그인입니다. 공유하거나 저장소에 넣지 말고, 의심스러우면 콘솔 「계정 → 로그인된 기기」에서 종료하십시오.
81
101
  - 요구 사항: Node.js 18 이상. 각 코딩 에이전트는 따로 설치돼 있어야 합니다.
82
102
 
83
103
  문서: https://bizrouter.ai/docs/cli
package/dist/api.js CHANGED
@@ -31,17 +31,17 @@ async function requestJson(path, apiKey) {
31
31
  catch (error) {
32
32
  const reason = error instanceof Error && error.name === 'AbortError' ? '응답이 8초 안에 오지 않았습니다' : String(error);
33
33
  throw new CliError(`BizRouter API(${apiBase()})에 연결할 수 없습니다: ${reason}`, {
34
- hint: '네트워크나 프록시 설정을 확인하세요. 사내망이면 api.bizrouter.ai 로 나가는 HTTPS 가 열려 있어야 합니다.',
34
+ hint: '네트워크나 프록시 설정을 확인하세요. 사내망이면 api.bizrouter.ai로 나가는 HTTPS가 열려 있어야 합니다.',
35
35
  });
36
36
  }
37
37
  finally {
38
38
  clearTimeout(timer);
39
39
  }
40
40
  if (response.status === 401 || response.status === 403) {
41
- throw new ApiError(response.status, 'API 키가 유효하지 않습니다.', '`bizrouter login` 으로 키를 다시 저장하세요.');
41
+ throw new ApiError(response.status, 'API 키가 유효하지 않습니다.', '`bizrouter login`으로 키를 다시 저장하세요.');
42
42
  }
43
43
  if (!response.ok) {
44
- throw new ApiError(response.status, `BizRouter API 가 ${response.status} 를 돌려주었습니다.`);
44
+ throw new ApiError(response.status, `BizRouter API가 ${response.status}를 돌려주었습니다.`);
45
45
  }
46
46
  return response.json();
47
47
  }
package/dist/args.js CHANGED
@@ -7,7 +7,7 @@ export class ArgError extends Error {
7
7
  * the harness untouched, so `bizrouter claude -p "hi"` still works.
8
8
  */
9
9
  export function parseLaunchArgs(argv) {
10
- const out = { closedNetwork: false, dryRun: false, help: false, noMcp: false, passthrough: [] };
10
+ const out = { closedNetwork: false, dryRun: false, help: false, noMcp: false, search: false, passthrough: [] };
11
11
  let i = 0;
12
12
  const takeValue = (flag) => {
13
13
  const value = argv[i + 1];
@@ -51,6 +51,10 @@ export function parseLaunchArgs(argv) {
51
51
  out.noMcp = true;
52
52
  i += 1;
53
53
  continue;
54
+ case '--search':
55
+ out.search = true;
56
+ i += 1;
57
+ continue;
54
58
  case '--help':
55
59
  case '-h':
56
60
  out.help = true;
@@ -13,7 +13,7 @@ function parseData(raw) {
13
13
  return JSON.parse(text);
14
14
  }
15
15
  catch (error) {
16
- throw new CliError(`-d 값이 JSON 이 아닙니다: ${error instanceof Error ? error.message : String(error)}`, {
16
+ throw new CliError(`-d 값이 JSON이 아닙니다: ${error instanceof Error ? error.message : String(error)}`, {
17
17
  hint: `예: -d '{"name":"ci-key"}' 또는 -d @body.json (표준 입력은 -d @-)`,
18
18
  });
19
19
  }
@@ -78,7 +78,7 @@ export function parseApiArgs(argv) {
78
78
  }
79
79
  function printOperations(rows, keyword) {
80
80
  if (!rows.length) {
81
- print(keyword ? `「${keyword}」에 맞는 콘솔 API 가 없습니다.` : '콘솔 API 목록이 비어 있습니다.');
81
+ print(keyword ? `「${keyword}」에 맞는 콘솔 API가 없습니다.` : '콘솔 API 목록이 비어 있습니다.');
82
82
  return;
83
83
  }
84
84
  const pathWidth = Math.min(56, Math.max(...rows.map((r) => r.path.length), 10));
@@ -106,7 +106,7 @@ export async function apiCommand(argv) {
106
106
  const method = flags.method && argv.some((a) => a.toUpperCase() === flags.method) ? flags.method : undefined;
107
107
  const matches = findOperation(spec, method, flags.schema);
108
108
  if (!matches.length)
109
- throw new CliError(`콘솔 API 에 없는 경로입니다: ${normalizeConsolePath(flags.schema)}`, { hint: '`bizrouter api --list <검색어>` 찾아보십시오.' });
109
+ throw new CliError(`콘솔 API에 없는 경로입니다: ${normalizeConsolePath(flags.schema)}`, { hint: '`bizrouter api --list <검색어>`로 찾아보십시오.' });
110
110
  print(JSON.stringify(matches.length === 1 ? matches[0] : matches, null, 2));
111
111
  return 0;
112
112
  }
@@ -119,11 +119,11 @@ export async function apiCommand(argv) {
119
119
  const path = normalizeConsolePath(flags.path);
120
120
  if (MUTATING_METHODS.has(flags.method) && !flags.yes) {
121
121
  if (!process.stdin.isTTY) {
122
- throw new CliError(`${flags.method} ${path} 는 데이터를 바꾸는 요청입니다. 확인 없이 실행하려면 --yes 를 붙이십시오.`, {
123
- hint: '에이전트가 실행하는 경우, 사람에게 먼저 확인을 받은 뒤 --yes 를 붙여 다시 실행하십시오.',
122
+ throw new CliError(`${flags.method} ${path}는 데이터를 바꾸는 요청입니다. 확인 없이 실행하려면 --yes를 붙이십시오.`, {
123
+ hint: '에이전트가 실행하는 경우, 사람에게 먼저 확인을 받은 뒤 --yes를 붙여 다시 실행하십시오.',
124
124
  });
125
125
  }
126
- warn(`${flags.method} ${path} 는 데이터를 바꾸는 요청입니다.`);
126
+ warn(`${flags.method} ${path}는 데이터를 바꾸는 요청입니다.`);
127
127
  if (flags.data !== undefined)
128
128
  print(c.dim(JSON.stringify(flags.data, null, 2)));
129
129
  const answer = await readLine('실행할까요? (y/N) ');
@@ -77,8 +77,8 @@ async function confirmMutation(parsed, description, payload) {
77
77
  if (parsed.yes)
78
78
  return;
79
79
  if (!process.stdin.isTTY) {
80
- throw new CliError(`${description} — 데이터를 바꾸는 작업입니다. 확인 없이 실행하려면 --yes 를 붙이십시오.`, {
81
- hint: '에이전트가 실행하는 경우, 사람에게 먼저 확인을 받은 뒤 --yes 를 붙여 다시 실행하십시오.',
80
+ throw new CliError(`${description} — 데이터를 바꾸는 작업입니다. 확인 없이 실행하려면 --yes를 붙이십시오.`, {
81
+ hint: '에이전트가 실행하는 경우, 사람에게 먼저 확인을 받은 뒤 --yes를 붙여 다시 실행하십시오.',
82
82
  });
83
83
  }
84
84
  warn(description);
@@ -132,11 +132,11 @@ async function resolveKeyId(ref) {
132
132
  if (byName.length === 1)
133
133
  return byName[0];
134
134
  if (byName.length > 1)
135
- throw new CliError(`이름 「${ref}」 인 키가 ${byName.length}개입니다. id 로 지정하십시오.`, { hint: 'bizrouter keys list --all' });
135
+ throw new CliError(`이름 「${ref}」 인 키가 ${byName.length}개입니다. id로 지정하십시오.`, { hint: 'bizrouter keys list --all' });
136
136
  const byPreview = keys.filter((k) => k.key_preview && ref.endsWith(k.key_preview.slice(-4)) && ref.startsWith(k.key_preview.slice(0, 13)));
137
137
  if (byPreview.length === 1)
138
138
  return byPreview[0];
139
- throw new CliError(`API 키를 찾을 수 없습니다: ${ref}`, { hint: 'bizrouter keys list --all 로 id 나 이름을 확인하십시오.' });
139
+ throw new CliError(`API 키를 찾을 수 없습니다: ${ref}`, { hint: 'bizrouter keys list --all로 id 나 이름을 확인하십시오.' });
140
140
  }
141
141
  const KEYS_USAGE = [
142
142
  'bizrouter keys list [--all] [--search 검색어] [--json]',
@@ -191,7 +191,7 @@ export async function keysCommand(argv) {
191
191
  if (alertEmails)
192
192
  body.limit_alert_recipient_emails = alertEmails;
193
193
  if (body.credit_limit !== null && Number.isNaN(body.credit_limit))
194
- throw new CliError('--limit 은 원 단위 숫자여야 합니다.');
194
+ throw new CliError('--limit은 원 단위 숫자여야 합니다.');
195
195
  await confirmMutation(parsed, `API 키 「${name}」 을 만듭니다.`, body);
196
196
  const created = await send('POST', '/api-keys', body);
197
197
  return emit(parsed, created, () => {
@@ -238,7 +238,7 @@ export async function keysCommand(argv) {
238
238
  body.limit_alert_recipient_emails = alertEmails;
239
239
  if (!Object.keys(body).length)
240
240
  throw new CliError('바꿀 항목이 없습니다.', { hint: KEYS_USAGE });
241
- await confirmMutation(parsed, `API 키 「${key.name}」 (${key.key_preview}) 을 수정합니다.`, body);
241
+ await confirmMutation(parsed, `API 키 「${key.name}」 (${key.key_preview})을 수정합니다.`, body);
242
242
  const updated = await send('PATCH', `/api-keys/${key.id}`, body);
243
243
  return emit(parsed, updated, () => ok(`API 키 「${updated.name}」 을 수정했습니다.`));
244
244
  }
@@ -248,7 +248,7 @@ export async function keysCommand(argv) {
248
248
  if (!ref)
249
249
  throw new CliError('키 id 또는 이름이 필요합니다.', { hint: KEYS_USAGE });
250
250
  const key = await resolveKeyId(ref);
251
- await confirmMutation(parsed, `API 키 「${key.name}」 (${key.key_preview}) 을 삭제합니다. 이 키를 쓰는 서비스는 즉시 인증에 실패합니다.`);
251
+ await confirmMutation(parsed, `API 키 「${key.name}」 (${key.key_preview})을 삭제합니다. 이 키를 쓰는 서비스는 즉시 인증에 실패합니다.`);
252
252
  await send('DELETE', `/api-keys/${key.id}`);
253
253
  ok(`API 키 「${key.name}」 을 삭제했습니다.`);
254
254
  return 0;
@@ -425,7 +425,7 @@ export async function auditCommand(argv) {
425
425
  case 'show': {
426
426
  const id = rest[0];
427
427
  if (!id)
428
- throw new CliError('log_id 가 필요합니다.', { hint: AUDIT_USAGE });
428
+ throw new CliError('log_id가 필요합니다.', { hint: AUDIT_USAGE });
429
429
  const detail = await get(`/audit-logs/${id}`);
430
430
  print(JSON.stringify(detail, null, 2));
431
431
  return 0;
@@ -466,7 +466,7 @@ export async function routingCommand(argv) {
466
466
  const body = parseAssignments(rest);
467
467
  if (!Object.keys(body).length)
468
468
  throw new CliError('바꿀 항목이 없습니다.', { hint: ROUTING_USAGE });
469
- await confirmMutation(parsed, '기본 스마트 라우팅 정책을 변경합니다. bizrouter/route 로 오는 요청의 모델 선택이 바뀝니다.', body);
469
+ await confirmMutation(parsed, '기본 스마트 라우팅 정책을 변경합니다. bizrouter/route로 오는 요청의 모델 선택이 바뀝니다.', body);
470
470
  const updated = await send('PATCH', '/smart-routing/policy', body);
471
471
  return emit(parsed, updated, () => {
472
472
  ok('정책을 변경했습니다.');
@@ -523,7 +523,7 @@ export async function routingCommand(argv) {
523
523
  if (top.length)
524
524
  print(` 상위 모델: ${top.slice(0, 5).map((t) => `${t.model} ${t.count}`).join(', ')}`);
525
525
  print();
526
- print(c.dim(`개별 결정 ${data.items.length}건은 --json 으로 보십시오.`));
526
+ print(c.dim(`개별 결정 ${data.items.length}건은 --json으로 보십시오.`));
527
527
  });
528
528
  }
529
529
  default:
@@ -12,7 +12,7 @@ export function agentManualPath() {
12
12
  export function readAgentManual() {
13
13
  const path = agentManualPath();
14
14
  if (!existsSync(path))
15
- throw new CliError('에이전트 매뉴얼 파일(AGENT_MANUAL.md)이 패키지에 없습니다.', { hint: '`bizrouter update` 다시 설치하십시오.' });
15
+ throw new CliError('에이전트 매뉴얼 파일(AGENT_MANUAL.md)이 패키지에 없습니다.', { hint: '`bizrouter update`로 다시 설치하십시오.' });
16
16
  return readFileSync(path, 'utf8');
17
17
  }
18
18
  /** Split llms-full.txt (## title / URL: … / 분류: …) into pages. */
@@ -58,16 +58,16 @@ async function fetchText(url) {
58
58
  try {
59
59
  const response = await fetch(url, { headers: { Accept: 'text/plain', 'User-Agent': 'bizrouter-cli' }, signal: controller.signal });
60
60
  if (!response.ok)
61
- throw new CliError(`${url} 가 ${response.status} 를 돌려주었습니다.`);
61
+ throw new CliError(`${url}가 ${response.status}를 돌려주었습니다.`);
62
62
  const text = await response.text();
63
63
  if (/^\s*<!doctype html/i.test(text) || /^\s*<html/i.test(text))
64
- throw new CliError(`${url} 가 문서 대신 HTML 을 돌려주었습니다.`);
64
+ throw new CliError(`${url}가 문서 대신 HTML을 돌려주었습니다.`);
65
65
  return text;
66
66
  }
67
67
  catch (error) {
68
68
  if (error instanceof CliError)
69
69
  throw error;
70
- throw new CliError(`${url} 를 내려받을 수 없습니다: ${error instanceof Error ? error.message : String(error)}`);
70
+ throw new CliError(`${url}를 내려받을 수 없습니다: ${error instanceof Error ? error.message : String(error)}`);
71
71
  }
72
72
  finally {
73
73
  clearTimeout(timer);
@@ -124,7 +124,7 @@ export async function docsCommand(argv) {
124
124
  const { text, fromCache } = await loadLlmsFull({ force: flags.refresh });
125
125
  const pages = parseLlmsFull(text);
126
126
  if (fromCache)
127
- info(`캐시된 문서를 사용합니다 (최대 하루 · --refresh 로 다시 받기)`);
127
+ info(`캐시된 문서를 사용합니다 (최대 하루 · --refresh로 다시 받기)`);
128
128
  if (flags.full) {
129
129
  print(text.trimEnd());
130
130
  return 0;
@@ -144,7 +144,7 @@ export async function docsCommand(argv) {
144
144
  }
145
145
  const matches = findDocsPages(pages, flags.topic);
146
146
  if (!matches.length) {
147
- print(`「${flags.topic}」에 해당하는 문서가 없습니다. bizrouter docs --list 로 목록을 확인하십시오.`);
147
+ print(`「${flags.topic}」에 해당하는 문서가 없습니다. bizrouter docs --list로 목록을 확인하십시오.`);
148
148
  return 1;
149
149
  }
150
150
  for (const page of matches) {
@@ -28,15 +28,15 @@ export function conflictFindings(env, files) {
28
28
  if (env.ANTHROPIC_API_KEY) {
29
29
  findings.push({
30
30
  level: 'warn',
31
- text: `ANTHROPIC_API_KEY 가 셸에 설정돼 있습니다 (${maskKey(env.ANTHROPIC_API_KEY)}).`,
32
- hint: '`bizrouter claude` 실행 시 빈 값으로 덮어쓰므로 동작에는 문제가 없지만, `claude` 직접 실행하면 Anthropic 으로 바로 갑니다.',
31
+ text: `ANTHROPIC_API_KEY가 셸에 설정돼 있습니다 (${maskKey(env.ANTHROPIC_API_KEY)}).`,
32
+ hint: '`bizrouter claude`는 실행 시 빈 값으로 덮어쓰므로 동작에는 문제가 없지만, `claude`를 직접 실행하면 Anthropic으로 바로 갑니다.',
33
33
  });
34
34
  }
35
35
  if (env.ANTHROPIC_BASE_URL && !env.ANTHROPIC_BASE_URL.startsWith(base)) {
36
- findings.push({ level: 'warn', text: `ANTHROPIC_BASE_URL 이 다른 주소를 가리킵니다: ${env.ANTHROPIC_BASE_URL}`, hint: '`bizrouter claude` 이 값을 덮어씁니다.' });
36
+ findings.push({ level: 'warn', text: `ANTHROPIC_BASE_URL이 다른 주소를 가리킵니다: ${env.ANTHROPIC_BASE_URL}`, hint: '`bizrouter claude`는 이 값을 덮어씁니다.' });
37
37
  }
38
38
  if (env.OPENAI_BASE_URL) {
39
- findings.push({ level: 'warn', text: `OPENAI_BASE_URL 이 설정돼 있습니다: ${env.OPENAI_BASE_URL}`, hint: 'Codex 는 provider 설정이 우선이라 영향이 없지만, 다른 도구가 이 값을 읽을 수 있습니다.' });
39
+ findings.push({ level: 'warn', text: `OPENAI_BASE_URL이 설정돼 있습니다: ${env.OPENAI_BASE_URL}`, hint: 'Codex는 provider 설정이 우선이라 영향이 없지만, 다른 도구가 이 값을 읽을 수 있습니다.' });
40
40
  }
41
41
  if (files.claudeSettings) {
42
42
  try {
@@ -46,22 +46,22 @@ export function conflictFindings(env, files) {
46
46
  if (foreignBase) {
47
47
  findings.push({
48
48
  level: 'warn',
49
- text: `Claude Code settings.json 의 env 가 다른 게이트웨이를 가리킵니다: ${parsed.env?.ANTHROPIC_BASE_URL}`,
50
- hint: '`bizrouter claude` 명령행 settings 로 덮어쓰지만, `claude` 단독 실행은 그 주소로 갑니다. `bizrouter setup claude` 갈아탈 수 있습니다.',
49
+ text: `Claude Code settings.json의 env가 다른 게이트웨이를 가리킵니다: ${parsed.env?.ANTHROPIC_BASE_URL}`,
50
+ hint: '`bizrouter claude`는 명령행 settings로 덮어쓰지만, `claude` 단독 실행은 그 주소로 갑니다. `bizrouter setup claude`로 갈아탈 수 있습니다.',
51
51
  });
52
52
  }
53
53
  else if (keys.length) {
54
- findings.push({ level: 'ok', text: `Claude Code settings.json 에 env ${keys.length}개가 있고 BizRouter 와 충돌하지 않습니다.` });
54
+ findings.push({ level: 'ok', text: `Claude Code settings.json에 env ${keys.length}개가 있고 BizRouter와 충돌하지 않습니다.` });
55
55
  }
56
56
  }
57
57
  catch {
58
- findings.push({ level: 'warn', text: 'Claude Code settings.json 을 JSON 으로 읽을 수 없습니다.', hint: 'Claude Code 자체도 이 파일을 못 읽습니다. 문법을 확인하세요.' });
58
+ findings.push({ level: 'warn', text: 'Claude Code settings.json을 JSON으로 읽을 수 없습니다.', hint: 'Claude Code 자체도 이 파일을 못 읽습니다. 문법을 확인하세요.' });
59
59
  }
60
60
  }
61
61
  if (files.codexConfig) {
62
62
  const provider = /^\s*model_provider\s*=\s*"([^"]+)"/m.exec(files.codexConfig)?.[1];
63
63
  if (provider && provider !== 'bizrouter') {
64
- findings.push({ level: 'ok', text: `Codex config.toml 의 기본 provider 는 "${provider}" 입니다.`, hint: '`bizrouter codex` -c 로 덮어쓰므로 그대로 두어도 됩니다. `codex` 단독으로도 BizRouter 를 쓰려면 `bizrouter setup codex`.' });
64
+ findings.push({ level: 'ok', text: `Codex config.toml의 기본 provider는 "${provider}" 입니다.`, hint: '`bizrouter codex`는 -c로 덮어쓰므로 그대로 두어도 됩니다. `codex` 단독으로도 BizRouter를 쓰려면 `bizrouter setup codex`.' });
65
65
  }
66
66
  }
67
67
  return findings;
@@ -74,7 +74,7 @@ export async function doctorCommand() {
74
74
  : { level: 'fail', text: `Node.js ${process.versions.node} — 18 이상이 필요합니다.` });
75
75
  const session = resolveSession();
76
76
  if (session.source === 'none') {
77
- findings.push({ level: 'warn', text: '콘솔 세션이 없습니다 — 콘솔 관리 명령(api·keys·usage …)과 MCP 연결은 비활성입니다.', hint: '`bizrouter login` 으로 브라우저 승인을 받으십시오.' });
77
+ findings.push({ level: 'warn', text: '콘솔 세션이 없습니다 — 콘솔 관리 명령(api·keys·usage …)과 MCP 연결은 비활성입니다.', hint: '`bizrouter login`으로 브라우저 승인을 받으십시오.' });
78
78
  }
79
79
  else {
80
80
  try {
@@ -82,7 +82,7 @@ export async function doctorCommand() {
82
82
  findings.push({ level: 'ok', text: `콘솔 세션 유효 · ${profile.user.email} · ${profile.organization.name} (${profile.user.role})${session.source === 'env' ? ` · ${ENV_SESSION_NAME}` : ''}` });
83
83
  }
84
84
  catch (error) {
85
- findings.push({ level: 'warn', text: `콘솔 세션 무효: ${error instanceof Error ? error.message : String(error)}`, hint: '`bizrouter login` 으로 다시 로그인하십시오.' });
85
+ findings.push({ level: 'warn', text: `콘솔 세션 무효: ${error instanceof Error ? error.message : String(error)}`, hint: '`bizrouter login`으로 다시 로그인하십시오.' });
86
86
  }
87
87
  }
88
88
  const cred = resolveCredential();
@@ -104,7 +104,7 @@ export async function doctorCommand() {
104
104
  const claudeModels = await probe(`${base}/claude/v1/models?limit=1`, cred.apiKey ?? '');
105
105
  findings.push(claudeModels.startsWith('200')
106
106
  ? { level: 'ok', text: `GET ${base}/claude/v1/models → ${claudeModels} (Claude Code /model 선택창 자동 채움)` }
107
- : { level: 'warn', text: `GET ${base}/claude/v1/models → ${claudeModels}`, hint: 'Claude Code 의 /model 선택창이 내장 목록으로만 채워집니다.' });
107
+ : { level: 'warn', text: `GET ${base}/claude/v1/models → ${claudeModels}`, hint: 'Claude Code의 /model 선택창이 내장 목록으로만 채워집니다.' });
108
108
  }
109
109
  for (const h of HARNESSES) {
110
110
  const version = binaryVersion(h);
@@ -51,7 +51,7 @@ export async function waitForApproval(deviceCode, options) {
51
51
  }
52
52
  async function browserLogin(flags) {
53
53
  const started = await startDeviceLogin();
54
- print(c.bold('BizRouter 에 로그인합니다.'));
54
+ print(c.bold('BizRouter에 로그인합니다.'));
55
55
  print();
56
56
  print(`1. 브라우저에서 이 주소를 엽니다: ${c.cyan(started.verification_url_complete)}`);
57
57
  print(`2. 화면의 코드가 아래와 같은지 확인하고 「승인」을 누릅니다.`);
@@ -69,9 +69,9 @@ async function browserLogin(flags) {
69
69
  deadline: Date.now() + started.expires_in * 1000,
70
70
  });
71
71
  if (result.status === 'denied')
72
- throw new CliError('브라우저에서 로그인 요청을 거부했습니다.', { hint: '직접 실행한 로그인이었다면 `bizrouter login` 다시 실행하십시오.' });
72
+ throw new CliError('브라우저에서 로그인 요청을 거부했습니다.', { hint: '직접 실행한 로그인이었다면 `bizrouter login`을 다시 실행하십시오.' });
73
73
  if (result.status !== 'approved' || !result.session_token) {
74
- throw new CliError('로그인 코드가 만료되었습니다.', { hint: '`bizrouter login` 다시 실행하고 10분 안에 승인하십시오.' });
74
+ throw new CliError('로그인 코드가 만료되었습니다.', { hint: '`bizrouter login`을 다시 실행하고 10분 안에 승인하십시오.' });
75
75
  }
76
76
  const account = accountFromLogin(result);
77
77
  const path = saveCredentials({
@@ -99,7 +99,7 @@ async function browserLogin(flags) {
99
99
  if (existing.apiKey)
100
100
  info(`저장돼 있던 API 키(${maskKey(existing.apiKey)})는 그대로 사용합니다.`);
101
101
  else
102
- info(`코딩 에이전트 실행에는 API 키가 필요합니다. \`bizrouter keys create <이름>\` 또는 ${consoleUrl()}${KEYS_PAGE_PATH} 에서 만든 뒤 \`bizrouter login --with-key\` 저장하십시오.`);
102
+ info(`코딩 에이전트 실행에는 API 키가 필요합니다. \`bizrouter keys create <이름>\` 또는 ${consoleUrl()}${KEYS_PAGE_PATH}에서 만든 뒤 \`bizrouter login --with-key\`로 저장하십시오.`);
103
103
  }
104
104
  print();
105
105
  print('이제 바로 실행할 수 있습니다:');
@@ -124,7 +124,7 @@ async function keyLogin(flags) {
124
124
  throw new CliError('키가 입력되지 않았습니다.');
125
125
  if (!looksLikeApiKey(entered)) {
126
126
  throw new CliError('BizRouter API 키 형식이 아닙니다. 키는 sk-br- 로 시작합니다.', {
127
- hint: `${keysUrl} 에서 발급한 키를 그대로 붙여 넣으십시오.`,
127
+ hint: `${keysUrl}에서 발급한 키를 그대로 붙여 넣으십시오.`,
128
128
  });
129
129
  }
130
130
  info('키를 확인하는 중…');
@@ -134,7 +134,7 @@ async function keyLogin(flags) {
134
134
  ok(`키를 저장했습니다: ${path} (권한 600)`);
135
135
  ok(`이 키로 쓸 수 있는 모델 ${chatModels(catalog.models).length}개를 확인했습니다.`);
136
136
  if (!resolveSession().token)
137
- info('콘솔 관리 명령(`bizrouter api`, `keys`, `usage` …)까지 쓰려면 `bizrouter login` 으로 브라우저 승인도 받으십시오.');
137
+ info('콘솔 관리 명령(`bizrouter api`, `keys`, `usage` …)까지 쓰려면 `bizrouter login`으로 브라우저 승인도 받으십시오.');
138
138
  print();
139
139
  print(`이제 바로 실행할 수 있습니다:`);
140
140
  print(` ${c.bold('bizrouter claude')} Claude Code`);
@@ -167,7 +167,7 @@ export async function authCommand() {
167
167
  const session = resolveSession();
168
168
  let failed = false;
169
169
  if (session.source === 'none') {
170
- info('콘솔 세션: 없음 — `bizrouter login` 으로 브라우저 승인을 받으면 콘솔 관리 명령을 쓸 수 있습니다.');
170
+ info('콘솔 세션: 없음 — `bizrouter login`으로 브라우저 승인을 받으면 콘솔 관리 명령을 쓸 수 있습니다.');
171
171
  }
172
172
  else {
173
173
  const where = session.source === 'env' ? `환경 변수 ${ENV_SESSION_NAME}` : `저장된 파일 ${credentialsPath()}`;
@@ -178,7 +178,7 @@ export async function authCommand() {
178
178
  catch (error) {
179
179
  failed = true;
180
180
  fail(`콘솔 세션 무효: ${error instanceof Error ? error.message : String(error)}`);
181
- info('`bizrouter login` 으로 다시 로그인하십시오.');
181
+ info('`bizrouter login`으로 다시 로그인하십시오.');
182
182
  }
183
183
  }
184
184
  if (cred.source === 'none') {
@@ -205,7 +205,7 @@ export async function logoutCommand() {
205
205
  if (revoked)
206
206
  ok('콘솔 세션을 서버에서 종료했습니다.');
207
207
  else
208
- warn(`콘솔 세션을 서버에서 종료하지 못했습니다. ${consoleUrl()}${ACCOUNT_PAGE_PATH} 의 「로그인된 기기」에서 직접 종료할 수 있습니다.`);
208
+ warn(`콘솔 세션을 서버에서 종료하지 못했습니다. ${consoleUrl()}${ACCOUNT_PAGE_PATH}의 「로그인된 기기」에서 직접 종료할 수 있습니다.`);
209
209
  }
210
210
  const removed = clearCredential();
211
211
  if (removed)
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
2
2
  import { ENV_KEY_NAME } from '../config.js';
3
3
  import { c, info, print } from '../ui.js';
4
4
  import { requireApiKey } from './shared.js';
5
- export const VERSION = '0.2.0';
5
+ export const VERSION = '0.3.0';
6
6
  /** `eval "$(bizrouter env)"` exports the saved key for tools configured by `bizrouter setup`. */
7
7
  export function envCommand() {
8
8
  const key = requireApiKey();
@@ -23,20 +23,23 @@ export function updateCommand() {
23
23
  });
24
24
  }
25
25
  export function helpText() {
26
- return `${c.bold('bizrouter')} — 코딩 에이전트를 BizRouter 로 연결하고, BizRouter 콘솔을 명령으로 다룹니다. (v${VERSION})
26
+ return `${c.bold('bizrouter')} — 코딩 에이전트를 BizRouter로 연결하고, BizRouter 콘솔을 명령으로 다룹니다. (v${VERSION})
27
27
 
28
28
  ${c.bold('시작하기')}
29
29
  bizrouter login 브라우저에서 승인하면 콘솔 세션과 API 키가 함께 저장됩니다 (한 번만)
30
- bizrouter claude Claude Code 를 BizRouter 로 실행합니다
31
- bizrouter codex Codex CLI 를 BizRouter 로 실행합니다
32
- bizrouter opencode OpenCode 를 BizRouter 로 실행합니다
33
- bizrouter hermes Hermes 를 BizRouter 로 실행합니다
30
+ bizrouter claude Claude Code를 BizRouter로 실행합니다
31
+ bizrouter codex Codex CLI를 BizRouter로 실행합니다
32
+ bizrouter opencode OpenCode를 BizRouter로 실행합니다
33
+ bizrouter hermes Hermes를 BizRouter로 실행합니다
34
+ 도구는 평소처럼 실행되고, 이번 실행의 모델 호출·과금만 BizRouter를 거칩니다. 설정 파일은 바뀌지 않습니다.
35
+ bizrouter search "질문" 모델이 법령·공시·통계·웹 검색 등 실제 데이터를 찾아 출처와 함께 답합니다 (Search)
34
36
 
35
37
  ${c.bold('실행 옵션')} (실행 명령 뒤, 도구 자체 옵션 앞에 씁니다)
36
38
  --model <ID> BizRouter 모델 ID (예: anthropic/claude-sonnet-5, openai/gpt-5.5)
37
39
  --reasoning-effort <단계> minimal · low · medium · high · xhigh (Codex·OpenCode)
38
- --closed-network Claude Code 부수 트래픽(버전 확인·텔레메트리)을 끕니다
39
- --no-mcp 콘솔 MCP 서버(bizrouter) 를 에이전트에 연결하지 않습니다
40
+ --search 모델에 Search를 켭니다(<모델>:search · OpenCode·Hermes). Claude Code·Codex는 MCP 도구 bizrouter_search로
41
+ --closed-network Claude Code의 부수 트래픽(버전 확인·텔레메트리) 끕니다
42
+ --no-mcp 콘솔 MCP 서버(bizrouter)를 에이전트에 연결하지 않습니다
40
43
  --dry-run 실행하지 않고 넘겨줄 환경 변수와 명령만 보여줍니다
41
44
  나머지 옵션은 그대로 도구에 전달됩니다. 예: bizrouter claude -p "테스트 고쳐줘"
42
45
 
@@ -53,9 +56,10 @@ ${c.bold('콘솔 관리')} (브라우저 승인 로그인 뒤 · 모두 --json
53
56
 
54
57
  ${c.bold('그 밖에')}
55
58
  bizrouter models [검색어] 이 키로 쓸 수 있는 모델과 원화 단가
59
+ bizrouter search "질문" Search 호출 (--model, --max-tokens, --json, --no-stream · 답은 stdout, 출처는 stderr)
56
60
  bizrouter docs [주제] 에이전트 매뉴얼 · 개발 문서 (--list, --full)
57
- bizrouter mcp MCP 서버(stdio)로 실행 — 에이전트가 콘솔을 도구로 다룹니다
58
- bizrouter setup <도구> claude · codex · opencode 설정 파일에 BizRouter 를 영구 등록
61
+ bizrouter mcp MCP 서버(stdio)로 실행 — 에이전트가 콘솔·Search를 도구로 다룹니다
62
+ bizrouter setup <도구> claude · codex · opencode 설정 파일에 BizRouter를 영구 등록
59
63
  bizrouter doctor 연결·키·세션·설치·충돌 점검
60
64
  bizrouter auth 지금 어떤 계정·키가 쓰이는지 확인
61
65
  bizrouter env eval "$(bizrouter env)" 로 ${ENV_KEY_NAME} 내보내기
@@ -53,5 +53,6 @@ export async function modelsCommand(argv) {
53
53
  print();
54
54
  print(c.dim(`${rows.length}개 · 원화 단가는 100만 토큰당 · 환율 ${catalog.exchange_rate}원/달러 · ${catalog.from_cache ? '캐시(최대 10분)' : '지금 조회'}`));
55
55
  print(c.dim('예: bizrouter codex --model openai/gpt-5.5 · bizrouter claude --model anthropic/claude-opus-5'));
56
+ print(c.dim('Search: 어느 채팅 모델이든 뒤에 :search 를 붙이면 실제 데이터를 조회해 답합니다 (예: bizrouter search "질문" --model openai/gpt-5.5)'));
56
57
  return 0;
57
58
  }