Kelly Invest (Webull)
Overview
Kelly Invest (Webull) is a Busabase Cloud App-in-Skill. Its canonical product
surface is the AirApp in Busabase, not a separate local-data product. The
same Hono source supports an explicitly requested local preview with OAuth
connection bootstrap. Use this skill as Kelly's read-only Webull portfolio
operator: an Overview (totals, unrealized P/L, day change, cash, allocation
donut, top movers, insights), a sortable Positions table, an Accounts view
(cash and margin), and a per-symbol Position detail pane.
Default behavior is AirApp-first. Unless the user explicitly asks only for
explanation, give the user the clickable AirApp URL. Start localhost only
when local preview/debugging is explicitly requested; it uses the same
Busabase resources and never offers another data provider. Use chat-only
mode only when the user says "纯聊天", "chat only", "不要打开 UI", or similar.
This is a monitoring dashboard: there is no approval lifecycle and no
decisions workflow.
Mandatory Dependencies
- Read and follow
$kelly-app-skill-creator for product behavior, visual
quality, responsive layout, and the complete canonical content/kelly-invest-webull-app/ artifact.
- Read and follow
$busabase for connection, target Space, node discovery,
ChangeRequests, review, and merge behavior.
- Read and follow
$busabase-app-creator for resource modeling, AirApp
runtime limits, security, validation, and deployment.
If a dependency is unavailable, preserve this skill's local artifact and
product contracts, stop before the unavailable Busabase operation, and report
the exact missing dependency. Do not invent a second data backend.
App UI Screenshots
Boundary
- The skill may read Webull account/balance/position data, normalize it, and
write Accounts/Positions/Settings rows into Busabase through the trusted
scripts/sync_webull.mjs process only.
- The AirApp reads Busabase records only; it is entirely read-only and must
NEVER place, modify, or cancel orders. NEVER move money, transfer,
withdraw, or change account settings. There is no trading path in this
skill by design (
readOnly: true, no writeProcedures).
- Treat all holdings/account data as sensitive. Never commit Webull
credentials,
config.local.json, env files, or raw Webull responses.
Busabase Resources
Three Bases under one application Folder (kelly-invest-webull), declared in
content/kelly-invest-webull-app/app/js/config.js and the generated template sidecars under content/:
accounts: Webull cash and margin accounts (account_id, account_type,
display_name, currency, net_liquidation, total_cash,
buying_power).
positions: holdings per account (position_id = account_id:symbol,
symbol, name, asset_type, account_id, quantity, avg_cost,
last_price, market_value, cost_basis, unrealized_pnl,
unrealized_pnl_pct, day_change, day_change_pct, currency). Weights,
totals, and allocation are computed at read time by
content/kelly-invest-webull-app/app/js/webull-model.js (assembleSnapshot), never stored.
settings: two rows — config (sanitized Webull region/base URL/account
allowlist, base currency, target allocation, generated_at, warnings — no
secrets) and onboarding.
Resources provision lazily through an idempotent Busabase ChangeRequest the
first time the app runs in a Space; see references/portfolio-schema.md for
exact field shapes. The AirApp never writes to any Base — only the trusted
sync script does.
Data Provider — Real Webull Integration
The skill reads Webull; the app only ever reads the normalized Busabase
snapshot.
lib/data-provider/webull.ts's field-mapping and credential-resolution
logic (mapAccount, mapPosition, normalizeAssetType,
resolveWebullCredentials) was ported verbatim into
content/kelly-invest-webull-app/app/js/webull-model.js, shared by both the browser (for
assembleSnapshot/computeInsights) and the trusted sync script (for the
Webull-specific mapping).
scripts/sync_webull.mjs is the only process that writes to Busabase. It
resolves Webull App Key / App Secret from env vars named in local config
(config.webull.app_key_env / app_secret_env, defaults
KELLY_INVEST_WEBULL_APP_KEY / KELLY_INVEST_WEBULL_APP_SECRET), fetches
live account/balance/position data, and writes normalized rows via
bases.createChangeRequest / records.changeRequest with its own
BUSABASE_BASE_URL / BUSABASE_API_KEY / BUSABASE_SPACE_ID credentials
(autoMerge: true).
- Webull has no first-party Node SDK — the official SDK is
webull-openapi-python-sdk (Python), and Webull's wire-level
signing/endpoint shape is not published outside that SDK (the retired
webull.ts adapter itself never implemented raw HTTP calls, only the
credential + mapping logic, for the same reason). scripts/sync_webull.mjs
therefore shells out to scripts/webull_bridge.py, which calls the exact
SDK methods documented in the original adapter's comments
(get_account_list(), get_account_balance(account_id),
get_account_positions(account_id)). Confirm the Python SDK's exact
import path against https://developer.webull.com/apis/docs/sdk/ before
first live use.
- For a credential-free dry run (e.g. CI, local testing), pass
--fixture <path/to/raw.json> to sync_webull.mjs with a JSON payload
shaped like { "accounts": [...], "positions": [...] } using the raw
Webull SDK field names documented in webull-model.js.
- Rate limit is ~10 requests / 30 seconds per App ID; batch and back off.
Run the sync from the skill root:
BUSABASE_BASE_URL=... BUSABASE_API_KEY=... BUSABASE_SPACE_ID=... \
node scripts/sync_webull.mjs
First Run And Onboarding
On invocation, check the config and onboarding Settings rows for
readiness. If absent, guide setup before syncing real holdings.
To connect Webull, the user needs a Webull OpenAPI App Key and App
Secret from Webull's OpenAPI Management/Portal (region id us; approval
typically takes 1-2 business days). A UAT test host exists at
us-openapi-alb.uat.webullbroker.com.
Ask for non-secret setup details only: region, base URL, base currency,
account allowlist, and which env var names hold the App Key / App Secret.
Never ask the user to paste secret values into chat. Secrets belong only in
local env files read by the trusted sync script, and config references them
by name (app_key_env, app_secret_env).
Demo Mode
?demo=1 opens a deterministic, fully offline mock portfolio (~10
positions across STOCK/ETF/CRYPTO in one cash and one margin account) with
computed P/L for documentation and screenshots.
?demo=positions, ?demo=accounts, and ?demo=detail select named mock
scenes.
lang=en or lang=zh forces UI chrome language for screenshots.
- Demo mode never reads or writes Busabase and never claims a real
connection.
Local App
Default behavior is AirApp-first — give the user the clickable AirApp URL.
Start pnpm --dir content/kelly-invest-webull-app dev only when local preview/debugging is explicitly
requested. UI language supports English and Chinese chrome with an Auto
default; the user is Chinese, keep symbols and provider values in their
original form.
Views
#/overview: total market value, total unrealized P/L (color + %), day
change, cash, and allocation-by-asset-type donut plus top day movers and
insights.
#/positions: sortable table (symbol, name, qty, avg cost, last, market
value, unrealized P/L %, weight).
#/accounts: cash and margin accounts with net liquidation, cash, buying
power.
#/accounts/<account_id>: account summary and its filtered positions.
#/positions/<symbol>: per-symbol detail pane.
#/settings: sanitized setup summary — data provider, Webull region and
base URL, credential-readiness booleans, and onboarding state. Never
exposes secret values.
Insights
Read-only, deterministic observations computed by computeInsights in
content/kelly-invest-webull-app/app/js/webull-model.js (ported verbatim from the retired
content/kelly-invest-webull-app/server/insights.ts): single_position_concentration,
crypto_concentration, allocation_drift, cash_drag, negative_cash,
top_gainer, top_laggard. Neutral facts/flags, never advice or actions —
no buy/sell/rebalance suggestions.
File Contract
Read references/portfolio-schema.md before editing the app,
content/kelly-invest-webull-app/app/js/config.js, content/kelly-invest-webull-app/app/js/webull-model.js, or
scripts/sync_webull.mjs.
Safety
- Read-only by design. Prefer read-only Webull scopes/credentials.
- Do not invent prices or fills. If a price or balance looks stale or
missing, add a snapshot warning instead of guessing.
- Redact credential-like strings in logs, reports, and UI state.
- Keep syncs idempotent —
scripts/sync_webull.mjs upserts by
account_id/position_id so repeated runs never duplicate rows.
1---2name: kelly-invest-webull3description: Busabase-backed, read-only App-in-Skill portfolio dashboard that aggregates Webull brokerage holdings (accounts, positions, cash/margin, market value, unrealized P/L, day change, buying power). Use when the user invokes $kelly-invest-webull or /kelly-invest-webull, wants to review their Webull portfolio, holdings, positions, accounts (cash/margin), asset allocation, market value, unrealized P/L, day change, cash, or buying power. Read-only aggregation only — it never places, modifies, or cancels orders and never moves money.4---56# Kelly Invest (Webull)78## Overview910Kelly Invest (Webull) is a Busabase Cloud App-in-Skill. Its canonical product11surface is the AirApp in Busabase, not a separate local-data product. The12same Hono source supports an explicitly requested local preview with OAuth13connection bootstrap. Use this skill as Kelly's read-only Webull portfolio14operator: an Overview (totals, unrealized P/L, day change, cash, allocation15donut, top movers, insights), a sortable Positions table, an Accounts view16(cash and margin), and a per-symbol Position detail pane.1718Default behavior is AirApp-first. Unless the user explicitly asks only for19explanation, give the user the clickable AirApp URL. Start localhost only20when local preview/debugging is explicitly requested; it uses the same21Busabase resources and never offers another data provider. Use chat-only22mode only when the user says "纯聊天", "chat only", "不要打开 UI", or similar.2324This is a monitoring dashboard: there is no approval lifecycle and no25decisions workflow.2627## Mandatory Dependencies28291. Read and follow `$kelly-app-skill-creator` for product behavior, visual30 quality, responsive layout, and the complete canonical `content/kelly-invest-webull-app/` artifact.312. Read and follow `$busabase` for connection, target Space, node discovery,32 ChangeRequests, review, and merge behavior.333. Read and follow `$busabase-app-creator` for resource modeling, AirApp34 runtime limits, security, validation, and deployment.3536If a dependency is unavailable, preserve this skill's local artifact and37product contracts, stop before the unavailable Busabase operation, and report38the exact missing dependency. Do not invent a second data backend.3940## App UI Screenshots4142<table>43 <tr>44 <td width="50%"><img src="assets/screenshots/overview.webp" alt="Kelly Invest overview"></td>45 <td width="50%"><img src="assets/screenshots/positions.webp" alt="Kelly Invest positions"></td>46 </tr>47 <tr>48 <td><strong>Overview</strong><br>Portfolio command desk with market value, unrealized P/L, day change, cash, an allocation-by-asset-type donut, and top day movers.</td>49 <td><strong>Positions</strong><br>Sortable holdings table across symbol, asset type, quantity, average cost, last price, market value, unrealized P/L, and portfolio weight.</td>50 </tr>51 <tr>52 <td width="50%"><img src="assets/screenshots/accounts.webp" alt="Kelly Invest accounts"></td>53 <td width="50%"><img src="assets/screenshots/detail.webp" alt="Kelly Invest position detail"></td>54 </tr>55 <tr>56 <td><strong>Accounts</strong><br>Per-account view (cash and margin) with net liquidation, total cash, buying power, and the positions held in each account.</td>57 <td><strong>Position detail</strong><br>Single-symbol view with cost basis, market value, unrealized P/L and percentage, day change, weight, and holding account.</td>58 </tr>59</table>6061## Boundary6263- The skill may read Webull account/balance/position data, normalize it, and64 write Accounts/Positions/Settings rows into Busabase through the trusted65 `scripts/sync_webull.mjs` process only.66- The AirApp reads Busabase records only; it is entirely read-only and must67 NEVER place, modify, or cancel orders. NEVER move money, transfer,68 withdraw, or change account settings. There is no trading path in this69 skill by design (`readOnly: true`, no `writeProcedures`).70- Treat all holdings/account data as sensitive. Never commit Webull71 credentials, `config.local.json`, env files, or raw Webull responses.7273## Busabase Resources7475Three Bases under one application Folder (`kelly-invest-webull`), declared in76`content/kelly-invest-webull-app/app/js/config.js` and the generated template sidecars under `content/`:7778- `accounts`: Webull cash and margin accounts (`account_id`, `account_type`,79 `display_name`, `currency`, `net_liquidation`, `total_cash`,80 `buying_power`).81- `positions`: holdings per account (`position_id` = `account_id:symbol`,82 `symbol`, `name`, `asset_type`, `account_id`, `quantity`, `avg_cost`,83 `last_price`, `market_value`, `cost_basis`, `unrealized_pnl`,84 `unrealized_pnl_pct`, `day_change`, `day_change_pct`, `currency`). Weights,85 totals, and allocation are computed at read time by86 `content/kelly-invest-webull-app/app/js/webull-model.js` (`assembleSnapshot`), never stored.87- `settings`: two rows — `config` (sanitized Webull region/base URL/account88 allowlist, base currency, target allocation, generated_at, warnings — no89 secrets) and `onboarding`.9091Resources provision lazily through an idempotent Busabase ChangeRequest the92first time the app runs in a Space; see `references/portfolio-schema.md` for93exact field shapes. The AirApp never writes to any Base — only the trusted94sync script does.9596## Data Provider — Real Webull Integration9798The skill reads Webull; the app only ever reads the normalized Busabase99snapshot.100101- `lib/data-provider/webull.ts`'s field-mapping and credential-resolution102 logic (`mapAccount`, `mapPosition`, `normalizeAssetType`,103 `resolveWebullCredentials`) was ported **verbatim** into104 `content/kelly-invest-webull-app/app/js/webull-model.js`, shared by both the browser (for105 `assembleSnapshot`/`computeInsights`) and the trusted sync script (for the106 Webull-specific mapping).107- `scripts/sync_webull.mjs` is the only process that writes to Busabase. It108 resolves Webull App Key / App Secret from env vars named in local config109 (`config.webull.app_key_env` / `app_secret_env`, defaults110 `KELLY_INVEST_WEBULL_APP_KEY` / `KELLY_INVEST_WEBULL_APP_SECRET`), fetches111 live account/balance/position data, and writes normalized rows via112 `bases.createChangeRequest` / `records.changeRequest` with its own113 `BUSABASE_BASE_URL` / `BUSABASE_API_KEY` / `BUSABASE_SPACE_ID` credentials114 (`autoMerge: true`).115- Webull has no first-party Node SDK — the official SDK is116 `webull-openapi-python-sdk` (Python), and Webull's wire-level117 signing/endpoint shape is not published outside that SDK (the retired118 `webull.ts` adapter itself never implemented raw HTTP calls, only the119 credential + mapping logic, for the same reason). `scripts/sync_webull.mjs`120 therefore shells out to `scripts/webull_bridge.py`, which calls the exact121 SDK methods documented in the original adapter's comments122 (`get_account_list()`, `get_account_balance(account_id)`,123 `get_account_positions(account_id)`). Confirm the Python SDK's exact124 import path against `https://developer.webull.com/apis/docs/sdk/` before125 first live use.126- For a credential-free dry run (e.g. CI, local testing), pass127 `--fixture <path/to/raw.json>` to `sync_webull.mjs` with a JSON payload128 shaped like `{ "accounts": [...], "positions": [...] }` using the raw129 Webull SDK field names documented in `webull-model.js`.130- Rate limit is ~10 requests / 30 seconds per App ID; batch and back off.131132Run the sync from the skill root:133134```bash135BUSABASE_BASE_URL=... BUSABASE_API_KEY=... BUSABASE_SPACE_ID=... \136 node scripts/sync_webull.mjs137```138139## First Run And Onboarding140141On invocation, check the `config` and `onboarding` Settings rows for142readiness. If absent, guide setup before syncing real holdings.143144To connect Webull, the user needs a Webull OpenAPI **App Key** and **App145Secret** from Webull's OpenAPI Management/Portal (region id `us`; approval146typically takes 1-2 business days). A UAT test host exists at147`us-openapi-alb.uat.webullbroker.com`.148149Ask for non-secret setup details only: region, base URL, base currency,150account allowlist, and which env var names hold the App Key / App Secret.151Never ask the user to paste secret values into chat. Secrets belong only in152local env files read by the trusted sync script, and config references them153by name (`app_key_env`, `app_secret_env`).154155## Demo Mode156157- `?demo=1` opens a deterministic, fully offline mock portfolio (~10158 positions across STOCK/ETF/CRYPTO in one cash and one margin account) with159 computed P/L for documentation and screenshots.160- `?demo=positions`, `?demo=accounts`, and `?demo=detail` select named mock161 scenes.162- `lang=en` or `lang=zh` forces UI chrome language for screenshots.163- Demo mode never reads or writes Busabase and never claims a real164 connection.165166## Local App167168Default behavior is AirApp-first — give the user the clickable AirApp URL.169Start `pnpm --dir content/kelly-invest-webull-app dev` only when local preview/debugging is explicitly170requested. UI language supports English and Chinese chrome with an `Auto`171default; the user is Chinese, keep symbols and provider values in their172original form.173174## Views175176- `#/overview`: total market value, total unrealized P/L (color + %), day177 change, cash, and allocation-by-asset-type donut plus top day movers and178 insights.179- `#/positions`: sortable table (symbol, name, qty, avg cost, last, market180 value, unrealized P/L %, weight).181- `#/accounts`: cash and margin accounts with net liquidation, cash, buying182 power.183- `#/accounts/<account_id>`: account summary and its filtered positions.184- `#/positions/<symbol>`: per-symbol detail pane.185- `#/settings`: sanitized setup summary — data provider, Webull region and186 base URL, credential-readiness booleans, and onboarding state. Never187 exposes secret values.188189## Insights190191Read-only, deterministic observations computed by `computeInsights` in192`content/kelly-invest-webull-app/app/js/webull-model.js` (ported verbatim from the retired193`content/kelly-invest-webull-app/server/insights.ts`): `single_position_concentration`,194`crypto_concentration`, `allocation_drift`, `cash_drag`, `negative_cash`,195`top_gainer`, `top_laggard`. Neutral facts/flags, never advice or actions —196no buy/sell/rebalance suggestions.197198## File Contract199200Read `references/portfolio-schema.md` before editing the app,201`content/kelly-invest-webull-app/app/js/config.js`, `content/kelly-invest-webull-app/app/js/webull-model.js`, or202`scripts/sync_webull.mjs`.203204## Safety205206- Read-only by design. Prefer read-only Webull scopes/credentials.207- Do not invent prices or fills. If a price or balance looks stale or208 missing, add a snapshot warning instead of guessing.209- Redact credential-like strings in logs, reports, and UI state.210- Keep syncs idempotent — `scripts/sync_webull.mjs` upserts by211 `account_id`/`position_id` so repeated runs never duplicate rows.