miaoda-cli
v1.0.4
Published
秒哒 (Miaoda) Node.js CLI — chat-driven full-stack app builder
Readme
miaoda-cli (WorkBuddy skill)
WorkBuddy skill wrapping the 秒哒 (Miaoda) platform.
Login uses OAuth2 Device Authorization Grant (RFC 8628), mirroring the
cnb-skill / tencentmeeting-cli convention already used by WorkBuddy.
Requirements
- Node.js ≥ 18 (built-in
fetch,AbortController)
Install
cd node-cli
npm install
npm link # optional: expose `miaoda` globallyLogin / status / logout
miaoda login # opens browser, prints the device code and returns
miaoda login --no-browser # just prints the URL, no auto-open
miaoda login --debug # verbose device-auth trace
miaoda login --api-key <key> # skip device auth, save the key as a long-lived token
miaoda status # single-shot token exchange attempt; re-run until approved (auto-refreshes if expired)
miaoda logout # delete ~/.miaoda/tokenlogin --api-key <key> skips the OAuth device flow entirely and stores the key
directly as { "source": "env", "access_token": "<key>" } — a long-lived token
with no expiry check (equivalent to the MIAODA_TOKEN env var source).
Token is stored at ~/.miaoda/token (mode 0600):
{
"access_token": "...",
"refresh_token": "...",
"expires_at": 1770000000,
"platform_url": "https://www.miaoda.cn",
"client_id": "miaoda_cli",
"login_host": "https://www.miaoda.cn"
}Token resolution order
- WorkBuddy sandbox — when
AGENTOS_RUNTIME_IDis set, token is fetched from the URL inWORKBUDDY_TOKEN_URL_MIAODA_APP(WorkBuddy-managed, no local file involved). MIAODA_TOKEN_FOR_CODEBUDDY— set by the CodeBuddy NPC runtime.MIAODA_TOKEN— plain env var override (CI/automation), no expiry check.~/.miaoda/tokenfile — normal interactive login; auto-refreshes viarefresh_tokenwhen expired, prompts re-login when refresh fails. A token written bylogin --api-key(with"source": "env") is treated like an environment source: no expiry check, no refresh.
Exit codes follow the cnb-skill convention: 0 on success, 1 on any
failure (auth required, refresh failed, API error, etc).
Project layout
node-cli/
├── bin/miaoda.js CLI entry (commander)
├── src/
│ ├── api.js Miaoda platform HTTP client (chat/publish/...)
│ ├── exit-code.js Shared exit-code constants
│ ├── commands/
│ │ ├── login.js Device authorization grant flow
│ │ ├── logout.js Delete ~/.miaoda/token
│ │ └── status.js Check login state, auto-refresh
│ └── utils/
│ ├── token-path.js ~/.miaoda/token path
│ ├── load-token.js / save-token.js
│ ├── mask-token.js Redact tokens in --debug output
│ ├── open-browser.js Cross-platform `open`/`xdg-open`/`start`
│ ├── request-device-code.js POST /oauth2/device/auth
│ ├── do-token-request.js Single POST /oauth2/token attempt
│ ├── poll-token.js RFC 8628 §3.4/3.5 single exchange attempt (no loop)
│ ├── refresh-token.js POST grant_type=refresh_token
│ ├── get-token.js Sync token getter (legacy/back-compat)
│ ├── is-workbuddy-sandbox.js AGENTOS_RUNTIME_ID detection
│ ├── get-token-env-key.js WORKBUDDY_TOKEN_URL_<CONNECTOR> naming
│ ├── resolve-token-source.js 4-tier token source resolution
│ ├── resolve-token.js Resolve + auto-refresh + exit(1) on failure
│ ├── resolve-api-domain.js Pick API domain (login_host vs override)
│ └── device-auth.js Barrel re-export of the above
└── SKILL.md WorkBuddy skill descriptorSupported commands
login / logout / status
list-apps [--brief] [--name] [--page] [--size]
projects list [--brief] [--name] [--page] [--size] # 与 list-apps 一致
projects delete --app-id # 移动到回收站
projects restore --app-id # 从回收站恢复
projects versions --app-id [--page] [--size] # 列出应用版本(默认 page=1 size=10)
projects pull --app-id [--path] [--force] # 下载源码 zip 并解压到目录
points # 查询当前用户剩余秒点(monRemainingScore)
preview --app-id # 进入源码目录安装依赖并启动开发服务器(Taro 默认 h5 预览,Expo 默认启动开发服务器)
build --app-id # 进入源码目录安装依赖并编译(Taro 默认编译原生小程序,Expo 默认编译 Android)
app-detail --app-id [--no-context]
chat --text [--app-id --context-id] [--no-stream] [--prompt-generate]
generate-app [--app-id] [--context-id] [--watch]
trajectory --app-id [--last-event-id]
fetch-trajectory --app-id [--last-event-id]
get-context-id --app-id
conversation-history --app-id [--full] [--limit]
publish --app-id [--wait]
publish-status --release-idEach command supports --help.
preview looks for the app-id-named directory in the current directory, then
installs dependencies with npm install --legacy-peer-deps using the Taobao
mirror by default, and starts the development server. Override the mirror with
the MIAODA_NPM_REGISTRY environment variable. build first installs
dependencies the same way, then compiles. projects pull prompts for
confirmation before downloading because downloading source code deducts
Miaoda points.
The command adapts to the tech stack: Taro mini-programs are detected by the
presence of project.config.json in the project root. For mini-programs,
preview runs taro build --type h5 --watch (h5 preview) and build runs
taro build --type weapp (native mini-program). React Native Expo apps are
detected by metro.config.js in the project root; preview runs npm run start
and build runs npm run android (with a reminder to complete the Android
environment setup first). Otherwise the Vite commands (vite --open /
vite build) are used.
Application lifecycle (summary)
chat → (PRD refinement) → generate-app --watch → publish --wait
↓
https://<app_id>.appmiaoda.comAfter generation, iterate by re-issuing chat --app-id ... --context-id ....
Never re-run generate-app for the same app.
Placeholders to confirm with the platform team
The device-auth endpoints in src/utils/request-device-code.js /
poll-token.js / refresh-token.js assume:
POST {platformUrl}/oauth2/device/authreturns{ device_code, user_code, verification_uri, verification_uri_complete, expires_in, interval }POST {platformUrl}/oauth2/tokenwithgrant_type=urn:ietf:params:oauth:grant-type:device_codereturns{ access_token, refresh_token, expires_in, token_type, scope }on success, or{ error, error_description }withauthorization_pending/slow_down/access_denied/expired_tokenwhile pending.
Update these three files once the real Miaoda OAuth endpoint paths/fields are confirmed — the polling state machine and exit-code behavior do not need to change.
