bizrouter 0.1.0 → 0.2.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 +195 -0
- package/README.md +42 -9
- package/dist/args.js +5 -1
- package/dist/commands/api.js +152 -0
- package/dist/commands/console.js +580 -0
- package/dist/commands/docs.js +160 -0
- package/dist/commands/doctor.js +16 -2
- package/dist/commands/login.js +153 -29
- package/dist/commands/misc.js +23 -8
- package/dist/config.js +26 -1
- package/dist/console.js +275 -0
- package/dist/harness/claude.js +4 -0
- package/dist/harness/codex.js +3 -1
- package/dist/harness/hermes.js +12 -0
- package/dist/harness/mcp.js +47 -0
- package/dist/harness/opencode.js +6 -1
- package/dist/index.js +50 -4
- package/dist/mcp.js +229 -0
- package/package.json +7 -4
package/AGENT_MANUAL.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# BizRouter CLI — manual for coding agents
|
|
2
|
+
|
|
3
|
+
`bizrouter docs` prints this file. It is written for an AI coding agent (Claude
|
|
4
|
+
Code, Codex, OpenCode, Hermes, …) that has the `bizrouter` CLI installed and
|
|
5
|
+
needs to operate a BizRouter account on the user's behalf. The human-facing
|
|
6
|
+
documentation at https://bizrouter.ai/docs is mirrored, from the same source,
|
|
7
|
+
at https://bizrouter.ai/llms-full.txt (`bizrouter docs <topic>` reads it).
|
|
8
|
+
|
|
9
|
+
## 1. What BizRouter is
|
|
10
|
+
|
|
11
|
+
BizRouter (https://bizrouter.ai) is an LLM gateway for Korean companies: one
|
|
12
|
+
API key, one bill in KRW, many providers (OpenAI, Anthropic, Google, xAI,
|
|
13
|
+
Perplexity, Upstage, z.AI, domestic serving). Around the gateway sits a console
|
|
14
|
+
where an organization manages API keys, model access, Smart Routing, security
|
|
15
|
+
filters (PII masking, forbidden words, Corepin), audit logs, statistics,
|
|
16
|
+
billing and members. **Everything the console does is an HTTP API**, and this
|
|
17
|
+
CLI can call all of it.
|
|
18
|
+
|
|
19
|
+
## 2. Two credentials, two purposes
|
|
20
|
+
|
|
21
|
+
| Credential | Used for | Where it comes from |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| API key `sk-br-v1-…` | Model calls: `https://api.bizrouter.ai/v1/*`, `/v1/messages`, `/claude/*`. Also what the launched coding agents use. | Issued during `bizrouter login` (browser approval) or pasted with `bizrouter login --with-key`. Env override: `BIZROUTER_API_KEY`. |
|
|
24
|
+
| Console session token | Console management API `https://bizrouter.ai/api/web/*` (`bizrouter api`, `keys`, `usage`, …). 30 days. | Minted when the user approves `bizrouter login` in the browser. Env override: `BIZROUTER_SESSION_TOKEN`. |
|
|
25
|
+
|
|
26
|
+
Both are stored in `~/.bizrouter/credentials.json` (mode 600;
|
|
27
|
+
`BIZROUTER_CONFIG_DIR` relocates it). `bizrouter auth` shows what is present
|
|
28
|
+
and whether it is still valid. The session carries the **user's role**:
|
|
29
|
+
`owner` can manage the whole organization, `member` sees only their own keys
|
|
30
|
+
and usage.
|
|
31
|
+
|
|
32
|
+
An API key cannot manage the console. A console session cannot call models.
|
|
33
|
+
If a management command fails with "콘솔 세션이 없습니다", ask the user to run
|
|
34
|
+
`bizrouter login` (it opens a browser; the user approves an 8-character code).
|
|
35
|
+
You cannot complete that approval yourself — it is deliberately a human step.
|
|
36
|
+
|
|
37
|
+
## 3. Command cheat sheet
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
bizrouter login [--with-key] [--no-browser] sign in (browser approval; --with-key pastes an API key)
|
|
41
|
+
bizrouter auth which credentials exist and whether they work
|
|
42
|
+
bizrouter logout revoke the console session, delete local credentials
|
|
43
|
+
bizrouter doctor connectivity, key, session, installed agents, conflicts
|
|
44
|
+
bizrouter models [query] [--json] models this key may use, KRW prices per 1M tokens
|
|
45
|
+
|
|
46
|
+
bizrouter claude|codex|opencode|hermes [--model ID] [--reasoning-effort L] [--no-mcp] [tool args…]
|
|
47
|
+
run a coding agent through BizRouter (config files untouched)
|
|
48
|
+
|
|
49
|
+
bizrouter api --list [keyword] every console API operation (from the live OpenAPI)
|
|
50
|
+
bizrouter api --schema [METHOD] <path> request/response fields of one operation
|
|
51
|
+
bizrouter api [METHOD] <path> [-q k=v]… [-d JSON|@file] [--yes]
|
|
52
|
+
call any console API; JSON on stdout
|
|
53
|
+
|
|
54
|
+
bizrouter keys list|create|update|delete API keys (name, limits, allowed models/IPs)
|
|
55
|
+
bizrouter usage [--from D --to D] [--by key|model|user] spend and tokens (statistics)
|
|
56
|
+
bizrouter billing credits, monthly bills
|
|
57
|
+
bizrouter org show|members|settings [k=v…] organization, members, org-level toggles
|
|
58
|
+
bizrouter audit list|show <id>|stats request audit logs
|
|
59
|
+
bizrouter routing policy|presets|profiles|logs Smart Routing
|
|
60
|
+
bizrouter security show|access content filters, console access (MFA, IP allowlist)
|
|
61
|
+
|
|
62
|
+
bizrouter docs [topic|--list|--full] this manual; developer docs from llms-full.txt
|
|
63
|
+
bizrouter mcp run as an MCP server (stdio) exposing the above as tools
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
All management commands accept `--json` and print exactly the server's JSON,
|
|
67
|
+
so you can pipe into `jq`. Without `--json` they print a compact table.
|
|
68
|
+
Exit code 0 = success, 1 = error (message on stderr; for `api`, the server's
|
|
69
|
+
structured error is also printed on stdout as `{"status":…,"error":…}`).
|
|
70
|
+
|
|
71
|
+
## 4. Rules of engagement (read before mutating anything)
|
|
72
|
+
|
|
73
|
+
1. **Reads are free; writes need the user's OK.** Listing keys, usage,
|
|
74
|
+
policies and logs is fine. Creating, updating or deleting anything (keys,
|
|
75
|
+
policies, security settings, members, credits) must be confirmed with the
|
|
76
|
+
user first — quote what you are about to change. `bizrouter api` enforces
|
|
77
|
+
this: `POST/PUT/PATCH/DELETE` prompt for confirmation in a terminal and
|
|
78
|
+
refuse without `--yes` in a pipe. Add `--yes` only after the user agreed.
|
|
79
|
+
2. **Never print a full API key into a file, log, commit or chat reply.**
|
|
80
|
+
`keys create` returns `full_key` once; hand it to the place it belongs
|
|
81
|
+
(env var, secret store) and show the user only the masked preview.
|
|
82
|
+
3. **Do not touch money without an explicit request**: `/payments/*`,
|
|
83
|
+
`/organizations/credits` (PATCH), auto-charge settings, referral payouts.
|
|
84
|
+
4. **Console access controls can lock people out.** Enabling the IP
|
|
85
|
+
allowlist (`/security/console-access`) or MFA requirement affects every
|
|
86
|
+
member's sign-in. The server refuses changes that would lock out the
|
|
87
|
+
caller, but not changes that lock out others. Confirm the exact list of
|
|
88
|
+
CIDRs with the user.
|
|
89
|
+
5. **Audit log on/off is an organization setting**: `skip_audit_log=true`
|
|
90
|
+
stops storing prompts/responses for new requests (billing rows remain).
|
|
91
|
+
Turning it on again does not recover skipped content. Say so.
|
|
92
|
+
6. **Members** can only see and manage their own API keys; commands that
|
|
93
|
+
need `owner` return 403 — report it, do not retry with tricks.
|
|
94
|
+
7. Prefer the wrapped commands (`keys`, `usage`, …) for common tasks; use
|
|
95
|
+
`bizrouter api` for everything else. Look the operation up with
|
|
96
|
+
`--list`/`--schema` before guessing a field name.
|
|
97
|
+
|
|
98
|
+
## 5. Console API map (paths are relative to https://bizrouter.ai/api/web)
|
|
99
|
+
|
|
100
|
+
| Area | Paths | Notes |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| Account, sessions | `GET /auth/profile` · `GET /auth/sessions` · `DELETE /auth/sessions/{id}` · `GET/POST /auth/mfa…` | who am I; devices logged in |
|
|
103
|
+
| API keys | `GET/POST /api-keys` · `PATCH/DELETE /api-keys/{key_id}` | body: `name`, `is_active`, `credit_limit` (KRW), `credit_limit_period` lifetime/daily/monthly, `limit_alert_enabled`, `limit_alert_threshold` (%), `limit_alert_recipient_emails[]`, `allowed_ips[]` (CIDR), `allowed_model_codes[]` (empty = all) |
|
|
104
|
+
| Usage API keys | `/usage-api-keys` | keys for the read-only Usage API (`https://api.bizrouter.ai/usage/v1/*`) |
|
|
105
|
+
| Models | `GET /models` · `GET/POST/DELETE /model-blacklist…` | catalog; org-wide model blocklist |
|
|
106
|
+
| Organization | `GET /organizations/{id}` · `GET/PATCH /organizations/settings` · `GET /organizations/{id}/users` · `PATCH/DELETE /organizations/{id}/users/{user_id}` · `POST /organizations/invitations` | settings: `skip_audit_log`, `block_file_upload`, Claude Code model mappings (`claude_code_*_model_id`) |
|
|
107
|
+
| Billing | `GET /organizations/credits` · `GET /organizations/billing/monthly?months=N` · `GET /organizations/billing/evidence…` · `/payments/*` | read freely; never change without being asked |
|
|
108
|
+
| Statistics | `GET /statistics/?start_date&end_date&models&api_keys&users&limit&offset` · `GET /statistics/filter-options` · `GET /statistics/charts/spend|tokens|requests|api-key-model-distribution|api-key-daily-trend` · `…/export` | dates `YYYY-MM-DD`; comma-separated filters |
|
|
109
|
+
| Audit logs | `GET /audit-logs/?api_key&model&status&start_date&end_date&search&cursor&limit` · `GET /audit-logs/{log_id}` · `GET /audit-logs/stats/summary` · `POST /audit-logs/exports` | request-level records incl. prompts unless `skip_audit_log` |
|
|
110
|
+
| Smart Routing | `GET/PATCH /smart-routing/policy` · `POST /smart-routing/policy/presets` · `GET /smart-routing/policy/diagnostics` · `GET/POST /smart-routing/profiles` · `GET/PATCH/DELETE /smart-routing/profiles/{id}` · `GET /smart-routing/pool-models` · `GET /smart-routing/logs` · `/smart-routing/taxonomy-rules…` · `/smart-routing/review-candidates…` | the virtual model `bizrouter/route` follows the default policy |
|
|
111
|
+
| Security | `GET/PATCH /security` · `GET/POST/DELETE /security/forbidden-words…` · `GET /security/corepin/catalog` · `GET/PATCH /security/console-access` · `POST/DELETE /security/console-access/allowed-ips…` | PII masking, forbidden words, Corepin; MFA/IP policy |
|
|
112
|
+
| BYOK | `GET /byok/` · `PUT/DELETE /byok/{provider_name}` · `POST /byok/test` | customer's own provider keys |
|
|
113
|
+
| Evals | `/evals/*` | datasets, rubrics, runs (owner-enabled feature) |
|
|
114
|
+
| Release notes | `GET /release-notes` | what changed in the product |
|
|
115
|
+
|
|
116
|
+
`bizrouter api --list` is authoritative — the table above is a map, not the
|
|
117
|
+
contract. `bizrouter api --schema PATCH /organizations/settings` shows the
|
|
118
|
+
exact fields.
|
|
119
|
+
|
|
120
|
+
## 6. Worked examples
|
|
121
|
+
|
|
122
|
+
Create a key for a project, limited to two models and ₩300,000 a month:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
bizrouter api --schema POST /api-keys
|
|
126
|
+
bizrouter keys create "growth-bot" --limit 300000 --period monthly \
|
|
127
|
+
--models openai/gpt-5.6-sol,anthropic/claude-sonnet-5 --json
|
|
128
|
+
# → full_key appears once. Put it where it belongs; show the user key_preview only.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This month's spend by API key, then by model:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
bizrouter usage --from 2026-09-01 --to 2026-09-30 --by key
|
|
135
|
+
bizrouter usage --from 2026-09-01 --to 2026-09-30 --by model --json | jq '.items[] | {model, cost}'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Turn audit logging off for the organization (after the user confirmed):
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
bizrouter org settings skip_audit_log=true --yes
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Why did requests fail this morning?
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
bizrouter audit list --status error --from 2026-09-06 --limit 20
|
|
148
|
+
bizrouter audit show <log_id>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Recommend and apply a Smart Routing preset:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
bizrouter routing presets # server suggests presets for the org's traffic
|
|
155
|
+
bizrouter routing policy # current default policy
|
|
156
|
+
bizrouter api PATCH /smart-routing/policy -d '{"strategy":"cost_optimized"}' --yes
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Anything not wrapped:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
bizrouter api --list byok
|
|
163
|
+
bizrouter api PUT /byok/openai -d '{"api_key":"sk-…"}' --yes
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 7. Running coding agents
|
|
167
|
+
|
|
168
|
+
`bizrouter claude|codex|opencode|hermes` launches the tool with BizRouter
|
|
169
|
+
injected for that process only (env + the tool's own override mechanism); the
|
|
170
|
+
user's config files are not modified. `--model <id>` picks a BizRouter model
|
|
171
|
+
(`bizrouter models` lists them), `--reasoning-effort` maps to the tool's
|
|
172
|
+
setting, everything after our flags is passed through untouched.
|
|
173
|
+
|
|
174
|
+
When a console session exists, the launcher also registers this CLI as an MCP
|
|
175
|
+
server named `bizrouter` inside Claude Code, Codex and OpenCode for that run
|
|
176
|
+
(`--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.
|
|
180
|
+
|
|
181
|
+
Claude Code can only run Anthropic models (it always sends Anthropic-only
|
|
182
|
+
fields); use Codex/OpenCode/Hermes for GPT or Gemini.
|
|
183
|
+
|
|
184
|
+
## 8. Troubleshooting
|
|
185
|
+
|
|
186
|
+
- `콘솔 세션이 없습니다` / 401 → `bizrouter login` (human step).
|
|
187
|
+
- 403 with a message about IP → the organization's console IP allowlist blocks
|
|
188
|
+
this machine; the user must add the address in the console or from an
|
|
189
|
+
allowed network.
|
|
190
|
+
- 403 on management calls → the account is a `member`; needs an `owner`.
|
|
191
|
+
- Model calls return 400 `model not found` → the key's allowed-model list or
|
|
192
|
+
the organization's model policy excludes it (`bizrouter models` shows what the
|
|
193
|
+
key may use).
|
|
194
|
+
- Hermes + model with output cap under 65536 → pick another model
|
|
195
|
+
(`bizrouter hermes --model anthropic/claude-sonnet-5`).
|
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# bizrouter CLI
|
|
2
2
|
|
|
3
|
-
쓰던 코딩 에이전트를 그대로
|
|
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
|
|
7
|
-
bizrouter login # 브라우저에서
|
|
7
|
+
bizrouter login # 브라우저에서 코드를 확인하고 승인합니다 (한 번만)
|
|
8
8
|
bizrouter claude # Claude Code
|
|
9
9
|
bizrouter codex # Codex CLI
|
|
10
10
|
bizrouter opencode # OpenCode
|
|
@@ -13,11 +13,20 @@ bizrouter hermes # Hermes
|
|
|
13
13
|
|
|
14
14
|
설치 없이 바로 써 볼 수도 있습니다: `npx bizrouter claude`
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## 로그인이 해 주는 것
|
|
17
|
+
|
|
18
|
+
`bizrouter login` 은 터미널에 8자리 코드를 보여주고 브라우저에서 BizRouter 콘솔을 엽니다. 콘솔에 로그인(비밀번호·Google·웍스AI·2차 인증 그대로)한 뒤 코드를 확인하고 「승인」하면
|
|
19
|
+
|
|
20
|
+
- 이 기기용 **콘솔 세션**(30일)과
|
|
21
|
+
- 이 기기 이름의 **API 키**(모델 호출용)가 함께 발급되어 `~/.bizrouter/credentials.json` 에 권한 600 으로 저장됩니다.
|
|
22
|
+
|
|
23
|
+
키만 붙여 넣던 방식도 그대로 됩니다: `bizrouter login --with-key`. CI·컨테이너에서는 `BIZROUTER_API_KEY`(모델 호출)·`BIZROUTER_SESSION_TOKEN`(콘솔) 환경 변수가 저장된 값보다 우선합니다. 세션은 콘솔 「계정 → 로그인된 기기」에서 언제든 종료할 수 있습니다.
|
|
24
|
+
|
|
25
|
+
## 코딩 에이전트 실행
|
|
17
26
|
|
|
18
27
|
| 도구 | 실행 시 하는 일 | 설정 파일 변경 |
|
|
19
28
|
| --- | --- | --- |
|
|
20
|
-
| Claude Code | `ANTHROPIC_BASE_URL=https://api.bizrouter.ai/claude`, 토큰, 빈 `ANTHROPIC_API_KEY`, 모델 선택창 자동
|
|
29
|
+
| Claude Code | `ANTHROPIC_BASE_URL=https://api.bizrouter.ai/claude`, 토큰, 빈 `ANTHROPIC_API_KEY`, 모델 선택창 자동 채움을 넘기고, 사용자 settings.json 의 다른 게이트웨이 설정이 이기지 못하도록 명령행 settings 로 고정 | 없음 |
|
|
21
30
|
| Codex CLI | `-c` 오버라이드로 BizRouter provider(Responses API)와 모델·추론 강도 지정 | 없음 |
|
|
22
31
|
| OpenCode | `OPENCODE_CONFIG_CONTENT` 로 이 키가 쓸 수 있는 모델 전체가 담긴 `bizrouter` provider 를 주입 | 없음 |
|
|
23
32
|
| Hermes | `custom` provider(`CUSTOM_BASE_URL`/`CUSTOM_API_KEY`)와 모델을 이번 실행에만 지정 | 없음 |
|
|
@@ -31,20 +40,44 @@ bizrouter opencode --model google/gemini-3.5-pro
|
|
|
31
40
|
bizrouter claude --dry-run # 실행 대신 넘겨줄 환경 변수와 명령을 보여줍니다
|
|
32
41
|
```
|
|
33
42
|
|
|
34
|
-
|
|
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` 로 끕니다.
|
|
44
|
+
|
|
45
|
+
## 콘솔 관리 명령
|
|
46
|
+
|
|
47
|
+
모두 `--json` 을 지원하고, 데이터를 바꾸는 명령은 터미널에서 확인을 묻거나 `--yes` 를 요구합니다.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bizrouter api --list [검색어] # 콘솔 API 전체 목록 (라이브 OpenAPI 기준)
|
|
51
|
+
bizrouter api --schema PATCH /organizations/settings # 필드 확인
|
|
52
|
+
bizrouter api /statistics/ -q start_date=2026-09-01 # 아무 콘솔 API 호출
|
|
53
|
+
bizrouter api POST /api-keys -d '{"name":"ci"}' --yes
|
|
54
|
+
|
|
55
|
+
bizrouter keys list|create|update|delete # API 키 (한도·기간·허용 모델·허용 IP)
|
|
56
|
+
bizrouter usage [--from --to] [--by model|key|day] # 사용 금액·요청 수
|
|
57
|
+
bizrouter billing # 크레딧·월별 청구
|
|
58
|
+
bizrouter org show|members|settings [k=v] # 조직·구성원·감사 로그 저장 등 조직 설정
|
|
59
|
+
bizrouter audit list|show <id>|stats # 요청 감사 로그
|
|
60
|
+
bizrouter routing policy|presets|profiles|logs # 스마트 라우팅
|
|
61
|
+
bizrouter security show|set|access # 보안 필터·콘솔 접근 통제
|
|
62
|
+
bizrouter docs [주제] # 에이전트 매뉴얼(AGENT_MANUAL.md)·개발 문서
|
|
63
|
+
bizrouter mcp # MCP 서버(stdio)로 직접 실행
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
에이전트에게 맡길 때는 `bizrouter docs` 가 출력하는 매뉴얼을 먼저 읽히면 됩니다. 사람용 개발 문서(https://bizrouter.ai/docs)는 같은 소스에서 만든 https://bizrouter.ai/llms-full.txt 로도 제공됩니다.
|
|
67
|
+
|
|
68
|
+
## 그 밖의 명령
|
|
35
69
|
|
|
36
70
|
```bash
|
|
37
71
|
bizrouter models [검색어] # 이 키로 쓸 수 있는 모델·컨텍스트·원화 단가
|
|
38
|
-
bizrouter doctor #
|
|
72
|
+
bizrouter doctor # 키·세션·연결·설치·충돌 점검
|
|
39
73
|
bizrouter setup claude|codex|opencode # 도구 설정 파일에 BizRouter 를 영구 등록 (백업 생성)
|
|
40
74
|
bizrouter auth / logout / env / update
|
|
41
75
|
```
|
|
42
76
|
|
|
43
|
-
`BIZROUTER_API_KEY` 환경 변수가 있으면 저장된 키보다 우선합니다(CI·컨테이너). 설정은 `~/.bizrouter/`(`BIZROUTER_CONFIG_DIR` 로 변경) 에 권한 600 으로 저장됩니다.
|
|
44
|
-
|
|
45
77
|
## 알아둘 것
|
|
46
78
|
|
|
47
|
-
- Claude Code 는 Anthropic 전용 필드(`thinking`, `cache_control`)를 항상 보내므로 Claude 모델만 실행할 수 있습니다. GPT·Gemini 는 Codex·OpenCode·Hermes 로
|
|
79
|
+
- Claude Code 는 Anthropic 전용 필드(`thinking`, `cache_control`)를 항상 보내므로 Claude 모델만 실행할 수 있습니다. GPT·Gemini 는 Codex·OpenCode·Hermes 로 실행하십시오.
|
|
80
|
+
- 콘솔 세션은 로그인한 사용자의 권한(owner/member)을 그대로 따릅니다. 구성원(member)은 자기 API 키와 사용량만 다룰 수 있습니다.
|
|
48
81
|
- 요구 사항: Node.js 18 이상. 각 코딩 에이전트는 따로 설치돼 있어야 합니다.
|
|
49
82
|
|
|
50
83
|
문서: https://bizrouter.ai/docs/cli
|
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, passthrough: [] };
|
|
10
|
+
const out = { closedNetwork: false, dryRun: false, help: false, noMcp: false, passthrough: [] };
|
|
11
11
|
let i = 0;
|
|
12
12
|
const takeValue = (flag) => {
|
|
13
13
|
const value = argv[i + 1];
|
|
@@ -47,6 +47,10 @@ export function parseLaunchArgs(argv) {
|
|
|
47
47
|
out.dryRun = true;
|
|
48
48
|
i += 1;
|
|
49
49
|
continue;
|
|
50
|
+
case '--no-mcp':
|
|
51
|
+
out.noMcp = true;
|
|
52
|
+
i += 1;
|
|
53
|
+
continue;
|
|
50
54
|
case '--help':
|
|
51
55
|
case '-h':
|
|
52
56
|
out.help = true;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { ConsoleApiError, HTTP_METHODS, MUTATING_METHODS, consoleRequest, fetchOpenApi, findOperation, listOperations, normalizeConsolePath } from '../console.js';
|
|
3
|
+
import { readLine } from '../prompt.js';
|
|
4
|
+
import { c, CliError, info, padEnd, print, warn } from '../ui.js';
|
|
5
|
+
const USAGE = [
|
|
6
|
+
'bizrouter api --list [검색어] 콘솔 API 경로 목록',
|
|
7
|
+
'bizrouter api --schema [METHOD] <경로> 요청·응답 필드',
|
|
8
|
+
'bizrouter api [METHOD] <경로> [-q k=v]… [-d JSON|@파일] [--yes]',
|
|
9
|
+
].join('\n');
|
|
10
|
+
function parseData(raw) {
|
|
11
|
+
const text = raw.startsWith('@') ? readFileSync(raw.slice(1) === '-' ? 0 : raw.slice(1), 'utf8') : raw;
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(text);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw new CliError(`-d 값이 JSON 이 아닙니다: ${error instanceof Error ? error.message : String(error)}`, {
|
|
17
|
+
hint: `예: -d '{"name":"ci-key"}' 또는 -d @body.json (표준 입력은 -d @-)`,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function parseApiArgs(argv) {
|
|
22
|
+
const flags = { query: {}, yes: false, refresh: false, raw: false };
|
|
23
|
+
const positional = [];
|
|
24
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
25
|
+
const token = argv[i];
|
|
26
|
+
const next = () => {
|
|
27
|
+
const value = argv[i + 1];
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
throw new CliError(`${token} 뒤에 값이 필요합니다.`, { hint: USAGE });
|
|
30
|
+
i += 1;
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
if (token === '--list' || token === '-l') {
|
|
34
|
+
flags.list = argv[i + 1] && !argv[i + 1].startsWith('-') ? (i += 1, argv[i]) : true;
|
|
35
|
+
}
|
|
36
|
+
else if (token.startsWith('--list='))
|
|
37
|
+
flags.list = token.slice(7) || true;
|
|
38
|
+
else if (token === '--schema' || token === '-s')
|
|
39
|
+
flags.schema = '';
|
|
40
|
+
else if (token === '-d' || token === '--data')
|
|
41
|
+
flags.data = parseData(next());
|
|
42
|
+
else if (token.startsWith('--data='))
|
|
43
|
+
flags.data = parseData(token.slice(7));
|
|
44
|
+
else if (token === '-q' || token === '--query') {
|
|
45
|
+
const pair = next();
|
|
46
|
+
const eq = pair.indexOf('=');
|
|
47
|
+
if (eq <= 0)
|
|
48
|
+
throw new CliError(`-q 값은 key=value 형식이어야 합니다: ${pair}`);
|
|
49
|
+
flags.query[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
50
|
+
}
|
|
51
|
+
else if (token === '--yes' || token === '-y')
|
|
52
|
+
flags.yes = true;
|
|
53
|
+
else if (token === '--refresh')
|
|
54
|
+
flags.refresh = true;
|
|
55
|
+
else if (token === '--raw')
|
|
56
|
+
flags.raw = true;
|
|
57
|
+
else if (token === '-h' || token === '--help')
|
|
58
|
+
flags.list = flags.list ?? true;
|
|
59
|
+
else if (token.startsWith('-') && !/^-\d/.test(token))
|
|
60
|
+
throw new CliError(`알 수 없는 옵션입니다: ${token}`, { hint: USAGE });
|
|
61
|
+
else
|
|
62
|
+
positional.push(token);
|
|
63
|
+
}
|
|
64
|
+
// `api GET /x`, `api /x` (GET), `api POST /x`
|
|
65
|
+
const first = positional[0]?.toUpperCase();
|
|
66
|
+
if (first && HTTP_METHODS.includes(first)) {
|
|
67
|
+
flags.method = first;
|
|
68
|
+
flags.path = positional[1];
|
|
69
|
+
}
|
|
70
|
+
else if (positional[0]) {
|
|
71
|
+
flags.path = positional[0];
|
|
72
|
+
}
|
|
73
|
+
if (flags.schema === '')
|
|
74
|
+
flags.schema = flags.path ?? '';
|
|
75
|
+
if (!flags.method && flags.path)
|
|
76
|
+
flags.method = flags.data !== undefined ? 'POST' : 'GET';
|
|
77
|
+
return flags;
|
|
78
|
+
}
|
|
79
|
+
function printOperations(rows, keyword) {
|
|
80
|
+
if (!rows.length) {
|
|
81
|
+
print(keyword ? `「${keyword}」에 맞는 콘솔 API 가 없습니다.` : '콘솔 API 목록이 비어 있습니다.');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const pathWidth = Math.min(56, Math.max(...rows.map((r) => r.path.length), 10));
|
|
85
|
+
print(c.dim(`${padEnd('METHOD', 7)} ${padEnd('경로 (/api/web 기준)', pathWidth)} 설명 · 질의 인자(*=필수)`));
|
|
86
|
+
for (const r of rows) {
|
|
87
|
+
const method = MUTATING_METHODS.has(r.method) ? c.yellow(padEnd(r.method, 7)) : c.green(padEnd(r.method, 7));
|
|
88
|
+
const extras = [r.params.length ? `?${r.params.join('&')}` : '', r.hasBody ? c.dim('[본문]') : ''].filter(Boolean).join(' ');
|
|
89
|
+
print(`${method} ${padEnd(r.path, pathWidth)} ${r.summary}${extras ? ` ${c.dim(extras)}` : ''}`);
|
|
90
|
+
}
|
|
91
|
+
print();
|
|
92
|
+
print(c.dim(`${rows.length}개 · 자세한 필드: bizrouter api --schema <METHOD> <경로> · 호출: bizrouter api <METHOD> <경로> [-q k=v] [-d JSON]`));
|
|
93
|
+
}
|
|
94
|
+
export async function apiCommand(argv) {
|
|
95
|
+
const flags = parseApiArgs(argv);
|
|
96
|
+
if (flags.list !== undefined && !flags.path) {
|
|
97
|
+
const spec = await fetchOpenApi({ force: flags.refresh });
|
|
98
|
+
const keyword = flags.list === true ? undefined : flags.list;
|
|
99
|
+
printOperations(listOperations(spec, keyword), keyword);
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
if (flags.schema !== undefined) {
|
|
103
|
+
if (!flags.schema)
|
|
104
|
+
throw new CliError('--schema 뒤에 경로가 필요합니다.', { hint: USAGE });
|
|
105
|
+
const spec = await fetchOpenApi({ force: flags.refresh });
|
|
106
|
+
const method = flags.method && argv.some((a) => a.toUpperCase() === flags.method) ? flags.method : undefined;
|
|
107
|
+
const matches = findOperation(spec, method, flags.schema);
|
|
108
|
+
if (!matches.length)
|
|
109
|
+
throw new CliError(`콘솔 API 에 없는 경로입니다: ${normalizeConsolePath(flags.schema)}`, { hint: '`bizrouter api --list <검색어>` 로 찾아보십시오.' });
|
|
110
|
+
print(JSON.stringify(matches.length === 1 ? matches[0] : matches, null, 2));
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
if (!flags.path || !flags.method) {
|
|
114
|
+
print(USAGE);
|
|
115
|
+
print();
|
|
116
|
+
print(c.dim('예: bizrouter api /api-keys · bizrouter api /statistics/ -q start_date=2026-09-01 · bizrouter api POST /api-keys -d \'{"name":"ci"}\''));
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
const path = normalizeConsolePath(flags.path);
|
|
120
|
+
if (MUTATING_METHODS.has(flags.method) && !flags.yes) {
|
|
121
|
+
if (!process.stdin.isTTY) {
|
|
122
|
+
throw new CliError(`${flags.method} ${path} 는 데이터를 바꾸는 요청입니다. 확인 없이 실행하려면 --yes 를 붙이십시오.`, {
|
|
123
|
+
hint: '에이전트가 실행하는 경우, 사람에게 먼저 확인을 받은 뒤 --yes 를 붙여 다시 실행하십시오.',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
warn(`${flags.method} ${path} 는 데이터를 바꾸는 요청입니다.`);
|
|
127
|
+
if (flags.data !== undefined)
|
|
128
|
+
print(c.dim(JSON.stringify(flags.data, null, 2)));
|
|
129
|
+
const answer = await readLine('실행할까요? (y/N) ');
|
|
130
|
+
if (!/^y(es)?$/i.test(answer)) {
|
|
131
|
+
info('취소했습니다.');
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const { status, data } = await consoleRequest(flags.method, path, { query: flags.query, body: flags.data });
|
|
137
|
+
if (data === undefined)
|
|
138
|
+
print(JSON.stringify({ status }));
|
|
139
|
+
else if (typeof data === 'string')
|
|
140
|
+
print(flags.raw ? data : JSON.stringify({ status, body: data }));
|
|
141
|
+
else
|
|
142
|
+
print(JSON.stringify(data, null, 2));
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (error instanceof ConsoleApiError && error.body !== undefined && typeof error.body === 'object') {
|
|
147
|
+
// Keep the server's structured error on stdout too so agents can parse it.
|
|
148
|
+
print(JSON.stringify({ status: error.status, error: error.body }, null, 2));
|
|
149
|
+
}
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|