Services
claude-cli — sidecar phân tích thị trường— market-analysis sidecar
Container nhỏ bọc Claude Code CLI để fxconsole chạy phần nhận định của trang Analysis bằng gói subscription của team thay vì API key tính tiền theo lượt. Trang này giải thích cách đăng nhập một lần, giữ credentials qua các lần redeploy và cách fxconsole gọi sang. A tiny container wrapping the Claude Code CLI so fxconsole can write the narrative on the Analysis page with the team's Claude subscription instead of a metered API key. This page covers the one-time login, keeping credentials across redeploys, and how fxconsole calls it.
Mục đíchPurpose
Lệnh analysis:run của fxconsole tạo các thẻ phân tích trên trang /analysis theo kiểu lai: các mức giá được tính cố định từ dữ liệu OHLC bằng PHP, còn đoạn nhận định do Claude viết. fxconsole gửi prompt sang container này; container chạy claude -p đã đăng nhập bằng subscription Claude hằng tháng của team (OAuth), nên tính năng phân tích không phải trả thêm tiền cho API key tính theo lượt.
fxconsole's analysis:run command builds the cards on /analysis in a hybrid way: price levels are computed deterministically from OHLC data in PHP, and Claude writes the narrative. fxconsole posts the prompt to this container, which runs claude -p authenticated with the team's monthly Claude subscription (OAuth), so the analysis feature does not also pay for a metered API key.
Subscription được bán cho việc lập trình tương tác. Dùng nó từ một backend chạy 24/7 có thể bị coi là sử dụng tự động và làm ảnh hưởng subscription của cả team. Giữ lưu lượng thấp: ít cặp tiền, chu kỳ chạy thưa. Trang admin /admin/analysis hiển thị cảnh báo quota vì lý do này. Đây là công cụ nội bộ, ít truy cập, và nên giữ nguyên như vậy.
A subscription is sold for interactive developer use. Driving it from a 24/7 backend may be flagged as automated use and could put the whole team's subscription at risk. Keep volume low: few pairs, a relaxed interval. The admin UI (/admin/analysis) shows a quota warning for this reason. This is an internal, low-traffic tool; keep it that way.
Vị trí trong hệ thốngHow it fits together
flowchart LR
CRON["Scheduler: analysis:run"] -- "POST /analyze (model, system, prompt)" --> CLI["claude-cli :8788"]
ADM["/admin/analysis"] -- "/health, /models, /relogin/*" --> CLI
CLI -- "claude -p --output-format json" --> OAUTH["OAuth (subscription của team)"]
VOL[("Volume claude_creds / claude_config")] -. "giữ credentials qua redeploy" .- CLIflowchart LR
CRON["Scheduler: analysis:run"] -- "POST /analyze (model, system, prompt)" --> CLI["claude-cli :8788"]
ADM["/admin/analysis"] -- "/health, /models, /relogin/*" --> CLI
CLI -- "claude -p --output-format json" --> OAUTH["OAuth (team subscription)"]
VOL[("Volumes claude_creds / claude_config")] -. "credentials survive redeploys" .- CLIContainer chỉ nằm trên default network của compose, không publish cổng ra host. fxconsole (container app và scheduler) gọi nó tại http://claude-cli:8788, lấy từ setting claude_cli_url (mặc định từ biến CLAUDE_CLI_URL).
It runs on the compose default network only, with no published port. fxconsole (the app and scheduler containers) reaches it at http://claude-cli:8788, taken from the claude_cli_url setting (default from the CLAUDE_CLI_URL variable).
Service trong docker-composeThe compose service
claude-cli:
build:
context: ../claude-cli
dockerfile: Dockerfile
# args: { CLAUDE_CODE_VERSION: "x.y.z" }
container_name: fxconsole-claude-cli
environment:
TZ: Asia/Ho_Chi_Minh
PORT: "8788"
ANALYZE_CONCURRENCY: "1"
ANALYZE_TIMEOUT_MS: "900000"
expose:
- "8788"
volumes:
- claude_creds:/home/node/.claude
- claude_config:/home/node/.config
restart: unless-stopped
- Image dựa trên
node:22-bookworm-slim, cài@anthropic-ai/claude-codetoàn cục (build argCLAUDE_CODE_VERSION, mặc địnhlatest) và server Express trongserver.js.node-ptycầnpython3,make,g++lúc build. - Chạy dưới user
node; thư mục/home/node/.claudevà/home/node/.configđược tạo sẵn để volume thuộc quyềnnode(UID 1000) chứ không phải root. ANALYZE_CONCURRENCY: "1": chỉ một lệnhclaude -pchạy cùng lúc để bảo vệ quota chung. Chỉ tăng khi thật cần.- Hai named volume
claude_credsvàclaude_configđược khai báo ở cuối file compose; chúng cấp quyền truy cập subscription nên phải backup và giữ kín.
- The image is based on
node:22-bookworm-slim, installs@anthropic-ai/claude-codeglobally (build argCLAUDE_CODE_VERSION, defaultlatest) plus the Express server inserver.js.node-ptyneedspython3,makeandg++at build time. - It runs as the
nodeuser;/home/node/.claudeand/home/node/.configare pre-created so the mounted volumes are owned bynode(UID 1000), not root. ANALYZE_CONCURRENCY: "1": only oneclaude -pat a time, to protect the shared quota. Raise it cautiously.- The
claude_credsandclaude_confignamed volumes are declared at the bottom of the compose file; they grant access to the subscription, so back them up and keep them private.
Endpoint (chỉ nội bộ)Endpoints (internal only)
| Method | Path | Công dụngPurpose |
|---|---|---|
| GET | /health | Liveness + cờ authed ước lượng (dựa vào file credentials có tồn tại), kèm queue, active, pty. Điều khiển banner đỏ trên trang admin.Liveness + a best-effort authed flag (based on a credentials file being present), plus queue, active, pty. Drives the red banner in the admin UI. |
| GET | /models | Danh sách model chọn sẵn cho dropdown admin.Curated model pick-list for the admin dropdown. |
| POST | /analyze | {model, system, prompt, timeoutMs, web_search, effort} → {ok, text, usage}. Được xếp hàng và có timeout.{model, system, prompt, timeoutMs, web_search, effort} → {ok, text, usage}. Queued and timed out. |
| POST | /relogin/start | Bắt đầu OAuth tương tác, trả {session_id, url}.Begin interactive OAuth, return {session_id, url}. |
| POST | /relogin/complete | {session_id, code} → {ok}: hoàn tất đăng nhập.{session_id, code} → {ok}: finish the login. |
- Mặc định
/analyzechạy không có tool (--allowed-tools ""): một lượt, chi phí dự đoán được. Khiweb_search=true, riêng lệnh đó được bậtWebSearch,WebFetchqua cả--toolsvà--allowed-tools(ở chế độ-pkhông ai trả lời hộp xin quyền); lệnh thành vòng lặp agent nhiều lượt, tốn gấp nhiều lần. effortchỉ nhậnlow,medium,high,xhigh,max; giá trị khác bị bỏ qua thay vì làm lệnh lỗi.usagegồm token vào/ra/cache,cost_usd, số lượt và số lần web search, đọc phòng thủ từ envelope JSON của CLI. fxconsole cộng dồn vào Redis để áp ngân sách riêng.- Re-login cần giữ nguyên một tiến trình
claude loginsống (dưới PTY) giữa hai lệnh, vì OAuth-PKCE giữcode_verifiertrong bộ nhớ tiến trình. Phiên sống tối đa 10 phút;/relogin/startmới sẽ huỷ phiên dở trước đó.
- By default
/analyzeruns with no tools (--allowed-tools ""): one turn, predictable spend. Withweb_search=truethat single call getsWebSearch,WebFetchthrough both--toolsand--allowed-tools(in-pmode nobody can answer a permission prompt); the call becomes a multi-turn agent loop and costs several times as much. effortaccepts onlylow,medium,high,xhigh,max; anything else is dropped rather than failing the call.usagecarries input/output/cache tokens,cost_usd, turns and web-search count, read defensively from the CLI's JSON envelope. fxconsole adds it up in Redis to enforce its own budget.- Re-login must keep one
claude loginprocess alive (under a PTY) across the two calls, because OAuth-PKCE holds thecode_verifierin process memory. A session lives at most 10 minutes; a new/relogin/startcancels any half-finished one.
Đăng nhập lần đầuFirst-time login
Credentials nằm trong home của user node, gắn trên hai named volume claude_creds / claude_config nên vẫn còn sau docker compose up --build. Chạy trên Docker host, trong thư mục monitor/fxconsole:
Credentials live under the node user's home, mounted on the claude_creds / claude_config named volumes so they survive docker compose up --build. Run on the Docker host, in monitor/fxconsole:
docker compose exec -it claude-cli claude login
- Mở URL được in ra bằng trình duyệt bất kỳ và chấp thuận.Open the printed URL in any browser and approve.
- Dán code nhận được trở lại terminal.Paste the code back into the terminal.
- Xong: container phục vụ
/analyzecho tới khi refresh token hết hạn hẳn.Done: the container serves/analyzeuntil the refresh token hard-expires.
Khi refresh token hết hạn (vài tuần đến vài tháng)When the refresh token expires (every few weeks or months)
Bạn không sửa .env. Chọn một trong hai cách:
- Từ giao diện:
/admin/analysishiện banner đỏ "CLI cần đăng nhập lại" → bấm Re-login → một URL xuất hiện → mở, copy code → dán vào và Save. - Từ shell:
docker compose exec -it claude-cli claude login.
Hằng ngày CLI tự làm mới access token ngắn hạn; bạn chỉ phải đụng tới thỉnh thoảng. Lưu ý cờ authed chỉ kiểm tra file credentials có tồn tại, không đọc được thời điểm refresh token hết hạn; dấu hiệu thật là các lần chạy bắt đầu lỗi xác thực.
You do not edit .env. Either:
- From the UI:
/admin/analysisshows a red "CLI cần đăng nhập lại" banner → click Re-login → a URL appears → open it, copy the code → paste and Save. - From a shell:
docker compose exec -it claude-cli claude login.
Day to day the CLI auto-refreshes the short-lived access token; you only touch this occasionally. Note that authed only checks that a credentials file exists and cannot read the refresh-token expiry; the real signal is runs starting to fail with auth errors.
Backup credentialsBacking up credentials
Credentials OAuth chỉ là file trên volume. Backup sau mỗi lần đăng nhập thành công để mất volume không bắt phải đăng nhập lại:
The OAuth credentials are just files on the volume. Back them up after a successful login so a lost volume doesn't force a re-auth:
docker compose exec claude-cli sh -c 'tar czf - -C /home/node .claude .config' > claude-creds-backup.tgz
# restore:
docker compose exec -T claude-cli sh -c 'tar xzf - -C /home/node' < claude-creds-backup.tgz
Ai có file claude-creds-backup.tgz là dùng được subscription của team. Không commit, không gửi qua kênh chat.
Whoever holds claude-creds-backup.tgz can use the team subscription. Never commit it or send it over chat.
fxconsole gọi sidecar thế nàoHow fxconsole calls the sidecar
ClaudeServicecó hai engine:cli(mặc định, đang dùng: sidecar này) vàapi(Messages API tính tiền, để dự phòng, chỉ chọn khi admin đặtanalysis_engine=apivà có key). Engineclikhông tự rơi về API.- CLI không ép được output theo JSON schema, nên
analyzeViaCli()yêu cầu đúng một JSON object (bias,summary_md,scenario_up,scenario_down,next_event,confidence), bóc JSON đầu tiên cân bằng ngoặc, và thử lại tối đa 3 lần với lời nhắc chặt hơn. Báo cáo dài dùnganalyzeTextViaCli()(văn bản thuần, tối đa 2 lần). - Mọi lần gọi đã tới model đều được ghi vào ngân sách (
AnalysisBudget), kể cả khi trả lời hỏng. Lỗitimeout afterkhông thử lại (thử lại chỉ tốn thêm quota với cùng giới hạn thời gian). - Timeout: lệnh thường 90 s; lệnh có web search lấy
analysis_web_timeout_ms(mặc định 280 000 ms, trần 900 000 ms). HTTP client luôn chờ thêm 20 s so với timeout gửi cho sidecar. Sidecar kẹp giá trị này theoANALYZE_TIMEOUT_MScủa nó (compose đặt 900 000). - Trang admin gọi
/healthvà/models(timeout 8 s),/relogin/start(35 s),/relogin/complete(60 s) qua các route/admin/analysis/health,/admin/analysis/relogin/start,/admin/analysis/relogin/complete.
ClaudeServicehas two engines:cli(default and active: this sidecar) andapi(the metered Messages API, dormant, used only if an admin setsanalysis_engine=apiand a key exists). Thecliengine never falls back to the API.- The CLI cannot enforce a JSON schema, so
analyzeViaCli()demands a single JSON object (bias,summary_md,scenario_up,scenario_down,next_event,confidence), extracts the first balanced object, and retries up to 3 times with a stricter nudge. Long reports useanalyzeTextViaCli()(plain text, up to 2 attempts). - Every attempt that reached the model is recorded in the budget (
AnalysisBudget), even a malformed reply. Atimeout aftererror is not retried (a retry gets the same clock and only burns more quota). - Timeouts: a normal call gets 90 s; a web-search call uses
analysis_web_timeout_ms(default 280 000 ms, max 900 000 ms). The HTTP client always waits 20 s longer than the timeout it sends. The sidecar clamps the value to its ownANALYZE_TIMEOUT_MS(compose sets 900 000). - The admin page calls
/healthand/models(8 s timeout),/relogin/start(35 s) and/relogin/complete(60 s) through the routes/admin/analysis/health,/admin/analysis/relogin/startand/admin/analysis/relogin/complete.
Lịch chạy và ngân sáchSchedule and budget
Scheduler chạy analysis:run --scope=global mỗi phút nhưng chỉ thực thi một lần cho mỗi "slot"; lệnh tự dừng khi không có cặp nào bật hoặc sidecar chưa đăng nhập. Custom prompt có lịch tuần riêng (--scope=weekly). Hai lịch dùng chung mutex analysis-run để không tranh sidecar concurrency=1. CLI không báo được quota còn lại thật của subscription, nên fxconsole tự đo phần mình tiêu. Các key cấu hình (giá trị env là mặc định; admin ghi đè trực tiếp ở /admin/analysis / /admin/settings):
The scheduler ticks analysis:run --scope=global every minute but runs it once per "slot"; the command aborts cleanly when no pairs are enabled or the sidecar isn't authed. Custom prompts have their own weekly schedule (--scope=weekly). Both share the analysis-run mutex so they never fight over a concurrency-1 sidecar. The CLI can't report the subscription's real remaining quota, so fxconsole meters its own spend. Settings (env values are defaults; admins override them live on /admin/analysis / /admin/settings):
| Key | Mặc địnhDefault | Ý nghĩaMeaning |
|---|---|---|
analysis_engine | cli | Engine viết nhận địnhNarrative engine |
gold_analysis_model | claude-sonnet-5 | Model mặc địnhDefault model |
analysis_interval_minutes | 240 | Chu kỳ chạy (tối thiểu 30)Run interval (minimum 30) |
analysis_start_time | 00:00 | Mốc neo trong ngày để các slot rơi đúng giờDaily anchor so slots land on predictable clock times |
analysis_exclude_days | trốngempty | Thứ bỏ qua, ví dụ sat,sunWeekdays to skip, e.g. sat,sun |
analysis_budget_calls_5h | 24 | Số lệnh tối đa trong 5 giờMax calls per 5 hours |
analysis_budget_calls_week | 220 | Số lệnh tối đa mỗi tuầnMax calls per week |
analysis_budget_tokens_5h / _week | 0 | Ngân sách token (0 = không giới hạn theo token)Token budgets (0 = no token cap) |
analysis_budget_threshold | 90 | Khi một cửa sổ (5 giờ hoặc tuần) chạm ngưỡng % này, lượt chạy theo lịch tự dừng; bấm ▶ thủ công vẫn chạy đượcWhen either rolling window (5 h or week) reaches this %, scheduled runs stand down; a manual ▶ still runs |
analysis_web_search | 0 | Cho phép web search (chỉ custom prompt)Allow web search (custom prompts only) |
analysis_effort | medium | Mức reasoning effortReasoning effort level |
Ghim phiên bản CLIPinning the CLI version
Image mặc định cài @anthropic-ai/claude-code@latest. Nên ghim phiên bản để một bản phát hành mới không đổi luồng đăng nhập hay cờ dòng lệnh dưới lớp /relogin:
The image installs @anthropic-ai/claude-code@latest by default. Pin it so an upstream release can't change the login flow or flags under the /relogin wrapper:
docker compose build --build-arg CLAUDE_CODE_VERSION=<x.y.z> claude-cli
Sau khi nâng phiên bản, kiểm tra trong container rằng:
claude -p --output-format json --allowed-tools "" --model <id>in ra envelope JSON có trườngresult.claude loginin URL OAuth rồi chờ code trên stdin.
Nếu lệnh con đăng nhập hoặc câu chữ thay đổi, chỉnh qua env trong docker-compose.yml: LOGIN_CMD, LOGIN_URL_RE, LOGIN_OK_RE.
After a bump, verify inside the container that:
claude -p --output-format json --allowed-tools "" --model <id>prints a JSON envelope with aresultfield.claude loginprints an OAuth URL, then waits on stdin for a pasted code.
If the login sub-command or its wording changed, adjust via env in docker-compose.yml: LOGIN_CMD, LOGIN_URL_RE, LOGIN_OK_RE.
Biến môi trườngTunables (env)
| Env | Mặc định trong codeCode default | Ý nghĩaMeaning |
|---|---|---|
PORT | 8788 | Cổng lắng nghe (nội bộ)Listen port (internal) |
ANALYZE_CONCURRENCY | 1 | Số claude -p chạy đồng thời tối đa (bảo vệ quota)Max simultaneous claude -p (protects quota) |
ANALYZE_TIMEOUT_MS | 300000 | Trần timeout mỗi request; compose đặt 900000Per-request hard ceiling; compose sets 900000 |
WEB_TOOLS | WebSearch,WebFetch | Tool bật khi web_search=trueTools unlocked when web_search=true |
CLAUDE_BIN | claude | Tên / đường dẫn binary CLICLI binary name or path |
LOGIN_CMD | login | Lệnh con đăng nhập (có thể nhiều tham số cách nhau dấu cách)Login sub-command (space-separated args ok) |
LOGIN_URL_RE | https?://\S+ | Regex lấy URL OAuth từ outputRegex to pull the OAuth URL from login output |
LOGIN_OK_RE | (logged in|login success|authenticated|success) | Dấu hiệu đăng nhập thành côngSuccess marker |
monitor/claude-cli/README.md ghi mặc định ANALYZE_TIMEOUT_MS là 90000, nhưng server.js dùng 300000 và compose đặt 900000. Bảng trên theo code. README cũng chưa nhắc web_search, effort và usage.
monitor/claude-cli/README.md lists ANALYZE_TIMEOUT_MS default as 90000, but server.js uses 300000 and compose sets 900000. The table above follows the code. The README also doesn't mention web_search, effort or usage.
Xử lý sự cố nhanhQuick troubleshooting
| Triệu chứngSymptom | Kiểm traCheck |
|---|---|
| Banner đỏ "cần đăng nhập lại"Red "needs re-login" banner | /health trả authed:false: chạy Re-login hoặc claude login./health returns authed:false: use Re-login or claude login. |
Re-login báo node-pty unavailable (503)Re-login returns node-pty unavailable (503) | Image build thiếu toolchain cho node-pty; rebuild, hoặc đăng nhập qua shell.The image built without the node-pty toolchain; rebuild, or log in from a shell. |
no such (or expired) login session (410)no such (or expired) login session (410) | Quá 10 phút hoặc đã bấm Start lần nữa; bắt đầu lại.More than 10 minutes passed or Start was clicked again; start over. |
Thẻ phân tích lỗi timeout after …msAnalysis card fails with timeout after …ms | Prompt quá dài / web search nhiều; tăng analysis_web_timeout_ms và nếu cần cả ANALYZE_TIMEOUT_MS.Prompt too long or many searches; raise analysis_web_timeout_ms and, if needed, ANALYZE_TIMEOUT_MS. |
| Xem hoạt động thựcSee real activity | docker logs fxconsole-claude-cli (log dạng [claude-cli] …)(lines prefixed [claude-cli]) |
Liên quan: fxconsole (web) · Hướng dẫn deploy.
Related: fxconsole (web) · Deployment guide.