MVP TypeScript CLI for CHAGEE ordering (region-aware, pickup-first).
Warning: This project is alpha and highly experimental. Use at your own risk. Attribution: Every line of code in this repository is written by AI.
This README is split into two tracks:
Using the CLIfor ordering usage.Developing chagee-clifor contributors.
- Overview
- Using the CLI
- Setup and Configuration
- Important Login Path (Manual Token)
- TUI Guide
- Ordering Workflows
- Command and State Reference
- Troubleshooting and FAQ
- Developing chagee-cli
- Contributing Guide
- License
- Project Notes
- Status: alpha, highly experimental. Use at your own risk.
- Default region: Singapore (
SG) - Extensible regions: yes (via custom region profiles)
- Fulfillment: pickup only
- Delivery: not implemented
- Discounts/promotions: not implemented yet
- Default mode:
dry-run(safe;placewill not submit real order)
Use this section if your goal is to run the tool and place orders.
- Node.js 20+
- npm 10+
If you are only using the tool, use the compiled run path:
npm install npm run build npm startBefore creating real orders, configure app ID outside source code:
export CHAGEE_APP_ID="<your-app-id>"Notes:
npm startruns the compiled CLI entrypoint (dist/cli.js).- On an interactive terminal,
chageestarts the TUI by default. npm run devis mainly for development (hot-run viatsx).
If login cannot finish automatically, use this flow.
- Log in to
https://h5.chagee.com.sg/mainin your normal browser session. - Open DevTools ->
Network. - Select any authenticated CHAGEE API request.
- Copy request header
authorizationvalue. - In CLI, run one of:
login token <token> # legacy aliases still accepted: login paste login import <token> Notes:
login pastereads token from clipboard.login importalso accepts full copied header lines, for example:authorization: Bearer <token>- After import, CLI validates token against profile endpoints before saving session.
chagee supports startup flags:
chagee --help chagee --version chagee --tui chagee --tui --yolo chagee --json chagee --mode dry-run chagee --region SG chagee --command "status" chagee --command "stores sort=wait" --command "status" --json chagee "/status"Supported options:
-h, --help-v, --version--tui--yolo(enables shell ordering commands)--location-policy <smart|ip-only|manual-only>--no-auto-locate(skip startup browser geolocation in TUI)--json--mode <dry-run|live>--region <CODE>-c, --command "<command>"(repeatable)
chagee-tui also supports:
-h, --help-v, --version--yolo--location-policy <smart|ip-only|manual-only>--no-auto-locate
Install from local checkout:
npm install npm run build npm install -g .Then use:
chagee chagee-tuiFor active development, use link mode:
npm linkUninstall global command later:
npm uninstall -g chagee-cliStart the pane-based TUI:
npm start # or npm run tuiThe TUI is built with Ink + React and uses the same command engine and APIs as line mode.
Example:
Phase:UNAUTH Mode:live Region:SG Shell:SAFE Watch:ON Mouse:ON Loc:1.3498,103.8489
Meaning:
Phase: current session phase (UNAUTH,READY,ORDER_CREATED, etc.).Mode: command safety mode (dry-runorlive).Region: active region profile code (defaultSG).Shell:SAFE(default) orYOLO.Watch: whether auto-refresh store polling is active.Mouse: whether TUI mouse handling is enabled.Loc: current latitude/longitude used for store distance sorting and queries.
Notes:
UNAUTH+livecan happen: login state and mode are independent.- Changing
mode,region,watch, orlocateupdates this header.
Tab: cycle focus forward (stores -> menu -> cart -> console -> stores)Shift+Tab: cycle focus backwardUp/Downinstores/menu/cart: move selected rowUp/Downinconsole: scroll visible output/logsCtrl+P/Ctrl+Ninconsole: previous/next command historyEnter (console pane): run typed slash commandEnter (stores pane): runuse <storeNo>Enter (menu pane): open staged variant/customization picker;Enteradvances stage and adds on final stageLeft/Right(menu variant picker): adjust quantity before addEsc(menu variant picker): go back one stage; closes picker from first stageEnter (cart pane): increment selected line qty (qty)Left/-(cart pane): decrease selected line qtyRight/+(cart pane): increase selected line qty/: jump to console input/mouse: reports current mouse capture stateCtrl+C: quit TUI
Layout:
- Top: three panes (
Stores,Menu,Cart) - Bottom: one
Consolepane for command input, updates, and order/payment notifications.
Console notes:
- Slash commands are supported (
/login,/otp,/stores,/status, etc.). - SAFE shell mode is default: most ordering commands in shell are blocked unless app was started with
--yolo(exception:/quoteis allowed). - Panel interactions still allow ordering flow in SAFE mode.
- Mouse click capture is enabled by default for pane navigation.
- Use
/mouse offif you want native terminal text selection/copy behavior. - Startup location policy defaults to
smart:default/ipstale after ~30m,browserstale after ~6h,manualstale after ~24h with large drift; large IP drift (>50km) also marks stale. - When stale and auto-locate is enabled, browser geolocation auto-runs (same as
/locate timeout=45 open=1), unless started with--no-auto-locateor--location-policy manual-only. - Store capacity auto-refresh starts on launch (
/watch on interval=10 sort=distance quiet=1). - Payment status auto-polling runs every 5s while an order payment is pending.
- Startup also performs an IP probe and may refresh to IP as fallback when stale/drift is detected.
- Distance heartbeat: backend re-checks IP geolocation every ~60s during store refresh for non-manual/non-browser sessions.
- For higher precision, run
/locate(browser geolocation) or set manually with:stores lat=<your-lat> lng=<your-lng>
Mouse support:
- terminal text highlight/copy is preserved (no pane click selection)
dry-runis default:placewill not create a real order.livemode enables real order creation.- Use
live onbefore placing a real order, thenlive offafter. - Sensitive values (for example
appId) must be supplied via environment/config, not hardcoded in repo.
Built-in:
SG(Singapore)
Region commands:
debug region listdebug region show [code]debug region set <code>debug region file(prints~/.chagee-cli/regions.json)
Add custom regions by creating ~/.chagee-cli/regions.json:
[ { "code": "MY", "name": "Malaysia", "country": "MY", "apiBase": "https://api-sea.chagee.com", "defaultPhoneCode": "+60", "currencyCode": "MYR", "currencySymbol": "RM", "appId": "<your-app-id>", "timeZone": "Asia/Kuala_Lumpur", "deviceTimeZoneRegion": "Asia/Kuala_Lumpur", "timezoneOffset": "480", "defaultLatitude": 3.139, "defaultLongitude": 101.6869 } ]Then restart CLI and run:
debug region list debug region set MY statusstores(default sort is distance)use <storeNo>menu search "jasmine"add <skuId> qty=1 spuId=<spuId>cartpay
At this point the CLI creates order + payment intent and opens payment URL in browser.
Replace placeholders and run in sequence:
status login # if guided login cannot complete automatically: # login token <token> stores use <storeNo> wait menu categories menu search "jasmine" item <spuId> add <skuId> qty=1 spuId=<spuId> name="Jasmine Green Milk Tea" cart pay pay await order Use the default login command:
login status Optional advanced form:
login timeout=180 cdp=http://127.0.0.1:9222 open=0 phone=+6591234567 Flow (login):
- If a valid session already exists, CLI reuses it after profile verification.
- CLI checks clipboard for a token and verifies it.
- CLI tries browser-session capture by scanning common local CDP endpoints (
9222/9223/9333). - CLI verifies profile and stores authenticated session.
Notes:
- CLI does not open a separate re-login flow. It reuses your existing browser session.
- If CDP capture fails, use the manual token flow in Important Login Path (Manual Token).
- Legacy aliases still work:
login web ...,login import ...,login paste.
If CDP/remote-debugging is unavailable, import token manually:
- Log in at
https://h5.chagee.com.sg/main. - Open DevTools -> Network and select any authenticated CHAGEE API request.
- Copy the
authorizationheader value. - Run one of:
login token <token>login paste(legacy, if token is already in clipboard)login import <token>(legacy)
login token / login import both accept copied header lines like authorization: Bearer ....
Menu visibility:
-
Store list and store menus are public and can be loaded without login.
-
Login is only required for account actions such as quote/order/payment.
-
One-time check:
stores(default sort is distance) -
Live polling:
watch on interval=10 sort=distance -
Stop polling:
watch off -
Selected store refresh:
wait -
Browser geolocation sync:
locate(orlocate timeout=90 open=0)
stores columns:
dist: distance from your configured location (lat/lng)cups: cups currently preparingwait(min): estimated waitstatus: store status text
menu search "<text>"for item candidates (spuId).item <spuId>for sellable SKUs (skuId).- Use returned
skuIdinadd.
You can prefix any command with / (example: /status).
SAFE shell mode (default):
- These shell commands require startup flag
--yolo:use,wait,menu,item,cart,add,qty,rm,clear,live on|off,place,checkout,confirm,order cancel,reorder,pay start,pay open(including legacystore use|wait). pay(guided) is allowed in SAFE shell when cart/order/payment context exists.- Panel-driven ordering in TUI remains available without
--yolo.
Simple flow commands:
helpstatusexitlogin [timeout=120] [cdp=auto|http://127.0.0.1:9222] [open=1] [phone=+6591234567]login token <token> [phone=+6591234567]otp <code> [phone=<phone>] [phoneCode=<dial-code>](legacy OTP verify)logoutlocate [timeout=60] [open=1]stores [sort=distance|wait|cups|name] [lat=1.35] [lng=103.81]watch on|off [interval=10] [sort=distance|wait|cups|name] [quiet=1]use <storeNo>waitmenu [search=<text>]menu categoriesmenu list <categoryId>menu search "<text>"item <spuId>add <skuId> [qty=1] [spuId=...] [name=...] [price=...] [specList=<json>] [attributeList=<json>]qty <item> <n>rm <item>clearcartquotelive on|offplace [open=1] [channelCode=H5] [payType=1]order [show|cancel [force=1]]orders [list [limit=10]|show <ref>|clear|file]reorder <ref> [append=0] [qty=1]pay [open=1] [channelCode=H5] [payType=1](guided)pay [status|await|open|start]pay await [timeout=180] [interval=3] [open=0]waits for terminal payment status.pay statusremains available for on-demand checks; background polling also updates status automatically while pending.
Cancel window notes:
order showdisplayscancelByAtandcancelRemainingSecwhen available from web-app order/payment payloads.order cancelwill block locally after window expiry unlessforce=1is provided.
Advanced/debug commands:
debug helpdebug last-reqdebug last-resdebug events [count=20]debug region list|show|set|filedebug mode dry-run|livedebug json on|offdebug guest show|set-token|bootstrap|clear
Legacy commands (still supported):
login start ...,login verify ...,login web ...,login import ...,login pastestores list ...,store use ...,store waitcart add ...,cart set ...,cart showcheckout,confirm,order show,order cancel,pay start,pay open,pay status
Session file:
~/.chagee-cli/session.json
Prompt format:
<PHASE>:<mode>
Phases:
UNAUTHAUTH_NO_STOREREADYCART_DIRTYQUOTEDORDER_CREATEDPAYMENT_PENDINGORDER_PAIDORDER_CANCELED
- OTP flow fails: use guided
login, or fallback to token import via Important Login Path (Manual Token). - Slider captcha blocks desktop OTP: use
login(guided token + browser flow), or manual token flow in Important Login Path (Manual Token). - Wrong country defaults: run
debug region showanddebug region set <code>. - Distance/order of stores seems wrong: run
statusto checklocation.source, then runlocateandstores. paydid not create/open payment: ensure login + cart + selected store, then retrypay.- Cart add fails: run
item <spuId>and use a validskuId. - Need reset: delete
~/.chagee-cli/session.jsonand restart. - Need payloads: run
debug last-req,debug last-res,debug events. - Browser session login cannot find a debuggable tab: run
login token <token>after copyingauthorizationfrom DevTools. See Important Login Path (Manual Token).
- Where do drink customization options (size/ice/sweetness) come from? A: From API data (
item/ goods detail response). The TUI does not hardcode these options. - Why do different drinks show different customization steps? A: Each product has its own option groups in API payloads. If a drink has fewer groups, fewer stages are shown.
- What is the staged picker order? A: The picker prioritizes
Variant/Sizefirst, thenIce, thenSweetness, then other groups. - How do I use the staged picker quickly? A:
Up/Downselects a value,Entermoves to next stage (or adds on final stage),Escmoves back,+/-adjusts quantity. - Why are long lines wrapped in the picker? A: To avoid hiding information. The footer now wraps lines instead of truncating with
.... - Do I need an order number to cancel an order? A: Not in normal flow.
order canceltargets the latest order in current session state. - Is there a cancellation time limit? A: Yes. Use
order showto inspectcancelByAtandcancelRemainingSec(when returned by API). - Do discounts/promo codes/member vouchers work? A: Not currently. This CLI does not apply or manage discounts right now.
- Why do you want to do this? A: Because I am extra.
- Are you trying to scam me? A: Nope. I'm using this for my own use. Use it at your own risk but I am not trying to scam you.
- Is this stable for production usage? A: No. This project is alpha and highly experimental; use at your own risk.
- Is this code AI-generated? A: Yes. Every line of code in this repository is written by AI.
Representative run from the TUI console pane:
/login # optional fallback: # /login token <token> /stores /use SG012 /menu search "jasmine" /add 30077881 qty=1 spuId=20001123 name="Jasmine Green Milk Tea" price=5.9 /pay Quick checks via piped input:
printf 'status\nstores\nexit\n' | npm startUse this section if you are changing code.
npm installnpm run dev: runsrc/cli.tsdirectly withtsx.npm run tui: runsrc/tui/cli.ts(interactive pane UI).npm run check: TypeScript typecheck only.npm run build: compile TypeScript todist/.npm start: run compileddist/cli.js.
- Pick an available package name in
package.json(for examplechagee-clior scoped). - Build and validate:
npm run check npm run build npm pack --dry-run- Login and publish:
npm login npm publish --access publicAfter publish, users install globally with:
npm install -g <your-package-name>Then run:
chagee chagee-tuisrc/cli.ts: executable CLI entrypoint (chagee)src/index.ts: reusable REPL app engine, command handling, state transitionssrc/tui/cli.ts: executable TUI entrypoint (chagee-tui)src/tui/index.tsx: TUI runtime and interaction model (Ink + React)src/config/regions.ts: built-in region defaults and region registry helperssrc/api/client.ts: API transport and endpoint wrapperssrc/types.ts: domain and state typessrc/lib/region-store.ts: custom region profile loader (~/.chagee-cli/regions.json)src/lib/session-store.ts: session persistencesrc/lib/state.ts: state helpers and phase derivationsrc/lib/parser.ts: command token/key-value parsingsrc/lib/format.ts: output formatting helpers
- API mapping is reverse-engineered and may break if backend contracts change.
- Keep user-facing safety defaults (
dry-run) intact. - Validate with both interactive (
npm run dev) and non-interactive (printf ... | npm start) flows.
- API payload mapping is reverse-engineered and can change without notice.
- Use live mode at your own risk.