docs: Remove sibling product and lab path leaks
Outsiders reading this module should not see private sibling names (tradey), unfinished rewire notes, or /fast/projects lab paths. Keep the library self-contained in README, design, plan, and test fixtures/identity strings.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Ship a Go library at `/fast/projects/golang/robinhood-agentic-mcp` that presents every Robinhood Agentic MCP tool with tradey’s transport/auth and Alpaca-shaped decimals/enums.
|
||||
**Goal:** Ship a Go library that presents every Robinhood Agentic MCP tool with OAuth/token transport and Alpaca-shaped decimals/enums.
|
||||
|
||||
**Architecture:** One shared `client.Client` (streamable HTTP session, JSON-RPC fallback). Asset-class packages (`accounts`, `equity`, `options`, `crypto`, `watchlists`, `market`, `scanner`) wrap `client.Caller`. Root `rh` is a facade (`Connect`, `Login`, re-exported `Config` and enums). Tests hit `internal/rhntest`, never live Robinhood.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Module path: `s1d3sw1ped/robinhood-agentic-mcp`. Go 1.25. Work only in `/fast/projects/golang/robinhood-agentic-mcp`. Do not modify tradey.
|
||||
- Module path: `s1d3sw1ped/robinhood-agentic-mcp`. Go 1.25. Work only in this repository.
|
||||
- Default MCP URL: `https://agent.robinhood.com/mcp/trading`.
|
||||
- Identity: `Config.Name` / `Config.Version` set before `Login`/`Connect`. Empty → `robinhood-agentic-mcp` / `0.1.0`.
|
||||
- Money/size/price: `decimal.Decimal` or `*decimal.Decimal`. Never `float64`. Encode with `Decimal.String()`. Unparseable JSON errors; omitted/null/`""`/`0` do not.
|
||||
@@ -165,7 +165,7 @@ func TestEncode(t *testing.T) {
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd /fast/projects/golang/robinhood-agentic-mcp && go test ./internal/wire/ -count=1`
|
||||
Run: `go test ./internal/wire/ -count=1`
|
||||
|
||||
Expected: FAIL module/package not found (create `go.mod` first if `go test` refuses, then FAIL undefined `wire`).
|
||||
|
||||
@@ -202,7 +202,7 @@ vet:
|
||||
go vet ./...
|
||||
```
|
||||
|
||||
`internal/wire/wire.go`: `Unwrap` reads optional `{"data": ...}` (same as tradey `unwrapData`). `Dec` accepts `nil` (zero, nil error), `float64`, `json.Number`, numeric `string`, empty string (zero, nil error); anything else errors with `fmt.Errorf("parse decimal: %v", v)`. `DecOpt`: `nil` or `""` → `(nil, nil)`; else `Dec` and return a pointer. `Encode` returns `d.String()`.
|
||||
`internal/wire/wire.go`: `Unwrap` reads optional `{"data": ...}`. `Dec` accepts `nil` (zero, nil error), `float64`, `json.Number`, numeric `string`, empty string (zero, nil error); anything else errors with `fmt.Errorf("parse decimal: %v", v)`. `DecOpt`: `nil` or `""` → `(nil, nil)`; else `Dec` and return a pointer. `Encode` returns `d.String()`.
|
||||
|
||||
- [ ] **Step 4: Run tests and make sure they pass**
|
||||
|
||||
@@ -395,7 +395,7 @@ git commit -m "feat: add httptest Robinhood MCP mock"
|
||||
- Produces: `type Client struct { URL, Token, Name, Version string; HTTP *http.Client; Hook Caller; session /* unexported */ }`, `func (c *Client) Call(ctx context.Context, name string, args map[string]any) (json.RawMessage, error)`
|
||||
- If `Hook != nil`, return `Hook.Call`
|
||||
- Else if `session != nil`, session path (implemented in Task 5)
|
||||
- Else JSON-RPC POST as tradey `rpcCall`: `Content-Type: application/json`, `Accept: application/json, text/event-stream`, `Authorization: Bearer `+Token when Token != `""`
|
||||
- Else JSON-RPC POST as `rpcCall`: `Content-Type: application/json`, `Accept: application/json, text/event-stream`, `Authorization: Bearer `+Token when Token != `""`
|
||||
- HTTP ≥300, decode errors, and JSON-RPC `error` → `*ToolError` with `Name` set
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
@@ -455,7 +455,7 @@ func TestClientCall_hook(t *testing.T) {
|
||||
}
|
||||
```
|
||||
|
||||
Copy tradey’s `rpcCall` from `/fast/projects/golang/tradey/internal/broker/mcp.go` (`rpcReq`/`rpcResp`, POST `tools/call`) and wrap failures with `ToolErrorf(name, "%w", err)` or `ToolErrorf(name, "http %d: %s", code, body)`.
|
||||
Implement `rpcCall` (`rpcReq`/`rpcResp`, POST `tools/call`) and wrap failures with `ToolErrorf(name, "%w", err)` or `ToolErrorf(name, "http %d: %s", code, body)`.
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
@@ -541,7 +541,7 @@ func TestLoginFromEnv(t *testing.T) {
|
||||
t.Setenv("ROBINHOOD_ACCESS_TOKEN", "tok-live")
|
||||
t.Setenv("ROBINHOOD_REFRESH_TOKEN", "ref")
|
||||
path := filepath.Join(t.TempDir(), "tokens.json")
|
||||
id, err := auth.Login(t.Context(), auth.Config{TokenFile: path, Name: "tradey", Version: "9"})
|
||||
id, err := auth.Login(t.Context(), auth.Config{TokenFile: path, Name: "example-app", Version: "9"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -563,14 +563,14 @@ func TestWithDefaults(t *testing.T) {
|
||||
if c.URL != auth.DefaultURL || c.Name != auth.DefaultName || c.Version != auth.DefaultVersion {
|
||||
t.Fatalf("%+v", c)
|
||||
}
|
||||
c = auth.Config{Name: "tradey", Version: "1.2.3", URL: "http://x"}.WithDefaults()
|
||||
if c.Name != "tradey" || c.Version != "1.2.3" || c.URL != "http://x" {
|
||||
c = auth.Config{Name: "example-app", Version: "1.2.3", URL: "http://x"}.WithDefaults()
|
||||
if c.Name != "example-app" || c.Version != "1.2.3" || c.URL != "http://x" {
|
||||
t.Fatalf("%+v", c)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Copy token JSON tags from `/fast/projects/golang/tradey/internal/broker/login.go` `TokenSet`.
|
||||
Define token JSON tags on `TokenSet` (access, refresh, type, expiry, client_id/secret, auth/token/redirect URLs, account_id).
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
@@ -604,15 +604,15 @@ git commit -m "feat: add token file and env Login"
|
||||
- Modify: `auth/login.go` (call `loginOAuth` instead of the placeholder error)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: tradey `/fast/projects/golang/tradey/internal/broker/oauth.go` and `connectSession` in `mcp.go`/`oauth.go`
|
||||
- Produces: `loginOAuth(ctx, cfg Config) (accountID string, error)` using `cfg.Name` as MCP `Implementation.Name` **and** OAuth `ClientName`; callback body `cfg.Name+" is signed in. You can close this tab."`; `client.ConnectSession(ctx, url string, tok auth.TokenSet, name, version string) (*Client, error)` port of tradey `connectSession` (OAuth handler if ClientID+TokenURL present, else bearer transport)
|
||||
- Consumes: Robinhood OAuth + MCP session connect requirements
|
||||
- Produces: `loginOAuth(ctx, cfg Config) (accountID string, error)` using `cfg.Name` as MCP `Implementation.Name` **and** OAuth `ClientName`; callback body `cfg.Name+" is signed in. You can close this tab."`; `client.ConnectSession(ctx, url string, tok auth.TokenSet, name, version string) (*Client, error)` (OAuth handler if ClientID+TokenURL present, else bearer transport)
|
||||
|
||||
No browser-OAuth test. Identity wiring is tested by exporting `oauthIdentity(cfg Config) (name, version string)` (unexported is fine if tested in package `auth`, not `auth_test`):
|
||||
|
||||
```go
|
||||
func TestOAuthIdentity(t *testing.T) {
|
||||
gotN, gotV := oauthIdentity(Config{Name: "tradey", Version: "0.9"})
|
||||
if gotN != "tradey" || gotV != "0.9" {
|
||||
gotN, gotV := oauthIdentity(Config{Name: "example-app", Version: "0.9"})
|
||||
if gotN != "example-app" || gotV != "0.9" {
|
||||
t.Fatalf("%s %s", gotN, gotV)
|
||||
}
|
||||
gotN, gotV = oauthIdentity(Config{})
|
||||
@@ -624,15 +624,15 @@ func TestOAuthIdentity(t *testing.T) {
|
||||
|
||||
`loginOAuth` must call `oauthIdentity(cfg.WithDefaults())` for Implementation.Name, ClientName, and the callback HTML.
|
||||
|
||||
Copy `loginOAuth` and `connectSession` / `bearerRT` / `tokenSetFrom` / `openBrowser` from tradey. Substitutions:
|
||||
Implement `loginOAuth` and `connectSession` / `bearerRT` / `tokenSetFrom` / `openBrowser`. Identity rules:
|
||||
|
||||
- `"tradey"` Implementation.Name → `cfg.Name` (defaulted)
|
||||
- Implementation.Name → `cfg.Name` (defaulted)
|
||||
- `"0.1.0"` Version → `cfg.Version`
|
||||
- `ClientName: "tradey"` → `cfg.Name`
|
||||
- `"Tradey is signed in..."` → `cfg.Name+" is signed in. You can close this tab."`
|
||||
- `"Open this URL to authorize Tradey with Robinhood"` → `"Open this URL to authorize "+cfg.Name+" with Robinhood"`
|
||||
- `"robinhood session expired; run tradey login"` → `"robinhood session expired; run Login again"`
|
||||
- After connect, `get_accounts` via a temporary `client.Client{session, Call: sessionCall}` is optional for account_id; port tradey’s block that writes `saved.AccountID`. Do **not** enforce tradey `supported()` IRA/margin bans.
|
||||
- `ClientName` → `cfg.Name`
|
||||
- Callback copy → `cfg.Name+" is signed in. You can close this tab."`
|
||||
- Authorize prompt → `"Open this URL to authorize "+cfg.Name+" with Robinhood"`
|
||||
- Expired session → `"robinhood session expired; run Login again"`
|
||||
- After connect, `get_accounts` via a temporary `client.Client{session, Call: sessionCall}` is optional for account_id; persist `saved.AccountID` when present. Do **not** enforce app-level IRA/margin bans.
|
||||
|
||||
`Login`: env token first, else `loginOAuth`.
|
||||
|
||||
@@ -644,7 +644,7 @@ Run: `go test ./auth/ -count=1 -run TestOAuthIdentity`
|
||||
|
||||
Expected: FAIL undefined `oauthIdentity`
|
||||
|
||||
- [ ] **Step 3: Write the OAuth implementation** (`oauthIdentity` + copy tradey OAuth with the substitutions below)
|
||||
- [ ] **Step 3: Write the OAuth implementation** (`oauthIdentity` + OAuth with the identity rules below)
|
||||
|
||||
- [ ] **Step 4: Run tests**
|
||||
|
||||
@@ -664,7 +664,7 @@ git commit -m "feat: add OAuth login and MCP session connect"
|
||||
### Task 7: accounts package
|
||||
|
||||
**Files:**
|
||||
- Create: `accounts/client.go`, `accounts/accounts.go`, `accounts/accounts_test.go`, `accounts/testdata/accounts.json` (copy `/fast/projects/golang/tradey/internal/broker/testdata/accounts.json`)
|
||||
- Create: `accounts/client.go`, `accounts/accounts.go`, `accounts/accounts_test.go`, `accounts/testdata/accounts.json`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `client.Caller`, `wire.Unwrap`, `wire.Dec` / `DecOpt`
|
||||
@@ -765,7 +765,7 @@ git commit -m "feat: add accounts MCP methods"
|
||||
| TechnicalIndicators | `get_equity_technical_indicators` | `Symbol`, `Type`, `Interval`, `StartTime time.Time` (required), `EndTime time.Time` (zero omits), `Bounds`, `AdjustmentType`, `Output`, `Period *int`, `NumStd *decimal.Decimal`, `FastPeriod *int`, `SlowPeriod *int`, `SignalPeriod *int`, `Multiplier *decimal.Decimal`, `Method` |
|
||||
| News | `get_equity_news` | `Symbol`, `Limit int`, `Cursor` |
|
||||
|
||||
`Quote`: `Symbol string`, `Bid, Ask, Last, PrevClose, Volume decimal.Decimal`. Parse tradey envelope `{quotes:[{quote:{symbol,last_trade_price,bid_price,ask_price}, close:{symbol,price}}]}`.
|
||||
`Quote`: `Symbol string`, `Bid, Ask, Last, PrevClose, Volume decimal.Decimal`. Parse Robinhood envelope `{quotes:[{quote:{symbol,last_trade_price,bid_price,ask_price}, close:{symbol,price}}]}`.
|
||||
|
||||
`Bar`: `Symbol`, `Time time.Time`, `Open, High, Low, Close, Volume decimal.Decimal`, `Interpolated bool`. Parse `{historicals:[{symbol, data_points:[{begins_at, open, high, low, close, volume, interpolated}]}]}`.
|
||||
|
||||
@@ -780,7 +780,7 @@ git commit -m "feat: add accounts MCP methods"
|
||||
|
||||
Assert `Last == 100`, `Bid == 99.9`, `PrevClose == 98`.
|
||||
|
||||
3. Historicals fixture from tradey parser: `begins_at` RFC3339, string OHLC.
|
||||
3. Historicals fixture from the parser: `begins_at` RFC3339, string OHLC.
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
@@ -836,7 +836,7 @@ func (c *Client) PlaceOrder(ctx context.Context, req PlaceOrderRequest) (Order,
|
||||
func (c *Client) CancelOrder(ctx context.Context, req CancelOrderRequest) error // account_number, order_id
|
||||
```
|
||||
|
||||
Wire map (tradey `placeArgs`): `quantity`/`limit_price`/`stop_price`/`dollar_amount` as `wire.Encode`; `time_in_force` only if non-empty; `ref_id` and `idempotency_key` both set from `RefID` on Place (not Review); `type` = `string(req.Type)` so `client.Stop` → `"stop_market"`.
|
||||
Wire map (`placeArgs`): `quantity`/`limit_price`/`stop_price`/`dollar_amount` as `wire.Encode`; `time_in_force` only if non-empty; `ref_id` and `idempotency_key` both set from `RefID` on Place (not Review); `type` = `string(req.Type)` so `client.Stop` → `"stop_market"`.
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
@@ -1000,9 +1000,9 @@ type Leg struct {
|
||||
| AddOption | `add_option_to_watchlist` |
|
||||
| RemoveOption | `remove_option_from_watchlist` |
|
||||
|
||||
Parse lists with `title` falling back to `name` (tradey). Items keep `symbol` / `object_type` / nested `instrument.symbol`. Do not filter object types in v1 (tradey’s equity-only filter stays in tradey).
|
||||
Parse lists with `title` falling back to `name`. Items keep `symbol` / `object_type` / nested `instrument.symbol`. Do not filter object types in v1 (equity-only filters stay in the app).
|
||||
|
||||
- [ ] **Step 1: Failing tests** — stub table 12 tools; rhntest Lists `{"watchlists":[{"id":"wl-1","title":"TRADEY"}]}` and Items `{"items":[{"symbol":"MU","object_type":"equity"}]}`.
|
||||
- [ ] **Step 1: Failing tests** — stub table 12 tools; rhntest Lists `{"watchlists":[{"id":"wl-1","title":"Tech"}]}` and Items `{"items":[{"symbol":"MU","object_type":"equity"}]}`.
|
||||
|
||||
- [ ] **Step 2:** `go test ./watchlists/ -count=1` FAIL
|
||||
|
||||
@@ -1086,7 +1086,7 @@ Filter/column request structs match the MCP properties (`filter_type`, `predicat
|
||||
|
||||
**Files:**
|
||||
- Create: `rh.go`, `connect.go`, `tools.go`, `rh_test.go`, `testdata/tools.json`, `README.md`
|
||||
- Modify: none of tradey
|
||||
- Modify: this repository only
|
||||
|
||||
**Interfaces:**
|
||||
```go
|
||||
@@ -1120,7 +1120,7 @@ func RegisteredTools() []string // concat of every package Tools()
|
||||
|
||||
`Connect` test: rhntest with token file mode 0600 + `get_accounts` fixture; `Connect` succeeds via RPC fallback; `api.Equity.Quotes` hits the mock.
|
||||
|
||||
README: module path, DefaultURL, Config.Name, example `Login`/`Connect`/`Equity.PlaceOrder` with `decimal` + `rh.Limit`, warning that this moves real money, not investment advice, no CLI, tradey not wired yet.
|
||||
README: module path, DefaultURL, Config.Name, example `Login`/`Connect`/`Equity.PlaceOrder` with `decimal` + `rh.Limit`, warning that this moves real money, not investment advice, no CLI.
|
||||
|
||||
- [ ] **Step 1: Write failing tests**
|
||||
|
||||
@@ -1151,7 +1151,7 @@ func TestConnect_rpcFallback(t *testing.T) {
|
||||
if err := auth.WriteTokens(path, "tok", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
api, err := rh.Connect(t.Context(), rh.Config{URL: s.URL, TokenFile: path, Name: "tradey"})
|
||||
api, err := rh.Connect(t.Context(), rh.Config{URL: s.URL, TokenFile: path, Name: "example-app"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1198,7 +1198,7 @@ git commit -m "feat: add rh Connect facade and tool coverage"
|
||||
| rhntest + per-method stub + round-trip | 3, 7–14 |
|
||||
| tools.json coverage | 15 |
|
||||
| No live MCP / no browser tests | 5–6, 15 |
|
||||
| tradey untouched | all |
|
||||
| this module stands alone | all |
|
||||
| README | 15 |
|
||||
|
||||
No `TBD`/`TODO`. `Connect` is defined in Task 15 after subpackages exist; earlier tasks test via `pkg.New(stub|rpcClient)`.
|
||||
|
||||
Reference in New Issue
Block a user