@farazirfan/costar-server-executor 1.7.20 → 1.7.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/index.html CHANGED
@@ -1034,6 +1034,25 @@
1034
1034
  <div class="loading-center"><div class="spinner"></div></div>
1035
1035
  </div>
1036
1036
  </div>
1037
+
1038
+ <!-- Session Management Card -->
1039
+ <div class="card">
1040
+ <div class="card-header">
1041
+ <h3>Agent Session</h3>
1042
+ <button class="btn btn-sm" onclick="loadSessionInfo()">Refresh</button>
1043
+ </div>
1044
+ <div class="config-list" id="session-info">
1045
+ <div class="loading-center"><div class="spinner"></div></div>
1046
+ </div>
1047
+ <div style="padding: 0 16px 16px; display: flex; gap: 10px; flex-wrap: wrap;">
1048
+ <button class="btn btn-sm btn-primary" id="compact-btn" onclick="triggerCompaction()">
1049
+ &#128230; Compact Session
1050
+ </button>
1051
+ <button class="btn btn-sm btn-danger" id="reset-session-btn" onclick="triggerResetSession()">
1052
+ &#128465; Reset Session
1053
+ </button>
1054
+ </div>
1055
+ </div>
1037
1056
  </div>
1038
1057
 
1039
1058
  <!-- ═══ Chat Page ═══ -->
@@ -1142,6 +1161,30 @@
1142
1161
 
1143
1162
  <!-- ═══ Settings Page ═══ -->
1144
1163
  <div class="page" id="page-settings">
1164
+ <div class="card">
1165
+ <div class="card-header">
1166
+ <h3>Environment Variables</h3>
1167
+ <div style="display:flex;gap:8px;">
1168
+ <button class="btn btn-sm" onclick="loadEnvVariables()">Refresh</button>
1169
+ <button class="btn btn-sm btn-primary" onclick="openEnvModal()">+ Add Variable</button>
1170
+ </div>
1171
+ </div>
1172
+ <div class="table-wrap">
1173
+ <table>
1174
+ <thead>
1175
+ <tr>
1176
+ <th>Key</th>
1177
+ <th>Value</th>
1178
+ <th>Actions</th>
1179
+ </tr>
1180
+ </thead>
1181
+ <tbody id="env-table-body">
1182
+ <tr><td colspan="3"><div class="loading-center"><div class="spinner"></div></div></td></tr>
1183
+ </tbody>
1184
+ </table>
1185
+ </div>
1186
+ </div>
1187
+
1145
1188
  <div class="card">
1146
1189
  <div class="card-header">
1147
1190
  <h3>Configuration</h3>
@@ -1211,6 +1254,25 @@
1211
1254
  </div>
1212
1255
  </div>
1213
1256
 
1257
+ <!-- Environment Variable Modal -->
1258
+ <div class="modal-overlay" id="env-modal">
1259
+ <div class="modal">
1260
+ <h3 id="env-modal-title">Add Environment Variable</h3>
1261
+ <div class="form-group">
1262
+ <label>Key (UPPERCASE_WITH_UNDERSCORES)</label>
1263
+ <input type="text" id="env-key" placeholder="e.g. MY_API_KEY" style="text-transform:uppercase;" />
1264
+ </div>
1265
+ <div class="form-group">
1266
+ <label>Value</label>
1267
+ <input type="text" id="env-value" placeholder="Enter value..." />
1268
+ </div>
1269
+ <div class="modal-actions">
1270
+ <button class="btn" onclick="closeEnvModal()">Cancel</button>
1271
+ <button class="btn btn-primary" onclick="saveEnvVariable()">Save</button>
1272
+ </div>
1273
+ </div>
1274
+ </div>
1275
+
1214
1276
  <script>
1215
1277
  /* ═══════════════════════════════════════════════
1216
1278
  * CoStar Dashboard - Client-side JS
@@ -1320,11 +1382,105 @@
1320
1382
  row('Cron Scheduler', cronStatus.isRunning ? 'Running' : 'Stopped'),
1321
1383
  row('Cron Last Check', cronStatus.lastCheckAt ? new Date(cronStatus.lastCheckAt).toLocaleString() : 'Never'),
1322
1384
  ].join('');
1385
+ // Load session info alongside dashboard
1386
+ loadSessionInfo();
1323
1387
  } catch (err) {
1324
1388
  document.getElementById('system-info').innerHTML = `<div style="color:var(--red);padding:12px;">Error: ${esc(err.message)}</div>`;
1325
1389
  }
1326
1390
  }
1327
1391
 
1392
+ // ─── Agent Session Management ─────────────────────
1393
+ async function loadSessionInfo() {
1394
+ try {
1395
+ const info = await api('/api/agent/session');
1396
+ const el = document.getElementById('session-info');
1397
+ if (!el) return;
1398
+
1399
+ el.innerHTML = [
1400
+ row('Session ID', info.sessionId),
1401
+ row('Session File', info.exists ? 'Exists' : 'Not created yet'),
1402
+ row('File Size', info.exists ? `${info.sizeKB} KB (${info.sizeBytes.toLocaleString()} bytes)` : '--'),
1403
+ row('Conversation Length', `${info.conversationLength} messages`),
1404
+ row('Status', info.isBusy ? '⏳ Busy' : '✅ Idle'),
1405
+ ].join('');
1406
+
1407
+ // Disable buttons if busy
1408
+ const compactBtn = document.getElementById('compact-btn');
1409
+ const resetBtn = document.getElementById('reset-session-btn');
1410
+ if (compactBtn) compactBtn.disabled = info.isBusy;
1411
+ if (resetBtn) resetBtn.disabled = info.isBusy;
1412
+ } catch (err) {
1413
+ const el = document.getElementById('session-info');
1414
+ if (el) el.innerHTML = `<div style="color:var(--red);padding:12px;">Error: ${esc(err.message)}</div>`;
1415
+ }
1416
+ }
1417
+
1418
+ async function triggerCompaction() {
1419
+ const btn = document.getElementById('compact-btn');
1420
+ if (!btn) return;
1421
+
1422
+ if (!confirm('Compact the agent session? This will summarize older messages to reduce context size.')) return;
1423
+
1424
+ btn.disabled = true;
1425
+ const origText = btn.innerHTML;
1426
+ btn.innerHTML = '&#9203; Compacting...';
1427
+
1428
+ try {
1429
+ const result = await api('/api/agent/compact', {
1430
+ method: 'POST',
1431
+ headers: { 'Content-Type': 'application/json' },
1432
+ });
1433
+
1434
+ if (result.compacted) {
1435
+ const before = result.result?.tokensBefore?.toLocaleString() ?? '?';
1436
+ const after = result.result?.tokensAfter?.toLocaleString() ?? '?';
1437
+ alert(`Compaction successful!\n\nTokens: ${before} → ${after}`);
1438
+ } else {
1439
+ alert(`Compaction did not reduce size: ${result.reason || 'Unknown reason'}`);
1440
+ }
1441
+
1442
+ // Refresh session info
1443
+ await loadSessionInfo();
1444
+ } catch (err) {
1445
+ alert(`Compaction failed: ${err.message}`);
1446
+ } finally {
1447
+ btn.disabled = false;
1448
+ btn.innerHTML = origText;
1449
+ }
1450
+ }
1451
+
1452
+ async function triggerResetSession() {
1453
+ const btn = document.getElementById('reset-session-btn');
1454
+ if (!btn) return;
1455
+
1456
+ if (!confirm('⚠️ Reset the agent session?\n\nThis will delete the conversation history. The agent will start fresh on the next turn.\n\nThis action cannot be undone.')) return;
1457
+
1458
+ btn.disabled = true;
1459
+ const origText = btn.innerHTML;
1460
+ btn.innerHTML = '&#9203; Resetting...';
1461
+
1462
+ try {
1463
+ const result = await api('/api/agent/reset-session', {
1464
+ method: 'POST',
1465
+ headers: { 'Content-Type': 'application/json' },
1466
+ });
1467
+
1468
+ if (result.success) {
1469
+ alert('Session reset successfully. The agent will start fresh on the next turn.');
1470
+ } else {
1471
+ alert(`Reset failed: ${result.error || 'Unknown error'}`);
1472
+ }
1473
+
1474
+ // Refresh session info
1475
+ await loadSessionInfo();
1476
+ } catch (err) {
1477
+ alert(`Reset failed: ${err.message}`);
1478
+ } finally {
1479
+ btn.disabled = false;
1480
+ btn.innerHTML = origText;
1481
+ }
1482
+ }
1483
+
1328
1484
  // ─── Update Banner ────────────────────────────────
1329
1485
  async function renderUpdateBanner(versionInfo) {
1330
1486
  const container = document.getElementById('update-banner-container');
@@ -2072,6 +2228,166 @@
2072
2228
  } catch (err) {
2073
2229
  document.getElementById('settings-config').innerHTML = `<div style="color:var(--red);padding:12px;">Error: ${esc(err.message)}</div>`;
2074
2230
  }
2231
+
2232
+ // Also load env variables
2233
+ loadEnvVariables();
2234
+ }
2235
+
2236
+ // ─── Environment Variables ───────────────────────
2237
+ let envVariables = [];
2238
+
2239
+ async function loadEnvVariables() {
2240
+ try {
2241
+ const data = await api('/api/env');
2242
+ envVariables = data.variables || [];
2243
+ const tbody = document.getElementById('env-table-body');
2244
+
2245
+ if (envVariables.length === 0) {
2246
+ tbody.innerHTML = `<tr><td colspan="3"><div class="empty-state"><div class="empty-icon">&#128295;</div><p>No environment variables configured.</p></div></td></tr>`;
2247
+ return;
2248
+ }
2249
+
2250
+ tbody.innerHTML = envVariables.map((env, idx) => {
2251
+ const valueDisplay = env.masked
2252
+ ? `<span style="font-family:var(--mono);color:var(--text-muted);" id="env-value-${idx}">${esc(env.value)}</span>
2253
+ <button class="btn btn-sm" onclick="revealEnvValue('${esc(env.key)}', ${idx})" id="env-reveal-${idx}" style="margin-left:8px;">&#128065; Reveal</button>`
2254
+ : `<span style="font-family:var(--mono);">${esc(env.value)}</span>`;
2255
+
2256
+ return `<tr>
2257
+ <td><code style="font-size:13px;color:var(--accent);">${esc(env.key)}</code></td>
2258
+ <td>${valueDisplay}</td>
2259
+ <td>
2260
+ <button class="btn btn-sm" onclick="editEnvVariable('${esc(env.key)}')">Edit</button>
2261
+ <button class="btn btn-sm btn-danger" onclick="deleteEnvVariable('${esc(env.key)}')">Delete</button>
2262
+ </td>
2263
+ </tr>`;
2264
+ }).join('');
2265
+ } catch (err) {
2266
+ document.getElementById('env-table-body').innerHTML = `<tr><td colspan="3" style="color:var(--red);padding:16px;">Error: ${esc(err.message)}</td></tr>`;
2267
+ }
2268
+ }
2269
+
2270
+ async function revealEnvValue(key, idx) {
2271
+ try {
2272
+ const btn = document.getElementById(`env-reveal-${idx}`);
2273
+ const valueEl = document.getElementById(`env-value-${idx}`);
2274
+
2275
+ if (!btn || !valueEl) return;
2276
+
2277
+ // If already revealed, hide it again
2278
+ if (btn.textContent.includes('Hide')) {
2279
+ const maskedVar = envVariables[idx];
2280
+ valueEl.textContent = maskedVar.value;
2281
+ btn.innerHTML = '&#128065; Reveal';
2282
+ return;
2283
+ }
2284
+
2285
+ btn.disabled = true;
2286
+ btn.textContent = 'Loading...';
2287
+
2288
+ const data = await api(`/api/env/${encodeURIComponent(key)}`);
2289
+ valueEl.textContent = data.value;
2290
+ btn.disabled = false;
2291
+ btn.innerHTML = '&#128065; Hide';
2292
+
2293
+ // Auto-hide after 10 seconds
2294
+ setTimeout(() => {
2295
+ if (btn.textContent.includes('Hide')) {
2296
+ const maskedVar = envVariables[idx];
2297
+ valueEl.textContent = maskedVar.value;
2298
+ btn.innerHTML = '&#128065; Reveal';
2299
+ }
2300
+ }, 10000);
2301
+ } catch (err) {
2302
+ alert('Failed to reveal value: ' + err.message);
2303
+ const btn = document.getElementById(`env-reveal-${idx}`);
2304
+ if (btn) {
2305
+ btn.disabled = false;
2306
+ btn.innerHTML = '&#128065; Reveal';
2307
+ }
2308
+ }
2309
+ }
2310
+
2311
+ function openEnvModal() {
2312
+ document.getElementById('env-modal').classList.add('open');
2313
+ document.getElementById('env-modal-title').textContent = 'Add Environment Variable';
2314
+ document.getElementById('env-key').value = '';
2315
+ document.getElementById('env-key').disabled = false;
2316
+ document.getElementById('env-value').value = '';
2317
+ document.getElementById('env-key').dataset.editMode = 'false';
2318
+ }
2319
+
2320
+ function closeEnvModal() {
2321
+ document.getElementById('env-modal').classList.remove('open');
2322
+ }
2323
+
2324
+ function editEnvVariable(key) {
2325
+ const envVar = envVariables.find(e => e.key === key);
2326
+ if (!envVar) return;
2327
+
2328
+ document.getElementById('env-modal').classList.add('open');
2329
+ document.getElementById('env-modal-title').textContent = 'Edit Environment Variable';
2330
+ document.getElementById('env-key').value = key;
2331
+ document.getElementById('env-key').disabled = true; // Can't change key when editing
2332
+ document.getElementById('env-value').value = ''; // Don't pre-fill for security
2333
+ document.getElementById('env-value').placeholder = 'Enter new value...';
2334
+ document.getElementById('env-key').dataset.editMode = 'true';
2335
+ }
2336
+
2337
+ async function saveEnvVariable() {
2338
+ const keyInput = document.getElementById('env-key');
2339
+ const valueInput = document.getElementById('env-value');
2340
+ const key = keyInput.value.trim().toUpperCase();
2341
+ const value = valueInput.value.trim();
2342
+ const isEdit = keyInput.dataset.editMode === 'true';
2343
+
2344
+ if (!key) {
2345
+ alert('Key is required.');
2346
+ return;
2347
+ }
2348
+
2349
+ if (!value) {
2350
+ alert('Value is required.');
2351
+ return;
2352
+ }
2353
+
2354
+ // Validate key format
2355
+ if (!/^[A-Z0-9_]+$/.test(key)) {
2356
+ alert('Key must contain only uppercase letters, numbers, and underscores.');
2357
+ return;
2358
+ }
2359
+
2360
+ try {
2361
+ if (isEdit) {
2362
+ await api(`/api/env/${encodeURIComponent(key)}`, {
2363
+ method: 'PUT',
2364
+ body: JSON.stringify({ value }),
2365
+ });
2366
+ } else {
2367
+ await api('/api/env', {
2368
+ method: 'POST',
2369
+ body: JSON.stringify({ key, value }),
2370
+ });
2371
+ }
2372
+
2373
+ closeEnvModal();
2374
+ loadEnvVariables();
2375
+ } catch (err) {
2376
+ alert('Failed to save: ' + err.message);
2377
+ }
2378
+ }
2379
+
2380
+ async function deleteEnvVariable(key) {
2381
+ if (!confirm(`Delete environment variable "${key}"?\n\nThis will remove it from ~/.costar/.env and the current process.`)) {
2382
+ return;
2383
+ }
2384
+
2385
+ try {
2386
+ await api(`/api/env/${encodeURIComponent(key)}`, { method: 'DELETE' });
2387
+ loadEnvVariables();
2388
+ } catch (err) {
2389
+ alert('Failed to delete: ' + err.message);
2390
+ }
2075
2391
  }
2076
2392
 
2077
2393
  // ─── Utilities ───────────────────────────────────
@@ -0,0 +1,117 @@
1
+ # Coinbase Trading Journal
2
+
3
+ > **Read this before every Coinbase operation.** Update after every trade.
4
+ > Never delete entries — mark outdated ones as `[OUTDATED]` with date and reason.
5
+
6
+ ---
7
+
8
+ ## Trade Log
9
+
10
+ | Date | Pair | Side | Type | Size | Fill Price | Fees | P&L | Notes |
11
+ |------|------|------|------|------|------------|------|-----|-------|
12
+ | *(No trades yet)* | | | | | | | | |
13
+
14
+ ---
15
+
16
+ ## API Quirks & Gotchas
17
+
18
+ > Things that tripped you up. Save others (your future self) the pain.
19
+
20
+ 1. All numeric values must be strings — `"100.00"` not `100`
21
+ 2. HTTP 200 doesn't mean success — always check `order.success`
22
+ 3. Duplicate `client_order_id` returns existing order, not error
23
+
24
+ *(Add more as you discover them)*
25
+
26
+ ---
27
+
28
+ ## Fee Insights
29
+
30
+ > Track fee rates and patterns to optimize execution.
31
+
32
+ - **Current fee tier:** *Unknown — check with `getTransactionSummary()`*
33
+ - **Maker rate:** *TBD*
34
+ - **Taker rate:** *TBD*
35
+
36
+ <!--
37
+ - [DATE] Fee observation. Example: post_only limit orders save 0.2% in fees vs market orders
38
+ -->
39
+
40
+ ---
41
+
42
+ ## Order Patterns That Work
43
+
44
+ > Reusable patterns you've validated with real trades.
45
+
46
+ <!--
47
+ - [DATE] PATTERN: description. PAIR: which pair. RESULT: outcome.
48
+ Example:
49
+ - [2026-02-14] PATTERN: Limit buy 0.5% below current price with GTD 1-hour expiry. PAIR: BTC-USD. RESULT: Filled 70% of the time, saved avg $15 in slippage vs market orders.
50
+ -->
51
+
52
+ ---
53
+
54
+ ## Order Patterns That Failed
55
+
56
+ > Equally important — what NOT to do.
57
+
58
+ <!--
59
+ - [DATE] PATTERN: description. PAIR: which pair. PROBLEM: what went wrong.
60
+ Example:
61
+ - [2026-02-14] PATTERN: Market sell large SOL position ($5000). PAIR: SOL-USD. PROBLEM: 1.2% slippage due to thin order book. Should have used limit or split into smaller orders.
62
+ -->
63
+
64
+ ---
65
+
66
+ ## Product-Specific Notes
67
+
68
+ > Observations about specific trading pairs.
69
+
70
+ <!--
71
+ - [DATE] PRODUCT: observation
72
+ Example:
73
+ - [2026-02-14] BTC-USD: min order $1, base_increment 0.00000001, quote_increment 0.01
74
+ - [2026-02-14] SOL-USD: wider spreads after hours, better to trade during US market hours
75
+ -->
76
+
77
+ ---
78
+
79
+ ## Scripts I've Created
80
+
81
+ | Script | Purpose | Works Well? | Last Modified |
82
+ |--------|---------|-------------|---------------|
83
+ | *(None yet)* | | | |
84
+
85
+ ---
86
+
87
+ ## Mistakes to Never Repeat
88
+
89
+ 1. *(Will accumulate from real trading)*
90
+
91
+ ---
92
+
93
+ ## Performance Summary
94
+
95
+ ### All Time
96
+ - **Total Trades:** 0
97
+ - **Total Fees Paid:** $0
98
+ - **Net P&L:** $0
99
+ - **Avg Slippage:** N/A
100
+
101
+ ### Current Week
102
+ - **Trades:** 0
103
+ - **Fees:** $0
104
+ - **P&L:** $0
105
+
106
+ ---
107
+
108
+ ## Open Questions
109
+
110
+ 1. What are the actual minimum order sizes for each product?
111
+ 2. How much slippage to expect on market orders for different pair sizes?
112
+ 3. What's the optimal order type for different trade sizes?
113
+ 4. When does `post_only` get rejected vs filled?
114
+
115
+ ---
116
+
117
+ > Every entry makes your next trade better. Record everything.
@@ -0,0 +1,217 @@
1
+ ---
2
+ name: coinbase
3
+ description: "Execute real trades on Coinbase Advanced Trade. Supports market, limit, stop-limit, and bracket orders for crypto. Handles account balances, order management, price data, portfolio tracking, and fee estimation. Use when the user wants to buy/sell crypto, check balances, place orders, manage positions, or do anything on Coinbase."
4
+ metadata:
5
+ emoji: "🪙"
6
+ requires:
7
+ env: ["COINBASE_API_KEY", "COINBASE_API_SECRET"]
8
+ ---
9
+
10
+ # Coinbase Trading Executor
11
+
12
+ You can execute real trades on Coinbase using the Advanced Trade API. This is real money — be precise, confirm with the user, and never skip risk checks.
13
+
14
+ ## CRITICAL: Always Read LEARNING.md First
15
+
16
+ **Before ANY Coinbase action, read [LEARNING.md](LEARNING.md).** It contains API quirks you've discovered, order patterns that work, mistakes to avoid, and fee insights from real trades.
17
+
18
+ ## Authentication
19
+
20
+ The user's Coinbase API credentials are available as environment variables:
21
+ - `COINBASE_API_KEY` — format: `organizations/{org_id}/apiKeys/{key_id}`
22
+ - `COINBASE_API_SECRET` — EC private key in PEM format
23
+
24
+ These use **CDP (Coinbase Developer Platform) keys** with **ES256 JWT** authentication. The `coinbase-api` npm package handles JWT generation internally.
25
+
26
+ ## Setup
27
+
28
+ Before first use, ensure the SDK is installed in the workspace:
29
+
30
+ ```bash
31
+ npm list coinbase-api 2>/dev/null || npm install coinbase-api
32
+ ```
33
+
34
+ ## How to Execute Trades
35
+
36
+ All Coinbase operations use TypeScript/JavaScript scripts via `execute_code` or shell commands. Use the `coinbase-api` package:
37
+
38
+ ```typescript
39
+ import { CBAdvancedTradeClient } from 'coinbase-api';
40
+
41
+ const client = new CBAdvancedTradeClient({
42
+ apiKey: process.env.COINBASE_API_KEY,
43
+ apiSecret: process.env.COINBASE_API_SECRET,
44
+ });
45
+ ```
46
+
47
+ ## Execution Workflow
48
+
49
+ ```
50
+ - [ ] Step 1: Read LEARNING.md
51
+ - [ ] Step 2: Verify balances and account status
52
+ - [ ] Step 3: Get current price and market data
53
+ - [ ] Step 4: Preview the order (estimate fees/slippage)
54
+ - [ ] Step 5: Execute the order
55
+ - [ ] Step 6: Verify order fill and record result
56
+ - [ ] Step 7: Update LEARNING.md
57
+ ```
58
+
59
+ ### Step 2: Check Balances
60
+
61
+ ```typescript
62
+ const accounts = await client.getAccounts();
63
+ for (const acct of accounts.accounts) {
64
+ if (parseFloat(acct.available_balance.value) > 0) {
65
+ console.log(`${acct.currency}: ${acct.available_balance.value}`);
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### Step 3: Get Price Data
71
+
72
+ ```typescript
73
+ // Current price
74
+ const product = await client.getProduct({ product_id: 'BTC-USD' });
75
+ console.log(`Price: $${product.price}, 24h change: ${product.price_percentage_change_24h}%`);
76
+
77
+ // Best bid/ask spread
78
+ const spread = await client.getBestBidAsk({ product_ids: ['BTC-USD'] });
79
+
80
+ // OHLCV candles (last 24h, 1-hour granularity)
81
+ const candles = await client.getProductCandles({
82
+ product_id: 'BTC-USD',
83
+ start: String(Math.floor(Date.now() / 1000) - 86400),
84
+ end: String(Math.floor(Date.now() / 1000)),
85
+ granularity: 'ONE_HOUR',
86
+ });
87
+ ```
88
+
89
+ ### Step 4: Preview Order
90
+
91
+ **Always preview before executing.** This shows estimated fees and slippage:
92
+
93
+ ```typescript
94
+ const preview = await client.previewOrder({
95
+ product_id: 'BTC-USD',
96
+ side: 'BUY',
97
+ order_configuration: {
98
+ market_market_ioc: { quote_size: '100.00' },
99
+ },
100
+ });
101
+ // Shows: quote_size, base_size, best_bid, best_ask, total_fees, slippage
102
+ ```
103
+
104
+ ### Step 5: Place Orders
105
+
106
+ See [references/order-types.md](references/order-types.md) for all order types and configurations.
107
+
108
+ **Market Buy** (spend $X of quote currency):
109
+ ```typescript
110
+ const order = await client.submitOrder({
111
+ client_order_id: crypto.randomUUID(),
112
+ product_id: 'BTC-USD',
113
+ side: 'BUY',
114
+ order_configuration: {
115
+ market_market_ioc: { quote_size: '100.00' }, // spend $100
116
+ },
117
+ });
118
+ ```
119
+
120
+ **Market Sell** (sell X units of base asset):
121
+ ```typescript
122
+ const order = await client.submitOrder({
123
+ client_order_id: crypto.randomUUID(),
124
+ product_id: 'BTC-USD',
125
+ side: 'SELL',
126
+ order_configuration: {
127
+ market_market_ioc: { base_size: '0.001' }, // sell 0.001 BTC
128
+ },
129
+ });
130
+ ```
131
+
132
+ **Limit Buy** (Good Till Cancel):
133
+ ```typescript
134
+ const order = await client.submitOrder({
135
+ client_order_id: crypto.randomUUID(),
136
+ product_id: 'BTC-USD',
137
+ side: 'BUY',
138
+ order_configuration: {
139
+ limit_limit_gtc: {
140
+ base_size: '0.001',
141
+ limit_price: '95000.00',
142
+ post_only: false,
143
+ },
144
+ },
145
+ });
146
+ ```
147
+
148
+ ### Step 6: Verify Order
149
+
150
+ ```typescript
151
+ // Check response immediately
152
+ if (order.success) {
153
+ const orderId = order.success_response.order_id;
154
+ // Get fill details
155
+ const status = await client.getOrder({ order_id: orderId });
156
+ console.log(`Status: ${status.status}, Filled: ${status.filled_size} @ ${status.average_filled_price}`);
157
+ } else {
158
+ console.error(`Order failed: ${order.error_response.message}`);
159
+ }
160
+ ```
161
+
162
+ **IMPORTANT:** Coinbase returns HTTP 200 even on order failure. Always check `order.success === true`.
163
+
164
+ ### Step 7: Update LEARNING.md
165
+
166
+ After every trade, record: asset, side, type, size, fill price, fees, outcome, and any API quirks encountered.
167
+
168
+ ## Order Management
169
+
170
+ ```typescript
171
+ // List open orders
172
+ const orders = await client.getOrders({ order_status: ['OPEN', 'PENDING'] });
173
+
174
+ // Cancel specific orders
175
+ const cancel = await client.cancelOrders({ order_ids: ['order-id-here'] });
176
+
177
+ // Get fills (trade executions)
178
+ const fills = await client.getFills({ order_id: 'order-id-here' });
179
+ ```
180
+
181
+ ## Portfolio & Fees
182
+
183
+ ```typescript
184
+ // Portfolio breakdown
185
+ const portfolios = await client.getPortfolios({});
186
+ const breakdown = await client.getPortfolioBreakdown({ portfolio_id: portfolios.portfolios[0].uuid });
187
+
188
+ // Fee tier info
189
+ const fees = await client.getTransactionSummary({});
190
+ // Shows: fee_tier, total_volume, maker_fee_rate, taker_fee_rate
191
+ ```
192
+
193
+ ## Critical Rules
194
+
195
+ 1. **ALWAYS preview orders before executing** — know the fees and slippage
196
+ 2. **All values are STRINGS** — `"100.00"` not `100`. The API rejects numbers.
197
+ 3. **client_order_id must be unique** — always use `crypto.randomUUID()`
198
+ 4. **Check `order.success`** — HTTP 200 doesn't mean order succeeded
199
+ 5. **Product ID format** — always `BASE-QUOTE` e.g. `BTC-USD`, `ETH-USD`, `SOL-USD`
200
+ 6. **Minimum order sizes vary** — check product details before ordering
201
+ 7. **Rate limits** — ~10 requests/second for private endpoints
202
+ 8. **Never place orders in a loop without delays** — respect rate limits
203
+ 9. **Log everything in LEARNING.md** — every trade, every error, every quirk
204
+
205
+ ## Reference Files
206
+
207
+ - [references/order-types.md](references/order-types.md) — All order configurations with examples
208
+ - [references/api-reference.md](references/api-reference.md) — Complete API endpoint list
209
+ - [references/products.md](references/products.md) — Common trading pairs and their details
210
+
211
+ ## Self-Evolution
212
+
213
+ You can modify any file in this skill:
214
+ - Found an API quirk? Update LEARNING.md and references
215
+ - Built a reusable script? Save it in `scripts/`
216
+ - Discovered a better workflow? Update this SKILL.md
217
+ - New order type pattern? Add to `references/order-types.md`