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.

  • Cập nhậtUpdated 13/09/2026
  • NguồnSource monitor/claude-cli/README.md
  • monitor/claude-cli/Dockerfile · monitor/claude-cli/server.js
  • monitor/fxconsole/docker-compose.yml · monitor/fxconsole/app/Services/ClaudeService.php

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.

Vùng xám điều khoản sử dụngToS grey area

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" .- CLI
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 (team subscription)"]
  VOL[("Volumes claude_creds / claude_config")] -. "credentials survive redeploys" .- CLI

Container 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-code toàn cục (build arg CLAUDE_CODE_VERSION, mặc định latest) và server Express trong server.js. node-pty cần python3, make, g++ lúc build.
  • Chạy dưới user node; thư mục /home/node/.claude và /home/node/.config được tạo sẵn để volume thuộc quyền node (UID 1000) chứ không phải root.
  • ANALYZE_CONCURRENCY: "1": chỉ một lệnh claude -p chạy cùng lúc để bảo vệ quota chung. Chỉ tăng khi thật cần.
  • Hai named volume claude_creds và 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-code globally (build arg CLAUDE_CODE_VERSION, default latest) plus the Express server in server.js. node-pty needs python3, make and g++ at build time.
  • It runs as the node user; /home/node/.claude and /home/node/.config are pre-created so the mounted volumes are owned by node (UID 1000), not root.
  • ANALYZE_CONCURRENCY: "1": only one claude -p at a time, to protect the shared quota. Raise it cautiously.
  • The claude_creds and claude_config named 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)

MethodPathCông dụngPurpose
GET/healthLiveness + 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/modelsDanh 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/startBắ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 /analyze chạy không có tool (--allowed-tools ""): một lượt, chi phí dự đoán được. Khi web_search=true, riêng lệnh đó được bật WebSearch,WebFetch qua cả --tools và --allowed-tools (ở chế độ -p khô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.
  • effort chỉ nhận low, medium, high, xhigh, max; giá trị khác bị bỏ qua thay vì làm lệnh lỗi.
  • usage gồ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 login sống (dưới PTY) giữa hai lệnh, vì OAuth-PKCE giữ code_verifier trong bộ nhớ tiến trình. Phiên sống tối đa 10 phút; /relogin/start mới sẽ huỷ phiên dở trước đó.
  • By default /analyze runs with no tools (--allowed-tools ""): one turn, predictable spend. With web_search=true that single call gets WebSearch,WebFetch through both --tools and --allowed-tools (in -p mode nobody can answer a permission prompt); the call becomes a multi-turn agent loop and costs several times as much.
  • effort accepts only low, medium, high, xhigh, max; anything else is dropped rather than failing the call.
  • usage carries 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 login process alive (under a PTY) across the two calls, because OAuth-PKCE holds the code_verifier in process memory. A session lives at most 10 minutes; a new /relogin/start cancels 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
  1. 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.
  2. Dán code nhận được trở lại terminal.Paste the code back into the terminal.
  3. Xong: container phục vụ /analyze cho tới khi refresh token hết hạn hẳn.Done: the container serves /analyze until 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/analysis hiệ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/analysis shows 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
File backup là secretThe backup is a secret

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

  • ClaudeService có 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 đặt analysis_engine=api và có key). Engine cli khô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ùng analyzeTextViaCli() (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ỗi timeout after khô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 theo ANALYZE_TIMEOUT_MS của nó (compose đặt 900 000).
  • Trang admin gọi /health và /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.
  • ClaudeService has two engines: cli (default and active: this sidecar) and api (the metered Messages API, dormant, used only if an admin sets analysis_engine=api and a key exists). The cli engine 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 use analyzeTextViaCli() (plain text, up to 2 attempts).
  • Every attempt that reached the model is recorded in the budget (AnalysisBudget), even a malformed reply. A timeout after error 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 own ANALYZE_TIMEOUT_MS (compose sets 900 000).
  • The admin page calls /health and /models (8 s timeout), /relogin/start (35 s) and /relogin/complete (60 s) through the routes /admin/analysis/health, /admin/analysis/relogin/start and /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):

KeyMặc địnhDefaultÝ nghĩaMeaning
analysis_enginecliEngine viết nhận địnhNarrative engine
gold_analysis_modelclaude-sonnet-5Model mặc địnhDefault model
analysis_interval_minutes240Chu kỳ chạy (tối thiểu 30)Run interval (minimum 30)
analysis_start_time00:00Mốc neo trong ngày để các slot rơi đúng giờDaily anchor so slots land on predictable clock times
analysis_exclude_daystrốngemptyThứ bỏ qua, ví dụ sat,sunWeekdays to skip, e.g. sat,sun
analysis_budget_calls_5h24Số lệnh tối đa trong 5 giờMax calls per 5 hours
analysis_budget_calls_week220Số lệnh tối đa mỗi tuầnMax calls per week
analysis_budget_tokens_5h / _week0Ngân sách token (0 = không giới hạn theo token)Token budgets (0 = no token cap)
analysis_budget_threshold90Khi 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_search0Cho phép web search (chỉ custom prompt)Allow web search (custom prompts only)
analysis_effortmediumMứ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ường result.
  • claude login in 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 a result field.
  • claude login prints 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)

EnvMặc định trong codeCode defaultÝ nghĩaMeaning
PORT8788Cổng lắng nghe (nội bộ)Listen port (internal)
ANALYZE_CONCURRENCY1Số claude -p chạy đồng thời tối đa (bảo vệ quota)Max simultaneous claude -p (protects quota)
ANALYZE_TIMEOUT_MS300000Trần timeout mỗi request; compose đặt 900000Per-request hard ceiling; compose sets 900000
WEB_TOOLSWebSearch,WebFetchTool bật khi web_search=trueTools unlocked when web_search=true
CLAUDE_BINclaudeTên / đường dẫn binary CLICLI binary name or path
LOGIN_CMDloginLệ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_REhttps?://\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
Lệch tài liệuDoc drift

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ứngSymptomKiể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 …msPrompt 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 activitydocker logs fxconsole-claude-cli (log dạng [claude-cli] …)(lines prefixed [claude-cli])