@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/dist/agent/agent.d.ts +18 -0
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +70 -0
- package/dist/agent/agent.js.map +1 -1
- package/dist/web-server.d.ts.map +1 -1
- package/dist/web-server.js +137 -0
- package/dist/web-server.js.map +1 -1
- package/package.json +1 -1
- package/public/index.html +316 -0
- package/skills/coinbase/LEARNING.md +117 -0
- package/skills/coinbase/SKILL.md +217 -0
- package/skills/coinbase/references/api-reference.md +124 -0
- package/skills/coinbase/references/order-types.md +150 -0
- package/skills/coinbase/references/products.md +93 -0
- package/skills/coinbase/scripts/coinbase-trade.ts +274 -0
- package/skills/trading/SKILL.md +2 -4
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
|
+
📦 Compact Session
|
|
1050
|
+
</button>
|
|
1051
|
+
<button class="btn btn-sm btn-danger" id="reset-session-btn" onclick="triggerResetSession()">
|
|
1052
|
+
🗑 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 = '⏳ 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 = '⏳ 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">🔧</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;">👁 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 = '👁 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 = '👁 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 = '👁 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 = '👁 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`
|